github.com/docker/docker@v299999999.0.0-20200612211812-aaf470eca7b5+incompatible/reference/store.go (about)

     1  package reference // import "github.com/docker/docker/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  	digest "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  		if oldID == id {
   153  			// Nothing to do. The caller may have checked for this using store.Get in advance, but store.mu was unlocked in the meantime, so this can legitimately happen nevertheless.
   154  			return nil
   155  		}
   156  
   157  		// force only works for tags
   158  		if digested, isDigest := ref.(reference.Canonical); isDigest {
   159  			return errors.WithStack(conflictingTagError("Cannot overwrite digest " + digested.Digest().String()))
   160  		}
   161  
   162  		if !force {
   163  			return errors.WithStack(
   164  				conflictingTagError(
   165  					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()),
   166  				),
   167  			)
   168  		}
   169  
   170  		if store.referencesByIDCache[oldID] != nil {
   171  			delete(store.referencesByIDCache[oldID], refStr)
   172  			if len(store.referencesByIDCache[oldID]) == 0 {
   173  				delete(store.referencesByIDCache, oldID)
   174  			}
   175  		}
   176  	}
   177  
   178  	repository[refStr] = id
   179  	if store.referencesByIDCache[id] == nil {
   180  		store.referencesByIDCache[id] = make(map[string]reference.Named)
   181  	}
   182  	store.referencesByIDCache[id][refStr] = ref
   183  
   184  	return store.save()
   185  }
   186  
   187  // Delete deletes a reference from the store. It returns true if a deletion
   188  // happened, or false otherwise.
   189  func (store *store) Delete(ref reference.Named) (bool, error) {
   190  	ref, err := favorDigest(ref)
   191  	if err != nil {
   192  		return false, err
   193  	}
   194  
   195  	ref = reference.TagNameOnly(ref)
   196  
   197  	refName := reference.FamiliarName(ref)
   198  	refStr := reference.FamiliarString(ref)
   199  
   200  	store.mu.Lock()
   201  	defer store.mu.Unlock()
   202  
   203  	repository, exists := store.Repositories[refName]
   204  	if !exists {
   205  		return false, ErrDoesNotExist
   206  	}
   207  
   208  	if id, exists := repository[refStr]; exists {
   209  		delete(repository, refStr)
   210  		if len(repository) == 0 {
   211  			delete(store.Repositories, refName)
   212  		}
   213  		if store.referencesByIDCache[id] != nil {
   214  			delete(store.referencesByIDCache[id], refStr)
   215  			if len(store.referencesByIDCache[id]) == 0 {
   216  				delete(store.referencesByIDCache, id)
   217  			}
   218  		}
   219  		return true, store.save()
   220  	}
   221  
   222  	return false, ErrDoesNotExist
   223  }
   224  
   225  // Get retrieves an item from the store by reference
   226  func (store *store) Get(ref reference.Named) (digest.Digest, error) {
   227  	if canonical, ok := ref.(reference.Canonical); ok {
   228  		// If reference contains both tag and digest, only
   229  		// lookup by digest as it takes precedence over
   230  		// tag, until tag/digest combos are stored.
   231  		if _, ok := ref.(reference.Tagged); ok {
   232  			var err error
   233  			ref, err = reference.WithDigest(reference.TrimNamed(canonical), canonical.Digest())
   234  			if err != nil {
   235  				return "", err
   236  			}
   237  		}
   238  	} else {
   239  		ref = reference.TagNameOnly(ref)
   240  	}
   241  
   242  	refName := reference.FamiliarName(ref)
   243  	refStr := reference.FamiliarString(ref)
   244  
   245  	store.mu.RLock()
   246  	defer store.mu.RUnlock()
   247  
   248  	repository, exists := store.Repositories[refName]
   249  	if !exists || repository == nil {
   250  		return "", ErrDoesNotExist
   251  	}
   252  
   253  	id, exists := repository[refStr]
   254  	if !exists {
   255  		return "", ErrDoesNotExist
   256  	}
   257  
   258  	return id, nil
   259  }
   260  
   261  // References returns a slice of references to the given ID. The slice
   262  // will be nil if there are no references to this ID.
   263  func (store *store) References(id digest.Digest) []reference.Named {
   264  	store.mu.RLock()
   265  	defer store.mu.RUnlock()
   266  
   267  	// Convert the internal map to an array for two reasons:
   268  	// 1) We must not return a mutable
   269  	// 2) It would be ugly to expose the extraneous map keys to callers.
   270  
   271  	var references []reference.Named
   272  	for _, ref := range store.referencesByIDCache[id] {
   273  		references = append(references, ref)
   274  	}
   275  
   276  	sort.Sort(lexicalRefs(references))
   277  
   278  	return references
   279  }
   280  
   281  // ReferencesByName returns the references for a given repository name.
   282  // If there are no references known for this repository name,
   283  // ReferencesByName returns nil.
   284  func (store *store) ReferencesByName(ref reference.Named) []Association {
   285  	refName := reference.FamiliarName(ref)
   286  
   287  	store.mu.RLock()
   288  	defer store.mu.RUnlock()
   289  
   290  	repository, exists := store.Repositories[refName]
   291  	if !exists {
   292  		return nil
   293  	}
   294  
   295  	var associations []Association
   296  	for refStr, refID := range repository {
   297  		ref, err := reference.ParseNormalizedNamed(refStr)
   298  		if err != nil {
   299  			// Should never happen
   300  			return nil
   301  		}
   302  		associations = append(associations,
   303  			Association{
   304  				Ref: ref,
   305  				ID:  refID,
   306  			})
   307  	}
   308  
   309  	sort.Sort(lexicalAssociations(associations))
   310  
   311  	return associations
   312  }
   313  
   314  func (store *store) save() error {
   315  	// Store the json
   316  	jsonData, err := json.Marshal(store)
   317  	if err != nil {
   318  		return err
   319  	}
   320  	return ioutils.AtomicWriteFile(store.jsonPath, jsonData, 0600)
   321  }
   322  
   323  func (store *store) reload() error {
   324  	f, err := os.Open(store.jsonPath)
   325  	if err != nil {
   326  		return err
   327  	}
   328  	defer f.Close()
   329  	if err := json.NewDecoder(f).Decode(&store); err != nil {
   330  		return err
   331  	}
   332  
   333  	for _, repository := range store.Repositories {
   334  		for refStr, refID := range repository {
   335  			ref, err := reference.ParseNormalizedNamed(refStr)
   336  			if err != nil {
   337  				// Should never happen
   338  				continue
   339  			}
   340  			if store.referencesByIDCache[refID] == nil {
   341  				store.referencesByIDCache[refID] = make(map[string]reference.Named)
   342  			}
   343  			store.referencesByIDCache[refID][refStr] = ref
   344  		}
   345  	}
   346  
   347  	return nil
   348  }