github.com/skanehira/moby@v17.12.1-ce-rc2+incompatible/reference/store.go (about)

     1  package reference
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"sort"
     9  	"sync"
    10  
    11  	"github.com/docker/distribution/reference"
    12  	"github.com/docker/docker/pkg/ioutils"
    13  	"github.com/opencontainers/go-digest"
    14  	"github.com/pkg/errors"
    15  )
    16  
    17  var (
    18  	// ErrDoesNotExist is returned if a reference is not found in the
    19  	// store.
    20  	ErrDoesNotExist notFoundError = "reference does not exist"
    21  )
    22  
    23  // An Association is a tuple associating a reference with an image ID.
    24  type Association struct {
    25  	Ref reference.Named
    26  	ID  digest.Digest
    27  }
    28  
    29  // Store provides the set of methods which can operate on a reference store.
    30  type Store interface {
    31  	References(id digest.Digest) []reference.Named
    32  	ReferencesByName(ref reference.Named) []Association
    33  	AddTag(ref reference.Named, id digest.Digest, force bool) error
    34  	AddDigest(ref reference.Canonical, id digest.Digest, force bool) error
    35  	Delete(ref reference.Named) (bool, error)
    36  	Get(ref reference.Named) (digest.Digest, error)
    37  }
    38  
    39  type store struct {
    40  	mu sync.RWMutex
    41  	// jsonPath is the path to the file where the serialized tag data is
    42  	// stored.
    43  	jsonPath string
    44  	// Repositories is a map of repositories, indexed by name.
    45  	Repositories map[string]repository
    46  	// referencesByIDCache is a cache of references indexed by ID, to speed
    47  	// up References.
    48  	referencesByIDCache map[digest.Digest]map[string]reference.Named
    49  }
    50  
    51  // Repository maps tags to digests. The key is a stringified Reference,
    52  // including the repository name.
    53  type repository map[string]digest.Digest
    54  
    55  type lexicalRefs []reference.Named
    56  
    57  func (a lexicalRefs) Len() int      { return len(a) }
    58  func (a lexicalRefs) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
    59  func (a lexicalRefs) Less(i, j int) bool {
    60  	return a[i].String() < a[j].String()
    61  }
    62  
    63  type lexicalAssociations []Association
    64  
    65  func (a lexicalAssociations) Len() int      { return len(a) }
    66  func (a lexicalAssociations) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
    67  func (a lexicalAssociations) Less(i, j int) bool {
    68  	return a[i].Ref.String() < a[j].Ref.String()
    69  }
    70  
    71  // NewReferenceStore creates a new reference store, tied to a file path where
    72  // the set of references are serialized in JSON format.
    73  func NewReferenceStore(jsonPath string) (Store, error) {
    74  	abspath, err := filepath.Abs(jsonPath)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  
    79  	store := &store{
    80  		jsonPath:            abspath,
    81  		Repositories:        make(map[string]repository),
    82  		referencesByIDCache: make(map[digest.Digest]map[string]reference.Named),
    83  	}
    84  	// Load the json file if it exists, otherwise create it.
    85  	if err := store.reload(); os.IsNotExist(err) {
    86  		if err := store.save(); err != nil {
    87  			return nil, err
    88  		}
    89  	} else if err != nil {
    90  		return nil, err
    91  	}
    92  	return store, nil
    93  }
    94  
    95  // AddTag adds a tag reference to the store. If force is set to true, existing
    96  // references can be overwritten. This only works for tags, not digests.
    97  func (store *store) AddTag(ref reference.Named, id digest.Digest, force bool) error {
    98  	if _, isCanonical := ref.(reference.Canonical); isCanonical {
    99  		return errors.WithStack(invalidTagError("refusing to create a tag with a digest reference"))
   100  	}
   101  	return store.addReference(reference.TagNameOnly(ref), id, force)
   102  }
   103  
   104  // AddDigest adds a digest reference to the store.
   105  func (store *store) AddDigest(ref reference.Canonical, id digest.Digest, force bool) error {
   106  	return store.addReference(ref, id, force)
   107  }
   108  
   109  func favorDigest(originalRef reference.Named) (reference.Named, error) {
   110  	ref := originalRef
   111  	// If the reference includes a digest and a tag, we must store only the
   112  	// digest.
   113  	canonical, isCanonical := originalRef.(reference.Canonical)
   114  	_, isNamedTagged := originalRef.(reference.NamedTagged)
   115  
   116  	if isCanonical && isNamedTagged {
   117  		trimmed, err := reference.WithDigest(reference.TrimNamed(canonical), canonical.Digest())
   118  		if err != nil {
   119  			// should never happen
   120  			return originalRef, err
   121  		}
   122  		ref = trimmed
   123  	}
   124  	return ref, nil
   125  }
   126  
   127  func (store *store) addReference(ref reference.Named, id digest.Digest, force bool) error {
   128  	ref, err := favorDigest(ref)
   129  	if err != nil {
   130  		return err
   131  	}
   132  
   133  	refName := reference.FamiliarName(ref)
   134  	refStr := reference.FamiliarString(ref)
   135  
   136  	if refName == string(digest.Canonical) {
   137  		return errors.WithStack(invalidTagError("refusing to create an ambiguous tag using digest algorithm as name"))
   138  	}
   139  
   140  	store.mu.Lock()
   141  	defer store.mu.Unlock()
   142  
   143  	repository, exists := store.Repositories[refName]
   144  	if !exists || repository == nil {
   145  		repository = make(map[string]digest.Digest)
   146  		store.Repositories[refName] = repository
   147  	}
   148  
   149  	oldID, exists := repository[refStr]
   150  
   151  	if exists {
   152  		// force only works for tags
   153  		if digested, isDigest := ref.(reference.Canonical); isDigest {
   154  			return errors.WithStack(conflictingTagError("Cannot overwrite digest " + digested.Digest().String()))
   155  		}
   156  
   157  		if !force {
   158  			return errors.WithStack(
   159  				conflictingTagError(
   160  					fmt.Sprintf("Conflict: Tag %s is already set to image %s, if you want to replace it, please use the force option", refStr, oldID.String()),
   161  				),
   162  			)
   163  		}
   164  
   165  		if store.referencesByIDCache[oldID] != nil {
   166  			delete(store.referencesByIDCache[oldID], refStr)
   167  			if len(store.referencesByIDCache[oldID]) == 0 {
   168  				delete(store.referencesByIDCache, oldID)
   169  			}
   170  		}
   171  	}
   172  
   173  	repository[refStr] = id
   174  	if store.referencesByIDCache[id] == nil {
   175  		store.referencesByIDCache[id] = make(map[string]reference.Named)
   176  	}
   177  	store.referencesByIDCache[id][refStr] = ref
   178  
   179  	return store.save()
   180  }
   181  
   182  // Delete deletes a reference from the store. It returns true if a deletion
   183  // happened, or false otherwise.
   184  func (store *store) Delete(ref reference.Named) (bool, error) {
   185  	ref, err := favorDigest(ref)
   186  	if err != nil {
   187  		return false, err
   188  	}
   189  
   190  	ref = reference.TagNameOnly(ref)
   191  
   192  	refName := reference.FamiliarName(ref)
   193  	refStr := reference.FamiliarString(ref)
   194  
   195  	store.mu.Lock()
   196  	defer store.mu.Unlock()
   197  
   198  	repository, exists := store.Repositories[refName]
   199  	if !exists {
   200  		return false, ErrDoesNotExist
   201  	}
   202  
   203  	if id, exists := repository[refStr]; exists {
   204  		delete(repository, refStr)
   205  		if len(repository) == 0 {
   206  			delete(store.Repositories, refName)
   207  		}
   208  		if store.referencesByIDCache[id] != nil {
   209  			delete(store.referencesByIDCache[id], refStr)
   210  			if len(store.referencesByIDCache[id]) == 0 {
   211  				delete(store.referencesByIDCache, id)
   212  			}
   213  		}
   214  		return true, store.save()
   215  	}
   216  
   217  	return false, ErrDoesNotExist
   218  }
   219  
   220  // Get retrieves an item from the store by reference
   221  func (store *store) Get(ref reference.Named) (digest.Digest, error) {
   222  	if canonical, ok := ref.(reference.Canonical); ok {
   223  		// If reference contains both tag and digest, only
   224  		// lookup by digest as it takes precedence over
   225  		// tag, until tag/digest combos are stored.
   226  		if _, ok := ref.(reference.Tagged); ok {
   227  			var err error
   228  			ref, err = reference.WithDigest(reference.TrimNamed(canonical), canonical.Digest())
   229  			if err != nil {
   230  				return "", err
   231  			}
   232  		}
   233  	} else {
   234  		ref = reference.TagNameOnly(ref)
   235  	}
   236  
   237  	refName := reference.FamiliarName(ref)
   238  	refStr := reference.FamiliarString(ref)
   239  
   240  	store.mu.RLock()
   241  	defer store.mu.RUnlock()
   242  
   243  	repository, exists := store.Repositories[refName]
   244  	if !exists || repository == nil {
   245  		return "", ErrDoesNotExist
   246  	}
   247  
   248  	id, exists := repository[refStr]
   249  	if !exists {
   250  		return "", ErrDoesNotExist
   251  	}
   252  
   253  	return id, nil
   254  }
   255  
   256  // References returns a slice of references to the given ID. The slice
   257  // will be nil if there are no references to this ID.
   258  func (store *store) References(id digest.Digest) []reference.Named {
   259  	store.mu.RLock()
   260  	defer store.mu.RUnlock()
   261  
   262  	// Convert the internal map to an array for two reasons:
   263  	// 1) We must not return a mutable
   264  	// 2) It would be ugly to expose the extraneous map keys to callers.
   265  
   266  	var references []reference.Named
   267  	for _, ref := range store.referencesByIDCache[id] {
   268  		references = append(references, ref)
   269  	}
   270  
   271  	sort.Sort(lexicalRefs(references))
   272  
   273  	return references
   274  }
   275  
   276  // ReferencesByName returns the references for a given repository name.
   277  // If there are no references known for this repository name,
   278  // ReferencesByName returns nil.
   279  func (store *store) ReferencesByName(ref reference.Named) []Association {
   280  	refName := reference.FamiliarName(ref)
   281  
   282  	store.mu.RLock()
   283  	defer store.mu.RUnlock()
   284  
   285  	repository, exists := store.Repositories[refName]
   286  	if !exists {
   287  		return nil
   288  	}
   289  
   290  	var associations []Association
   291  	for refStr, refID := range repository {
   292  		ref, err := reference.ParseNormalizedNamed(refStr)
   293  		if err != nil {
   294  			// Should never happen
   295  			return nil
   296  		}
   297  		associations = append(associations,
   298  			Association{
   299  				Ref: ref,
   300  				ID:  refID,
   301  			})
   302  	}
   303  
   304  	sort.Sort(lexicalAssociations(associations))
   305  
   306  	return associations
   307  }
   308  
   309  func (store *store) save() error {
   310  	// Store the json
   311  	jsonData, err := json.Marshal(store)
   312  	if err != nil {
   313  		return err
   314  	}
   315  	return ioutils.AtomicWriteFile(store.jsonPath, jsonData, 0600)
   316  }
   317  
   318  func (store *store) reload() error {
   319  	f, err := os.Open(store.jsonPath)
   320  	if err != nil {
   321  		return err
   322  	}
   323  	defer f.Close()
   324  	if err := json.NewDecoder(f).Decode(&store); err != nil {
   325  		return err
   326  	}
   327  
   328  	for _, repository := range store.Repositories {
   329  		for refStr, refID := range repository {
   330  			ref, err := reference.ParseNormalizedNamed(refStr)
   331  			if err != nil {
   332  				// Should never happen
   333  				continue
   334  			}
   335  			if store.referencesByIDCache[refID] == nil {
   336  				store.referencesByIDCache[refID] = make(map[string]reference.Named)
   337  			}
   338  			store.referencesByIDCache[refID][refStr] = ref
   339  		}
   340  	}
   341  
   342  	return nil
   343  }