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