github.com/mheon/docker@v0.11.2-0.20150922122814-44f47903a831/graph/tags.go (about)

     1  package graph
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  	"sort"
    12  	"strings"
    13  	"sync"
    14  
    15  	"github.com/docker/distribution/digest"
    16  	"github.com/docker/docker/daemon/events"
    17  	"github.com/docker/docker/graph/tags"
    18  	"github.com/docker/docker/image"
    19  	"github.com/docker/docker/pkg/parsers"
    20  	"github.com/docker/docker/pkg/progressreader"
    21  	"github.com/docker/docker/pkg/stringid"
    22  	"github.com/docker/docker/registry"
    23  	"github.com/docker/docker/trust"
    24  	"github.com/docker/docker/utils"
    25  	"github.com/docker/libtrust"
    26  )
    27  
    28  // TagStore manages repositories. It encompasses the Graph used for versioned
    29  // storage, as well as various services involved in pushing and pulling
    30  // repositories.
    31  type TagStore struct {
    32  	path  string
    33  	graph *Graph
    34  	// Repositories is a map of repositories, indexed by name.
    35  	Repositories map[string]Repository
    36  	trustKey     libtrust.PrivateKey
    37  	sync.Mutex
    38  	// FIXME: move push/pull-related fields
    39  	// to a helper type
    40  	pullingPool     map[string]*progressreader.Broadcaster
    41  	pushingPool     map[string]*progressreader.Broadcaster
    42  	registryService *registry.Service
    43  	eventsService   *events.Events
    44  	trustService    *trust.Store
    45  }
    46  
    47  // Repository maps tags to image IDs.
    48  type Repository map[string]string
    49  
    50  // Update updates repository mapping with content of repository 'u'.
    51  func (r Repository) Update(u Repository) {
    52  	for k, v := range u {
    53  		r[k] = v
    54  	}
    55  }
    56  
    57  // Contains returns true if the contents of Repository u are wholly contained
    58  // in Repository r.
    59  func (r Repository) Contains(u Repository) bool {
    60  	for k, v := range u {
    61  		// if u's key is not present in r OR u's key is present, but not the same value
    62  		if rv, ok := r[k]; !ok || (ok && rv != v) {
    63  			return false
    64  		}
    65  	}
    66  	return true
    67  }
    68  
    69  // TagStoreConfig provides parameters for a new TagStore.
    70  type TagStoreConfig struct {
    71  	// Graph is the versioned image store
    72  	Graph *Graph
    73  	// Key is the private key to use for signing manifests.
    74  	Key libtrust.PrivateKey
    75  	// Registry is the registry service to use for TLS configuration and
    76  	// endpoint lookup.
    77  	Registry *registry.Service
    78  	// Events is the events service to use for logging.
    79  	Events *events.Events
    80  	// Trust is the trust service to use for push and pull operations.
    81  	Trust *trust.Store
    82  }
    83  
    84  // NewTagStore creates a new TagStore at specified path, using the parameters
    85  // and services provided in cfg.
    86  func NewTagStore(path string, cfg *TagStoreConfig) (*TagStore, error) {
    87  	abspath, err := filepath.Abs(path)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	store := &TagStore{
    93  		path:            abspath,
    94  		graph:           cfg.Graph,
    95  		trustKey:        cfg.Key,
    96  		Repositories:    make(map[string]Repository),
    97  		pullingPool:     make(map[string]*progressreader.Broadcaster),
    98  		pushingPool:     make(map[string]*progressreader.Broadcaster),
    99  		registryService: cfg.Registry,
   100  		eventsService:   cfg.Events,
   101  		trustService:    cfg.Trust,
   102  	}
   103  	// Load the json file if it exists, otherwise create it.
   104  	if err := store.reload(); os.IsNotExist(err) {
   105  		if err := store.save(); err != nil {
   106  			return nil, err
   107  		}
   108  	} else if err != nil {
   109  		return nil, err
   110  	}
   111  	return store, nil
   112  }
   113  
   114  func (store *TagStore) save() error {
   115  	// Store the json ball
   116  	jsonData, err := json.Marshal(store)
   117  	if err != nil {
   118  		return err
   119  	}
   120  	if err := ioutil.WriteFile(store.path, jsonData, 0600); err != nil {
   121  		return err
   122  	}
   123  	return nil
   124  }
   125  
   126  func (store *TagStore) reload() error {
   127  	f, err := os.Open(store.path)
   128  	if err != nil {
   129  		return err
   130  	}
   131  	defer f.Close()
   132  	if err := json.NewDecoder(f).Decode(&store); err != nil {
   133  		return err
   134  	}
   135  	return nil
   136  }
   137  
   138  // LookupImage returns pointer to an Image struct corresponding to the given
   139  // name. The name can include an optional tag; otherwise the default tag will
   140  // be used.
   141  func (store *TagStore) LookupImage(name string) (*image.Image, error) {
   142  	// FIXME: standardize on returning nil when the image doesn't exist, and err for everything else
   143  	// (so we can pass all errors here)
   144  	repoName, ref := parsers.ParseRepositoryTag(name)
   145  	if ref == "" {
   146  		ref = tags.DefaultTag
   147  	}
   148  	var (
   149  		err error
   150  		img *image.Image
   151  	)
   152  
   153  	img, err = store.GetImage(repoName, ref)
   154  	if err != nil {
   155  		return nil, err
   156  	}
   157  
   158  	if img != nil {
   159  		return img, nil
   160  	}
   161  
   162  	// name must be an image ID.
   163  	store.Lock()
   164  	defer store.Unlock()
   165  	if img, err = store.graph.Get(name); err != nil {
   166  		return nil, err
   167  	}
   168  
   169  	return img, nil
   170  }
   171  
   172  // ByID returns a reverse-lookup table of all the names which refer to each
   173  // image - e.g. {"43b5f19b10584": {"base:latest", "base:v1"}}
   174  func (store *TagStore) ByID() map[string][]string {
   175  	store.Lock()
   176  	defer store.Unlock()
   177  	byID := make(map[string][]string)
   178  	for repoName, repository := range store.Repositories {
   179  		for tag, id := range repository {
   180  			name := utils.ImageReference(repoName, tag)
   181  			if _, exists := byID[id]; !exists {
   182  				byID[id] = []string{name}
   183  			} else {
   184  				byID[id] = append(byID[id], name)
   185  				sort.Strings(byID[id])
   186  			}
   187  		}
   188  	}
   189  	return byID
   190  }
   191  
   192  // HasReferences returns whether or not the given image is referenced in one or
   193  // more repositories.
   194  func (store *TagStore) HasReferences(img *image.Image) bool {
   195  	return len(store.ByID()[img.ID]) > 0
   196  }
   197  
   198  // ImageName returns name of an image, given the image's ID.
   199  func (store *TagStore) ImageName(id string) string {
   200  	if names, exists := store.ByID()[id]; exists && len(names) > 0 {
   201  		return names[0]
   202  	}
   203  	return stringid.TruncateID(id)
   204  }
   205  
   206  // DeleteAll removes images identified by a specific ID from the store.
   207  func (store *TagStore) DeleteAll(id string) error {
   208  	names, exists := store.ByID()[id]
   209  	if !exists || len(names) == 0 {
   210  		return nil
   211  	}
   212  	for _, name := range names {
   213  		if strings.Contains(name, ":") {
   214  			nameParts := strings.Split(name, ":")
   215  			if _, err := store.Delete(nameParts[0], nameParts[1]); err != nil {
   216  				return err
   217  			}
   218  		} else {
   219  			if _, err := store.Delete(name, ""); err != nil {
   220  				return err
   221  			}
   222  		}
   223  	}
   224  	return nil
   225  }
   226  
   227  // Delete deletes a repository or a specific tag. If ref is empty, the entire
   228  // repository named repoName will be deleted; otherwise only the tag named by
   229  // ref will be deleted.
   230  func (store *TagStore) Delete(repoName, ref string) (bool, error) {
   231  	store.Lock()
   232  	defer store.Unlock()
   233  	deleted := false
   234  	if err := store.reload(); err != nil {
   235  		return false, err
   236  	}
   237  
   238  	repoName = registry.NormalizeLocalName(repoName)
   239  
   240  	if ref == "" {
   241  		// Delete the whole repository.
   242  		delete(store.Repositories, repoName)
   243  		return true, store.save()
   244  	}
   245  
   246  	repoRefs, exists := store.Repositories[repoName]
   247  	if !exists {
   248  		return false, fmt.Errorf("No such repository: %s", repoName)
   249  	}
   250  
   251  	if _, exists := repoRefs[ref]; exists {
   252  		delete(repoRefs, ref)
   253  		if len(repoRefs) == 0 {
   254  			delete(store.Repositories, repoName)
   255  		}
   256  		deleted = true
   257  	}
   258  
   259  	return deleted, store.save()
   260  }
   261  
   262  // Tag creates a tag in the repository reponame, pointing to the image named
   263  // imageName. If force is true, an existing tag with the same name may be
   264  // overwritten.
   265  func (store *TagStore) Tag(repoName, tag, imageName string, force bool) error {
   266  	return store.setLoad(repoName, tag, imageName, force, nil)
   267  }
   268  
   269  // setLoad stores the image to the store.
   270  // If the imageName is already in the repo then a '-f' flag should be used to replace existing image.
   271  func (store *TagStore) setLoad(repoName, tag, imageName string, force bool, out io.Writer) error {
   272  	img, err := store.LookupImage(imageName)
   273  	store.Lock()
   274  	defer store.Unlock()
   275  	if err != nil {
   276  		return err
   277  	}
   278  	if tag == "" {
   279  		tag = tags.DefaultTag
   280  	}
   281  	if err := validateRepoName(repoName); err != nil {
   282  		return err
   283  	}
   284  	if err := tags.ValidateTagName(tag); err != nil {
   285  		if _, formatError := err.(tags.ErrTagInvalidFormat); !formatError {
   286  			return err
   287  		}
   288  		if _, dErr := digest.ParseDigest(tag); dErr != nil {
   289  			// Still return the tag validation error.
   290  			// It's more likely to be a user generated issue.
   291  			return err
   292  		}
   293  	}
   294  	if err := store.reload(); err != nil {
   295  		return err
   296  	}
   297  	var repo Repository
   298  	repoName = registry.NormalizeLocalName(repoName)
   299  	if r, exists := store.Repositories[repoName]; exists {
   300  		repo = r
   301  		if old, exists := store.Repositories[repoName][tag]; exists {
   302  
   303  			if !force {
   304  				return fmt.Errorf("Conflict: Tag %s is already set to image %s, if you want to replace it, please use -f option", tag, old)
   305  			}
   306  
   307  			if old != img.ID && out != nil {
   308  
   309  				fmt.Fprintf(out, "The image %s:%s already exists, renaming the old one with ID %s to empty string\n", repoName, tag, old[:12])
   310  
   311  			}
   312  		}
   313  	} else {
   314  		repo = make(map[string]string)
   315  		store.Repositories[repoName] = repo
   316  	}
   317  	repo[tag] = img.ID
   318  	return store.save()
   319  }
   320  
   321  // SetDigest creates a digest reference to an image ID.
   322  func (store *TagStore) SetDigest(repoName, digest, imageName string) error {
   323  	img, err := store.LookupImage(imageName)
   324  	if err != nil {
   325  		return err
   326  	}
   327  
   328  	if err := validateRepoName(repoName); err != nil {
   329  		return err
   330  	}
   331  
   332  	if err := validateDigest(digest); err != nil {
   333  		return err
   334  	}
   335  
   336  	store.Lock()
   337  	defer store.Unlock()
   338  	if err := store.reload(); err != nil {
   339  		return err
   340  	}
   341  
   342  	repoName = registry.NormalizeLocalName(repoName)
   343  	repoRefs, exists := store.Repositories[repoName]
   344  	if !exists {
   345  		repoRefs = Repository{}
   346  		store.Repositories[repoName] = repoRefs
   347  	} else if oldID, exists := repoRefs[digest]; exists && oldID != img.ID {
   348  		return fmt.Errorf("Conflict: Digest %s is already set to image %s", digest, oldID)
   349  	}
   350  
   351  	repoRefs[digest] = img.ID
   352  	return store.save()
   353  }
   354  
   355  // Get returns the Repository tag/image map for a given repository.
   356  func (store *TagStore) Get(repoName string) (Repository, error) {
   357  	store.Lock()
   358  	defer store.Unlock()
   359  	if err := store.reload(); err != nil {
   360  		return nil, err
   361  	}
   362  	repoName = registry.NormalizeLocalName(repoName)
   363  	if r, exists := store.Repositories[repoName]; exists {
   364  		return r, nil
   365  	}
   366  	return nil, nil
   367  }
   368  
   369  // GetImage returns a pointer to an Image structure describing the image
   370  // referred to by refOrID inside repository repoName.
   371  func (store *TagStore) GetImage(repoName, refOrID string) (*image.Image, error) {
   372  	repo, err := store.Get(repoName)
   373  
   374  	if err != nil {
   375  		return nil, err
   376  	}
   377  	if repo == nil {
   378  		return nil, nil
   379  	}
   380  
   381  	store.Lock()
   382  	defer store.Unlock()
   383  	if imgID, exists := repo[refOrID]; exists {
   384  		return store.graph.Get(imgID)
   385  	}
   386  
   387  	// If no matching tag is found, search through images for a matching image id
   388  	// iff it looks like a short ID or would look like a short ID
   389  	if stringid.IsShortID(stringid.TruncateID(refOrID)) {
   390  		for _, revision := range repo {
   391  			if strings.HasPrefix(revision, refOrID) {
   392  				return store.graph.Get(revision)
   393  			}
   394  		}
   395  	}
   396  
   397  	return nil, nil
   398  }
   399  
   400  // GetRepoRefs returns a map with image IDs as keys, and slices listing
   401  // repo/tag references as the values. It covers all repositories.
   402  func (store *TagStore) GetRepoRefs() map[string][]string {
   403  	store.Lock()
   404  	reporefs := make(map[string][]string)
   405  
   406  	for name, repository := range store.Repositories {
   407  		for tag, id := range repository {
   408  			shortID := stringid.TruncateID(id)
   409  			reporefs[shortID] = append(reporefs[shortID], utils.ImageReference(name, tag))
   410  		}
   411  	}
   412  	store.Unlock()
   413  	return reporefs
   414  }
   415  
   416  // validateRepoName validates the name of a repository.
   417  func validateRepoName(name string) error {
   418  	if name == "" {
   419  		return fmt.Errorf("Repository name can't be empty")
   420  	}
   421  	if name == "scratch" {
   422  		return fmt.Errorf("'scratch' is a reserved name")
   423  	}
   424  	return nil
   425  }
   426  
   427  func validateDigest(dgst string) error {
   428  	if dgst == "" {
   429  		return errors.New("digest can't be empty")
   430  	}
   431  	if _, err := digest.ParseDigest(dgst); err != nil {
   432  		return err
   433  	}
   434  	return nil
   435  }
   436  
   437  // poolAdd checks if a push or pull is already running, and returns
   438  // (broadcaster, true) if a running operation is found. Otherwise, it creates a
   439  // new one and returns (broadcaster, false).
   440  func (store *TagStore) poolAdd(kind, key string) (*progressreader.Broadcaster, bool) {
   441  	store.Lock()
   442  	defer store.Unlock()
   443  
   444  	if p, exists := store.pullingPool[key]; exists {
   445  		return p, true
   446  	}
   447  	if p, exists := store.pushingPool[key]; exists {
   448  		return p, true
   449  	}
   450  
   451  	broadcaster := progressreader.NewBroadcaster()
   452  
   453  	switch kind {
   454  	case "pull":
   455  		store.pullingPool[key] = broadcaster
   456  	case "push":
   457  		store.pushingPool[key] = broadcaster
   458  	default:
   459  		panic("Unknown pool type")
   460  	}
   461  
   462  	return broadcaster, false
   463  }
   464  
   465  func (store *TagStore) poolRemoveWithError(kind, key string, broadcasterResult error) error {
   466  	store.Lock()
   467  	defer store.Unlock()
   468  	switch kind {
   469  	case "pull":
   470  		if broadcaster, exists := store.pullingPool[key]; exists {
   471  			broadcaster.CloseWithError(broadcasterResult)
   472  			delete(store.pullingPool, key)
   473  		}
   474  	case "push":
   475  		if broadcaster, exists := store.pushingPool[key]; exists {
   476  			broadcaster.CloseWithError(broadcasterResult)
   477  			delete(store.pushingPool, key)
   478  		}
   479  	default:
   480  		return fmt.Errorf("Unknown pool type")
   481  	}
   482  	return nil
   483  }
   484  
   485  func (store *TagStore) poolRemove(kind, key string) error {
   486  	return store.poolRemoveWithError(kind, key, nil)
   487  }