graph/tags.go
01b6b2be
 package graph
680f40c3
 
 import (
 	"encoding/json"
a2b0c977
 	"errors"
49a78929
 	"fmt"
7d0053d5
 	"io"
680f40c3
 	"io/ioutil"
d0c77652
 	"os"
680f40c3
 	"path/filepath"
846a9e89
 	"sort"
49a78929
 	"strings"
c4990ab9
 	"sync"
 
1ec25653
 	"github.com/docker/distribution/digest"
c9eb37f9
 	"github.com/docker/docker/daemon/events"
aed8f9f7
 	"github.com/docker/docker/graph/tags"
9001ea26
 	"github.com/docker/docker/image"
23912334
 	"github.com/docker/docker/pkg/broadcaster"
43981084
 	"github.com/docker/docker/pkg/parsers"
b80fae73
 	"github.com/docker/docker/pkg/stringid"
568f86eb
 	"github.com/docker/docker/registry"
a2b0c977
 	"github.com/docker/docker/utils"
8ceb9d20
 	"github.com/docker/libtrust"
680f40c3
 )
 
d4836cd7
 // TagStore manages repositories. It encompasses the Graph used for versioned
 // storage, as well as various services involved in pushing and pulling
 // repositories.
44faa07b
 type TagStore struct {
d4836cd7
 	path  string
 	graph *Graph
 	// Repositories is a map of repositories, indexed by name.
568f86eb
 	Repositories map[string]Repository
8ceb9d20
 	trustKey     libtrust.PrivateKey
c4990ab9
 	sync.Mutex
6856a6b1
 	// FIXME: move push/pull-related fields
 	// to a helper type
23912334
 	pullingPool     map[string]*broadcaster.Buffered
 	pushingPool     map[string]*broadcaster.Buffered
03d3d79b
 	registryService *registry.Service
c9eb37f9
 	eventsService   *events.Events
680f40c3
 }
 
d4836cd7
 // Repository maps tags to image IDs.
44faa07b
 type Repository map[string]string
680f40c3
 
1d6e4431
 // Update updates repository mapping with content of repository 'u'.
e64131d1
 func (r Repository) Update(u Repository) {
 	for k, v := range u {
 		r[k] = v
 	}
 }
 
d4836cd7
 // Contains returns true if the contents of Repository u are wholly contained
 // in Repository r.
e64131d1
 func (r Repository) Contains(u Repository) bool {
 	for k, v := range u {
 		// if u's key is not present in r OR u's key is present, but not the same value
 		if rv, ok := r[k]; !ok || (ok && rv != v) {
 			return false
 		}
 	}
 	return true
 }
 
d4836cd7
 // TagStoreConfig provides parameters for a new TagStore.
9e50bf62
 type TagStoreConfig struct {
d4836cd7
 	// Graph is the versioned image store
 	Graph *Graph
 	// Key is the private key to use for signing manifests.
 	Key libtrust.PrivateKey
 	// Registry is the registry service to use for TLS configuration and
 	// endpoint lookup.
9e50bf62
 	Registry *registry.Service
d4836cd7
 	// Events is the events service to use for logging.
 	Events *events.Events
9e50bf62
 }
 
d4836cd7
 // NewTagStore creates a new TagStore at specified path, using the parameters
 // and services provided in cfg.
9e50bf62
 func NewTagStore(path string, cfg *TagStoreConfig) (*TagStore, error) {
680f40c3
 	abspath, err := filepath.Abs(path)
 	if err != nil {
 		return nil, err
 	}
f29b2e48
 
44faa07b
 	store := &TagStore{
03d3d79b
 		path:            abspath,
9e50bf62
 		graph:           cfg.Graph,
 		trustKey:        cfg.Key,
03d3d79b
 		Repositories:    make(map[string]Repository),
23912334
 		pullingPool:     make(map[string]*broadcaster.Buffered),
 		pushingPool:     make(map[string]*broadcaster.Buffered),
9e50bf62
 		registryService: cfg.Registry,
 		eventsService:   cfg.Events,
680f40c3
 	}
d0c77652
 	// Load the json file if it exists, otherwise create it.
c4990ab9
 	if err := store.reload(); os.IsNotExist(err) {
 		if err := store.save(); err != nil {
d0c77652
 			return nil, err
 		}
 	} else if err != nil {
680f40c3
 		return nil, err
 	}
 	return store, nil
 }
 
c4990ab9
 func (store *TagStore) save() error {
680f40c3
 	// Store the json ball
 	jsonData, err := json.Marshal(store)
 	if err != nil {
 		return err
 	}
 	if err := ioutil.WriteFile(store.path, jsonData, 0600); err != nil {
 		return err
 	}
 	return nil
 }
 
c4990ab9
 func (store *TagStore) reload() error {
26543e03
 	f, err := os.Open(store.path)
680f40c3
 	if err != nil {
 		return err
 	}
26543e03
 	defer f.Close()
 	if err := json.NewDecoder(f).Decode(&store); err != nil {
680f40c3
 		return err
 	}
 	return nil
 }
 
d4836cd7
 // LookupImage returns pointer to an Image struct corresponding to the given
 // name. The name can include an optional tag; otherwise the default tag will
 // be used.
9001ea26
 func (store *TagStore) LookupImage(name string) (*image.Image, error) {
ca98434a
 	// FIXME: standardize on returning nil when the image doesn't exist, and err for everything else
 	// (so we can pass all errors here)
a2b0c977
 	repoName, ref := parsers.ParseRepositoryTag(name)
 	if ref == "" {
d4836cd7
 		ref = tags.DefaultTag
a2b0c977
 	}
 	var (
 		err error
9001ea26
 		img *image.Image
a2b0c977
 	)
 
 	img, err = store.GetImage(repoName, ref)
 	if err != nil {
 		return nil, err
 	}
 
 	if img != nil {
f11b7acc
 		return img, nil
ca98434a
 	}
a2b0c977
 
 	// name must be an image ID.
c4990ab9
 	store.Lock()
 	defer store.Unlock()
a2b0c977
 	if img, err = store.graph.Get(name); err != nil {
ca98434a
 		return nil, err
640026ec
 	}
a2b0c977
 
640026ec
 	return img, nil
 }
 
d4836cd7
 // ByID returns a reverse-lookup table of all the names which refer to each
 // image - e.g. {"43b5f19b10584": {"base:latest", "base:v1"}}
fd224ee5
 func (store *TagStore) ByID() map[string][]string {
c4990ab9
 	store.Lock()
 	defer store.Unlock()
fd224ee5
 	byID := make(map[string][]string)
12049f95
 	for repoName, repository := range store.Repositories {
 		for tag, id := range repository {
a2b0c977
 			name := utils.ImageReference(repoName, tag)
fd224ee5
 			if _, exists := byID[id]; !exists {
 				byID[id] = []string{name}
12049f95
 			} else {
fd224ee5
 				byID[id] = append(byID[id], name)
 				sort.Strings(byID[id])
12049f95
 			}
 		}
 	}
fd224ee5
 	return byID
12049f95
 }
 
111d2f34
 // HasReferences returns whether or not the given image is referenced in one or
 // more repositories.
 func (store *TagStore) HasReferences(img *image.Image) bool {
 	return len(store.ByID()[img.ID]) > 0
 }
 
d4836cd7
 // ImageName returns name of an image, given the image's ID.
12049f95
 func (store *TagStore) ImageName(id string) string {
fd224ee5
 	if names, exists := store.ByID()[id]; exists && len(names) > 0 {
12049f95
 		return names[0]
 	}
b80fae73
 	return stringid.TruncateID(id)
12049f95
 }
 
d4836cd7
 // DeleteAll removes images identified by a specific ID from the store.
5aa95b66
 func (store *TagStore) DeleteAll(id string) error {
66d9a733
 	names, exists := store.ByID()[id]
5aa95b66
 	if !exists || len(names) == 0 {
 		return nil
 	}
 	for _, name := range names {
 		if strings.Contains(name, ":") {
 			nameParts := strings.Split(name, ":")
9060b5c2
 			if _, err := store.Delete(nameParts[0], nameParts[1]); err != nil {
5aa95b66
 				return err
 			}
 		} else {
9060b5c2
 			if _, err := store.Delete(name, ""); err != nil {
5aa95b66
 				return err
 			}
 		}
 	}
 	return nil
 }
 
d4836cd7
 // Delete deletes a repository or a specific tag. If ref is empty, the entire
 // repository named repoName will be deleted; otherwise only the tag named by
 // ref will be deleted.
a2b0c977
 func (store *TagStore) Delete(repoName, ref string) (bool, error) {
c4990ab9
 	store.Lock()
 	defer store.Unlock()
9060b5c2
 	deleted := false
c4990ab9
 	if err := store.reload(); err != nil {
9060b5c2
 		return false, err
c80448c4
 	}
a2b0c977
 
568f86eb
 	repoName = registry.NormalizeLocalName(repoName)
a2b0c977
 
 	if ref == "" {
 		// Delete the whole repository.
 		delete(store.Repositories, repoName)
 		return true, store.save()
 	}
 
 	repoRefs, exists := store.Repositories[repoName]
 	if !exists {
 		return false, fmt.Errorf("No such repository: %s", repoName)
 	}
 
 	if _, exists := repoRefs[ref]; exists {
 		delete(repoRefs, ref)
 		if len(repoRefs) == 0 {
c80448c4
 			delete(store.Repositories, repoName)
 		}
a2b0c977
 		deleted = true
c80448c4
 	}
a2b0c977
 
c4990ab9
 	return deleted, store.save()
12049f95
 }
 
d4836cd7
 // Tag creates a tag in the repository reponame, pointing to the image named
 // imageName. If force is true, an existing tag with the same name may be
 // overwritten.
99f6309b
 func (store *TagStore) Tag(repoName, tag, imageName string, force bool) error {
d4836cd7
 	return store.setLoad(repoName, tag, imageName, force, nil)
7d0053d5
 }
 
d4836cd7
 // setLoad stores the image to the store.
1d6e4431
 // If the imageName is already in the repo then a '-f' flag should be used to replace existing image.
d4836cd7
 func (store *TagStore) setLoad(repoName, tag, imageName string, force bool, out io.Writer) error {
bf7602bc
 	img, err := store.LookupImage(imageName)
c4990ab9
 	store.Lock()
 	defer store.Unlock()
bf7602bc
 	if err != nil {
 		return err
 	}
640026ec
 	if tag == "" {
d4836cd7
 		tag = tags.DefaultTag
49a78929
 	}
640026ec
 	if err := validateRepoName(repoName); err != nil {
 		return err
 	}
aed8f9f7
 	if err := tags.ValidateTagName(tag); err != nil {
d08ca5c2
 		return err
49a78929
 	}
c4990ab9
 	if err := store.reload(); err != nil {
680f40c3
 		return err
 	}
44faa07b
 	var repo Repository
568f86eb
 	repoName = registry.NormalizeLocalName(repoName)
680f40c3
 	if r, exists := store.Repositories[repoName]; exists {
 		repo = r
7d0053d5
 		if old, exists := store.Repositories[repoName][tag]; exists {
 
 			if !force {
582a24e9
 				return fmt.Errorf("Conflict: Tag %s:%s is already set to image %s, if you want to replace it, please use -f option", repoName, tag, old[:12])
7d0053d5
 			}
 
 			if old != img.ID && out != nil {
 
 				fmt.Fprintf(out, "The image %s:%s already exists, renaming the old one with ID %s to empty string\n", repoName, tag, old[:12])
 
 			}
5e6f16e3
 		}
680f40c3
 	} else {
44faa07b
 		repo = make(map[string]string)
680f40c3
 		store.Repositories[repoName] = repo
 	}
fd224ee5
 	repo[tag] = img.ID
c4990ab9
 	return store.save()
680f40c3
 }
 
a2b0c977
 // SetDigest creates a digest reference to an image ID.
 func (store *TagStore) SetDigest(repoName, digest, imageName string) error {
 	img, err := store.LookupImage(imageName)
 	if err != nil {
 		return err
 	}
 
 	if err := validateRepoName(repoName); err != nil {
 		return err
 	}
 
 	if err := validateDigest(digest); err != nil {
 		return err
 	}
 
 	store.Lock()
 	defer store.Unlock()
 	if err := store.reload(); err != nil {
 		return err
 	}
 
 	repoName = registry.NormalizeLocalName(repoName)
 	repoRefs, exists := store.Repositories[repoName]
 	if !exists {
 		repoRefs = Repository{}
 		store.Repositories[repoName] = repoRefs
 	} else if oldID, exists := repoRefs[digest]; exists && oldID != img.ID {
 		return fmt.Errorf("Conflict: Digest %s is already set to image %s", digest, oldID)
 	}
 
 	repoRefs[digest] = img.ID
 	return store.save()
 }
 
d4836cd7
 // Get returns the Repository tag/image map for a given repository.
44faa07b
 func (store *TagStore) Get(repoName string) (Repository, error) {
c4990ab9
 	store.Lock()
 	defer store.Unlock()
 	if err := store.reload(); err != nil {
680f40c3
 		return nil, err
 	}
568f86eb
 	repoName = registry.NormalizeLocalName(repoName)
680f40c3
 	if r, exists := store.Repositories[repoName]; exists {
 		return r, nil
 	}
 	return nil, nil
 }
 
d4836cd7
 // GetImage returns a pointer to an Image structure describing the image
 // referred to by refOrID inside repository repoName.
9001ea26
 func (store *TagStore) GetImage(repoName, refOrID string) (*image.Image, error) {
680f40c3
 	repo, err := store.Get(repoName)
a2b0c977
 
680f40c3
 	if err != nil {
 		return nil, err
a2b0c977
 	}
 	if repo == nil {
680f40c3
 		return nil, nil
 	}
a2b0c977
 
 	store.Lock()
 	defer store.Unlock()
 	if imgID, exists := repo[refOrID]; exists {
 		return store.graph.Get(imgID)
44b3e8d5
 	}
a2b0c977
 
44b3e8d5
 	// If no matching tag is found, search through images for a matching image id
8d1a5003
 	// iff it looks like a short ID or would look like a short ID
 	if stringid.IsShortID(stringid.TruncateID(refOrID)) {
 		for _, revision := range repo {
 			if strings.HasPrefix(revision, refOrID) {
 				return store.graph.Get(revision)
 			}
d26a3b37
 		}
 	}
a2b0c977
 
680f40c3
 	return nil, nil
 }
640026ec
 
d4836cd7
 // GetRepoRefs returns a map with image IDs as keys, and slices listing
 // repo/tag references as the values. It covers all repositories.
c4990ab9
 func (store *TagStore) GetRepoRefs() map[string][]string {
 	store.Lock()
 	reporefs := make(map[string][]string)
 
 	for name, repository := range store.Repositories {
 		for tag, id := range repository {
b80fae73
 			shortID := stringid.TruncateID(id)
a2b0c977
 			reporefs[shortID] = append(reporefs[shortID], utils.ImageReference(name, tag))
c4990ab9
 		}
 	}
 	store.Unlock()
 	return reporefs
 }
 
1d6e4431
 // validateRepoName validates the name of a repository.
640026ec
 func validateRepoName(name string) error {
 	if name == "" {
 		return fmt.Errorf("Repository name can't be empty")
 	}
89367899
 	if name == "scratch" {
 		return fmt.Errorf("'scratch' is a reserved name")
 	}
640026ec
 	return nil
 }
 
a2b0c977
 func validateDigest(dgst string) error {
 	if dgst == "" {
 		return errors.New("digest can't be empty")
 	}
1ec25653
 	if _, err := digest.ParseDigest(dgst); err != nil {
 		return err
a2b0c977
 	}
 	return nil
 }
 
26c9b585
 // poolAdd checks if a push or pull is already running, and returns
 // (broadcaster, true) if a running operation is found. Otherwise, it creates a
 // new one and returns (broadcaster, false).
23912334
 func (store *TagStore) poolAdd(kind, key string) (*broadcaster.Buffered, bool) {
114838cb
 	store.Lock()
 	defer store.Unlock()
6856a6b1
 
572f008e
 	if p, exists := store.pullingPool[key]; exists {
80513d85
 		return p, true
6856a6b1
 	}
572f008e
 	if p, exists := store.pushingPool[key]; exists {
80513d85
 		return p, true
6856a6b1
 	}
 
23912334
 	broadcaster := broadcaster.NewBuffered()
80513d85
 
6856a6b1
 	switch kind {
 	case "pull":
26c9b585
 		store.pullingPool[key] = broadcaster
6856a6b1
 	case "push":
26c9b585
 		store.pushingPool[key] = broadcaster
6856a6b1
 	default:
80513d85
 		panic("Unknown pool type")
6856a6b1
 	}
80513d85
 
26c9b585
 	return broadcaster, false
6856a6b1
 }
 
23e68679
 func (store *TagStore) poolRemoveWithError(kind, key string, broadcasterResult error) error {
114838cb
 	store.Lock()
 	defer store.Unlock()
6856a6b1
 	switch kind {
 	case "pull":
23e68679
 		if broadcaster, exists := store.pullingPool[key]; exists {
 			broadcaster.CloseWithError(broadcasterResult)
114838cb
 			delete(store.pullingPool, key)
6856a6b1
 		}
 	case "push":
23e68679
 		if broadcaster, exists := store.pushingPool[key]; exists {
 			broadcaster.CloseWithError(broadcasterResult)
114838cb
 			delete(store.pushingPool, key)
6856a6b1
 		}
 	default:
 		return fmt.Errorf("Unknown pool type")
 	}
 	return nil
 }
23e68679
 
 func (store *TagStore) poolRemove(kind, key string) error {
 	return store.poolRemoveWithError(kind, key, nil)
 }