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