reference/store.go
2655954c
 package reference
7de380c5
 
 import (
 	"encoding/json"
 	"errors"
 	"fmt"
 	"os"
 	"path/filepath"
6e37b622
 	"sort"
7de380c5
 	"sync"
 
3a127939
 	"github.com/docker/distribution/reference"
ea3cbd32
 	"github.com/docker/docker/pkg/ioutils"
7a855799
 	"github.com/opencontainers/go-digest"
7de380c5
 )
 
 var (
 	// ErrDoesNotExist is returned if a reference is not found in the
 	// store.
 	ErrDoesNotExist = errors.New("reference does not exist")
 )
 
 // An Association is a tuple associating a reference with an image ID.
 type Association struct {
3a127939
 	Ref reference.Named
80522398
 	ID  digest.Digest
7de380c5
 }
 
 // Store provides the set of methods which can operate on a tag store.
 type Store interface {
3a127939
 	References(id digest.Digest) []reference.Named
 	ReferencesByName(ref reference.Named) []Association
 	AddTag(ref reference.Named, id digest.Digest, force bool) error
 	AddDigest(ref reference.Canonical, id digest.Digest, force bool) error
 	Delete(ref reference.Named) (bool, error)
 	Get(ref reference.Named) (digest.Digest, error)
7de380c5
 }
 
 type store struct {
 	mu sync.RWMutex
 	// jsonPath is the path to the file where the serialized tag data is
 	// stored.
 	jsonPath string
 	// Repositories is a map of repositories, indexed by name.
 	Repositories map[string]repository
 	// referencesByIDCache is a cache of references indexed by ID, to speed
 	// up References.
3a127939
 	referencesByIDCache map[digest.Digest]map[string]reference.Named
3aa4a007
 	// platform is the container target platform for this store (which may be
 	// different to the host operating system
 	platform string
7de380c5
 }
 
80522398
 // Repository maps tags to digests. The key is a stringified Reference,
7de380c5
 // including the repository name.
80522398
 type repository map[string]digest.Digest
7de380c5
 
3a127939
 type lexicalRefs []reference.Named
6e37b622
 
3a127939
 func (a lexicalRefs) Len() int      { return len(a) }
 func (a lexicalRefs) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
 func (a lexicalRefs) Less(i, j int) bool {
 	return a[i].String() < a[j].String()
 }
6e37b622
 
 type lexicalAssociations []Association
 
3a127939
 func (a lexicalAssociations) Len() int      { return len(a) }
 func (a lexicalAssociations) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
 func (a lexicalAssociations) Less(i, j int) bool {
 	return a[i].Ref.String() < a[j].Ref.String()
 }
6e37b622
 
2655954c
 // NewReferenceStore creates a new reference store, tied to a file path where
 // the set of references are serialized in JSON format.
3aa4a007
 func NewReferenceStore(jsonPath, platform string) (Store, error) {
7de380c5
 	abspath, err := filepath.Abs(jsonPath)
 	if err != nil {
 		return nil, err
 	}
 
 	store := &store{
 		jsonPath:            abspath,
 		Repositories:        make(map[string]repository),
3a127939
 		referencesByIDCache: make(map[digest.Digest]map[string]reference.Named),
3aa4a007
 		platform:            platform,
7de380c5
 	}
 	// Load the json file if it exists, otherwise create it.
 	if err := store.reload(); os.IsNotExist(err) {
 		if err := store.save(); err != nil {
 			return nil, err
 		}
 	} else if err != nil {
 		return nil, err
 	}
 	return store, nil
 }
 
2655954c
 // AddTag adds a tag reference to the store. If force is set to true, existing
7de380c5
 // references can be overwritten. This only works for tags, not digests.
3a127939
 func (store *store) AddTag(ref reference.Named, id digest.Digest, force bool) error {
 	if _, isCanonical := ref.(reference.Canonical); isCanonical {
20e759ab
 		return errors.New("refusing to create a tag with a digest reference")
 	}
3a127939
 	return store.addReference(reference.TagNameOnly(ref), id, force)
20e759ab
 }
 
2655954c
 // AddDigest adds a digest reference to the store.
3a127939
 func (store *store) AddDigest(ref reference.Canonical, id digest.Digest, force bool) error {
20e759ab
 	return store.addReference(ref, id, force)
 }
 
0fbfeb17
 func favorDigest(originalRef reference.Named) (reference.Named, error) {
 	ref := originalRef
b7a2d853
 	// If the reference includes a digest and a tag, we must store only the
 	// digest.
0fbfeb17
 	canonical, isCanonical := originalRef.(reference.Canonical)
 	_, isNamedTagged := originalRef.(reference.NamedTagged)
b7a2d853
 
 	if isCanonical && isNamedTagged {
 		trimmed, err := reference.WithDigest(reference.TrimNamed(canonical), canonical.Digest())
 		if err != nil {
 			// should never happen
0fbfeb17
 			return originalRef, err
b7a2d853
 		}
 		ref = trimmed
 	}
0fbfeb17
 	return ref, nil
 }
 
 func (store *store) addReference(ref reference.Named, id digest.Digest, force bool) error {
 	ref, err := favorDigest(ref)
 	if err != nil {
 		return err
 	}
b7a2d853
 
3a127939
 	refName := reference.FamiliarName(ref)
 	refStr := reference.FamiliarString(ref)
 
 	if refName == string(digest.Canonical) {
20e759ab
 		return errors.New("refusing to create an ambiguous tag using digest algorithm as name")
 	}
7de380c5
 
 	store.mu.Lock()
 	defer store.mu.Unlock()
 
3a127939
 	repository, exists := store.Repositories[refName]
7de380c5
 	if !exists || repository == nil {
80522398
 		repository = make(map[string]digest.Digest)
3a127939
 		store.Repositories[refName] = repository
7de380c5
 	}
 
 	oldID, exists := repository[refStr]
 
 	if exists {
 		// force only works for tags
3a127939
 		if digested, isDigest := ref.(reference.Canonical); isDigest {
7de380c5
 			return fmt.Errorf("Cannot overwrite digest %s", digested.Digest().String())
 		}
 
 		if !force {
3a127939
 			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())
7de380c5
 		}
 
 		if store.referencesByIDCache[oldID] != nil {
 			delete(store.referencesByIDCache[oldID], refStr)
 			if len(store.referencesByIDCache[oldID]) == 0 {
 				delete(store.referencesByIDCache, oldID)
 			}
 		}
 	}
 
 	repository[refStr] = id
 	if store.referencesByIDCache[id] == nil {
3a127939
 		store.referencesByIDCache[id] = make(map[string]reference.Named)
7de380c5
 	}
 	store.referencesByIDCache[id][refStr] = ref
 
 	return store.save()
 }
 
 // Delete deletes a reference from the store. It returns true if a deletion
 // happened, or false otherwise.
3a127939
 func (store *store) Delete(ref reference.Named) (bool, error) {
0fbfeb17
 	ref, err := favorDigest(ref)
 	if err != nil {
 		return false, err
 	}
 
3a127939
 	ref = reference.TagNameOnly(ref)
 
 	refName := reference.FamiliarName(ref)
 	refStr := reference.FamiliarString(ref)
7de380c5
 
 	store.mu.Lock()
 	defer store.mu.Unlock()
 
3a127939
 	repository, exists := store.Repositories[refName]
7de380c5
 	if !exists {
 		return false, ErrDoesNotExist
 	}
 
 	if id, exists := repository[refStr]; exists {
 		delete(repository, refStr)
 		if len(repository) == 0 {
3a127939
 			delete(store.Repositories, refName)
7de380c5
 		}
 		if store.referencesByIDCache[id] != nil {
 			delete(store.referencesByIDCache[id], refStr)
 			if len(store.referencesByIDCache[id]) == 0 {
 				delete(store.referencesByIDCache, id)
 			}
 		}
 		return true, store.save()
 	}
 
 	return false, ErrDoesNotExist
 }
 
80522398
 // Get retrieves an item from the store by reference
3a127939
 func (store *store) Get(ref reference.Named) (digest.Digest, error) {
 	if canonical, ok := ref.(reference.Canonical); ok {
 		// If reference contains both tag and digest, only
39bcaee4
 		// lookup by digest as it takes precedence over
3a127939
 		// tag, until tag/digest combos are stored.
 		if _, ok := ref.(reference.Tagged); ok {
 			var err error
 			ref, err = reference.WithDigest(reference.TrimNamed(canonical), canonical.Digest())
 			if err != nil {
 				return "", err
 			}
 		}
 	} else {
 		ref = reference.TagNameOnly(ref)
 	}
 
 	refName := reference.FamiliarName(ref)
 	refStr := reference.FamiliarString(ref)
7de380c5
 
 	store.mu.RLock()
 	defer store.mu.RUnlock()
 
3a127939
 	repository, exists := store.Repositories[refName]
7de380c5
 	if !exists || repository == nil {
 		return "", ErrDoesNotExist
 	}
 
3a127939
 	id, exists := repository[refStr]
7de380c5
 	if !exists {
 		return "", ErrDoesNotExist
 	}
 
 	return id, nil
 }
 
80522398
 // References returns a slice of references to the given ID. The slice
 // will be nil if there are no references to this ID.
3a127939
 func (store *store) References(id digest.Digest) []reference.Named {
7de380c5
 	store.mu.RLock()
 	defer store.mu.RUnlock()
 
 	// Convert the internal map to an array for two reasons:
2655954c
 	// 1) We must not return a mutable
7de380c5
 	// 2) It would be ugly to expose the extraneous map keys to callers.
 
3a127939
 	var references []reference.Named
7de380c5
 	for _, ref := range store.referencesByIDCache[id] {
 		references = append(references, ref)
 	}
 
6e37b622
 	sort.Sort(lexicalRefs(references))
 
7de380c5
 	return references
 }
 
 // ReferencesByName returns the references for a given repository name.
 // If there are no references known for this repository name,
 // ReferencesByName returns nil.
3a127939
 func (store *store) ReferencesByName(ref reference.Named) []Association {
 	refName := reference.FamiliarName(ref)
 
7de380c5
 	store.mu.RLock()
 	defer store.mu.RUnlock()
 
3a127939
 	repository, exists := store.Repositories[refName]
7de380c5
 	if !exists {
 		return nil
 	}
 
 	var associations []Association
 	for refStr, refID := range repository {
3a127939
 		ref, err := reference.ParseNormalizedNamed(refStr)
7de380c5
 		if err != nil {
 			// Should never happen
 			return nil
 		}
 		associations = append(associations,
 			Association{
80522398
 				Ref: ref,
 				ID:  refID,
7de380c5
 			})
 	}
 
6e37b622
 	sort.Sort(lexicalAssociations(associations))
 
7de380c5
 	return associations
 }
 
 func (store *store) save() error {
 	// Store the json
 	jsonData, err := json.Marshal(store)
 	if err != nil {
 		return err
 	}
ea3cbd32
 	return ioutils.AtomicWriteFile(store.jsonPath, jsonData, 0600)
7de380c5
 }
 
 func (store *store) reload() error {
 	f, err := os.Open(store.jsonPath)
 	if err != nil {
 		return err
 	}
 	defer f.Close()
 	if err := json.NewDecoder(f).Decode(&store); err != nil {
 		return err
 	}
 
 	for _, repository := range store.Repositories {
 		for refStr, refID := range repository {
3a127939
 			ref, err := reference.ParseNormalizedNamed(refStr)
7de380c5
 			if err != nil {
 				// Should never happen
 				continue
 			}
 			if store.referencesByIDCache[refID] == nil {
3a127939
 				store.referencesByIDCache[refID] = make(map[string]reference.Named)
7de380c5
 			}
 			store.referencesByIDCache[refID][refStr] = ref
 		}
 	}
 
 	return nil
 }