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