github.com/jiasir/docker@v1.3.3-0.20170609024000-252e610103e7/reference/store.go (about)

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