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