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