github.com/ralexstokes/docker@v1.6.2/graph/tags.go (about)

     1  package graph
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  	"regexp"
    11  	"sort"
    12  	"strings"
    13  	"sync"
    14  
    15  	"github.com/docker/docker/image"
    16  	"github.com/docker/docker/pkg/common"
    17  	"github.com/docker/docker/pkg/parsers"
    18  	"github.com/docker/docker/registry"
    19  	"github.com/docker/docker/utils"
    20  	"github.com/docker/libtrust"
    21  )
    22  
    23  const DEFAULTTAG = "latest"
    24  
    25  var (
    26  	//FIXME these 2 regexes also exist in registry/v2/regexp.go
    27  	validTagName = regexp.MustCompile(`^[\w][\w.-]{0,127}$`)
    28  	validDigest  = regexp.MustCompile(`[a-zA-Z0-9-_+.]+:[a-fA-F0-9]+`)
    29  )
    30  
    31  type TagStore struct {
    32  	path         string
    33  	graph        *Graph
    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  }
    42  
    43  type Repository map[string]string
    44  
    45  // update Repository mapping with content of u
    46  func (r Repository) Update(u Repository) {
    47  	for k, v := range u {
    48  		r[k] = v
    49  	}
    50  }
    51  
    52  // return true if the contents of u Repository, are wholly contained in r Repository
    53  func (r Repository) Contains(u Repository) bool {
    54  	for k, v := range u {
    55  		// if u's key is not present in r OR u's key is present, but not the same value
    56  		if rv, ok := r[k]; !ok || (ok && rv != v) {
    57  			return false
    58  		}
    59  	}
    60  	return true
    61  }
    62  
    63  func NewTagStore(path string, graph *Graph, key libtrust.PrivateKey) (*TagStore, error) {
    64  	abspath, err := filepath.Abs(path)
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  
    69  	store := &TagStore{
    70  		path:         abspath,
    71  		graph:        graph,
    72  		trustKey:     key,
    73  		Repositories: make(map[string]Repository),
    74  		pullingPool:  make(map[string]chan struct{}),
    75  		pushingPool:  make(map[string]chan struct{}),
    76  	}
    77  	// Load the json file if it exists, otherwise create it.
    78  	if err := store.reload(); os.IsNotExist(err) {
    79  		if err := store.save(); err != nil {
    80  			return nil, err
    81  		}
    82  	} else if err != nil {
    83  		return nil, err
    84  	}
    85  	return store, nil
    86  }
    87  
    88  func (store *TagStore) save() error {
    89  	// Store the json ball
    90  	jsonData, err := json.Marshal(store)
    91  	if err != nil {
    92  		return err
    93  	}
    94  	if err := ioutil.WriteFile(store.path, jsonData, 0600); err != nil {
    95  		return err
    96  	}
    97  	return nil
    98  }
    99  
   100  func (store *TagStore) reload() error {
   101  	jsonData, err := ioutil.ReadFile(store.path)
   102  	if err != nil {
   103  		return err
   104  	}
   105  	if err := json.Unmarshal(jsonData, store); err != nil {
   106  		return err
   107  	}
   108  	return nil
   109  }
   110  
   111  func (store *TagStore) LookupImage(name string) (*image.Image, error) {
   112  	// FIXME: standardize on returning nil when the image doesn't exist, and err for everything else
   113  	// (so we can pass all errors here)
   114  	repoName, ref := parsers.ParseRepositoryTag(name)
   115  	if ref == "" {
   116  		ref = DEFAULTTAG
   117  	}
   118  	var (
   119  		err error
   120  		img *image.Image
   121  	)
   122  
   123  	img, err = store.GetImage(repoName, ref)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  
   128  	if img != nil {
   129  		return img, err
   130  	}
   131  
   132  	// name must be an image ID.
   133  	store.Lock()
   134  	defer store.Unlock()
   135  	if img, err = store.graph.Get(name); err != nil {
   136  		return nil, err
   137  	}
   138  
   139  	return img, nil
   140  }
   141  
   142  // Return a reverse-lookup table of all the names which refer to each image
   143  // Eg. {"43b5f19b10584": {"base:latest", "base:v1"}}
   144  func (store *TagStore) ByID() map[string][]string {
   145  	store.Lock()
   146  	defer store.Unlock()
   147  	byID := make(map[string][]string)
   148  	for repoName, repository := range store.Repositories {
   149  		for tag, id := range repository {
   150  			name := utils.ImageReference(repoName, tag)
   151  			if _, exists := byID[id]; !exists {
   152  				byID[id] = []string{name}
   153  			} else {
   154  				byID[id] = append(byID[id], name)
   155  				sort.Strings(byID[id])
   156  			}
   157  		}
   158  	}
   159  	return byID
   160  }
   161  
   162  func (store *TagStore) ImageName(id string) string {
   163  	if names, exists := store.ByID()[id]; exists && len(names) > 0 {
   164  		return names[0]
   165  	}
   166  	return common.TruncateID(id)
   167  }
   168  
   169  func (store *TagStore) DeleteAll(id string) error {
   170  	names, exists := store.ByID()[id]
   171  	if !exists || len(names) == 0 {
   172  		return nil
   173  	}
   174  	for _, name := range names {
   175  		if strings.Contains(name, ":") {
   176  			nameParts := strings.Split(name, ":")
   177  			if _, err := store.Delete(nameParts[0], nameParts[1]); err != nil {
   178  				return err
   179  			}
   180  		} else {
   181  			if _, err := store.Delete(name, ""); err != nil {
   182  				return err
   183  			}
   184  		}
   185  	}
   186  	return nil
   187  }
   188  
   189  func (store *TagStore) Delete(repoName, ref string) (bool, error) {
   190  	store.Lock()
   191  	defer store.Unlock()
   192  	deleted := false
   193  	if err := store.reload(); err != nil {
   194  		return false, err
   195  	}
   196  
   197  	repoName = registry.NormalizeLocalName(repoName)
   198  
   199  	if ref == "" {
   200  		// Delete the whole repository.
   201  		delete(store.Repositories, repoName)
   202  		return true, store.save()
   203  	}
   204  
   205  	repoRefs, exists := store.Repositories[repoName]
   206  	if !exists {
   207  		return false, fmt.Errorf("No such repository: %s", repoName)
   208  	}
   209  
   210  	if _, exists := repoRefs[ref]; exists {
   211  		delete(repoRefs, ref)
   212  		if len(repoRefs) == 0 {
   213  			delete(store.Repositories, repoName)
   214  		}
   215  		deleted = true
   216  	}
   217  
   218  	return deleted, store.save()
   219  }
   220  
   221  func (store *TagStore) Set(repoName, tag, imageName string, force bool) error {
   222  	img, err := store.LookupImage(imageName)
   223  	store.Lock()
   224  	defer store.Unlock()
   225  	if err != nil {
   226  		return err
   227  	}
   228  	if tag == "" {
   229  		tag = DEFAULTTAG
   230  	}
   231  	if err := validateRepoName(repoName); err != nil {
   232  		return err
   233  	}
   234  	if err := ValidateTagName(tag); err != nil {
   235  		return err
   236  	}
   237  	if err := store.reload(); err != nil {
   238  		return err
   239  	}
   240  	var repo Repository
   241  	repoName = registry.NormalizeLocalName(repoName)
   242  	if r, exists := store.Repositories[repoName]; exists {
   243  		repo = r
   244  		if old, exists := store.Repositories[repoName][tag]; exists && !force {
   245  			return fmt.Errorf("Conflict: Tag %s is already set to image %s, if you want to replace it, please use -f option", tag, old)
   246  		}
   247  	} else {
   248  		repo = make(map[string]string)
   249  		store.Repositories[repoName] = repo
   250  	}
   251  	repo[tag] = img.ID
   252  	return store.save()
   253  }
   254  
   255  // SetDigest creates a digest reference to an image ID.
   256  func (store *TagStore) SetDigest(repoName, digest, imageName string) error {
   257  	img, err := store.LookupImage(imageName)
   258  	if err != nil {
   259  		return err
   260  	}
   261  
   262  	if err := validateRepoName(repoName); err != nil {
   263  		return err
   264  	}
   265  
   266  	if err := validateDigest(digest); err != nil {
   267  		return err
   268  	}
   269  
   270  	store.Lock()
   271  	defer store.Unlock()
   272  	if err := store.reload(); err != nil {
   273  		return err
   274  	}
   275  
   276  	repoName = registry.NormalizeLocalName(repoName)
   277  	repoRefs, exists := store.Repositories[repoName]
   278  	if !exists {
   279  		repoRefs = Repository{}
   280  		store.Repositories[repoName] = repoRefs
   281  	} else if oldID, exists := repoRefs[digest]; exists && oldID != img.ID {
   282  		return fmt.Errorf("Conflict: Digest %s is already set to image %s", digest, oldID)
   283  	}
   284  
   285  	repoRefs[digest] = img.ID
   286  	return store.save()
   287  }
   288  
   289  func (store *TagStore) Get(repoName string) (Repository, error) {
   290  	store.Lock()
   291  	defer store.Unlock()
   292  	if err := store.reload(); err != nil {
   293  		return nil, err
   294  	}
   295  	repoName = registry.NormalizeLocalName(repoName)
   296  	if r, exists := store.Repositories[repoName]; exists {
   297  		return r, nil
   298  	}
   299  	return nil, nil
   300  }
   301  
   302  func (store *TagStore) GetImage(repoName, refOrID string) (*image.Image, error) {
   303  	repo, err := store.Get(repoName)
   304  
   305  	if err != nil {
   306  		return nil, err
   307  	}
   308  	if repo == nil {
   309  		return nil, nil
   310  	}
   311  
   312  	store.Lock()
   313  	defer store.Unlock()
   314  	if imgID, exists := repo[refOrID]; exists {
   315  		return store.graph.Get(imgID)
   316  	}
   317  
   318  	// If no matching tag is found, search through images for a matching image id
   319  	for _, revision := range repo {
   320  		if strings.HasPrefix(revision, refOrID) {
   321  			return store.graph.Get(revision)
   322  		}
   323  	}
   324  
   325  	return nil, nil
   326  }
   327  
   328  func (store *TagStore) GetRepoRefs() map[string][]string {
   329  	store.Lock()
   330  	reporefs := make(map[string][]string)
   331  
   332  	for name, repository := range store.Repositories {
   333  		for tag, id := range repository {
   334  			shortID := common.TruncateID(id)
   335  			reporefs[shortID] = append(reporefs[shortID], utils.ImageReference(name, tag))
   336  		}
   337  	}
   338  	store.Unlock()
   339  	return reporefs
   340  }
   341  
   342  // Validate the name of a repository
   343  func validateRepoName(name string) error {
   344  	if name == "" {
   345  		return fmt.Errorf("Repository name can't be empty")
   346  	}
   347  	if name == "scratch" {
   348  		return fmt.Errorf("'scratch' is a reserved name")
   349  	}
   350  	return nil
   351  }
   352  
   353  // ValidateTagName validates the name of a tag
   354  func ValidateTagName(name string) error {
   355  	if name == "" {
   356  		return fmt.Errorf("tag name can't be empty")
   357  	}
   358  	if !validTagName.MatchString(name) {
   359  		return fmt.Errorf("Illegal tag name (%s): only [A-Za-z0-9_.-] are allowed, minimum 1, maximum 128 in length", name)
   360  	}
   361  	return nil
   362  }
   363  
   364  func validateDigest(dgst string) error {
   365  	if dgst == "" {
   366  		return errors.New("digest can't be empty")
   367  	}
   368  	if !validDigest.MatchString(dgst) {
   369  		return fmt.Errorf("illegal digest (%s): must be of the form [a-zA-Z0-9-_+.]+:[a-fA-F0-9]+", dgst)
   370  	}
   371  	return nil
   372  }
   373  
   374  func (store *TagStore) poolAdd(kind, key string) (chan struct{}, error) {
   375  	store.Lock()
   376  	defer store.Unlock()
   377  
   378  	if c, exists := store.pullingPool[key]; exists {
   379  		return c, fmt.Errorf("pull %s is already in progress", key)
   380  	}
   381  	if c, exists := store.pushingPool[key]; exists {
   382  		return c, fmt.Errorf("push %s is already in progress", key)
   383  	}
   384  
   385  	c := make(chan struct{})
   386  	switch kind {
   387  	case "pull":
   388  		store.pullingPool[key] = c
   389  	case "push":
   390  		store.pushingPool[key] = c
   391  	default:
   392  		return nil, fmt.Errorf("Unknown pool type")
   393  	}
   394  	return c, nil
   395  }
   396  
   397  func (store *TagStore) poolRemove(kind, key string) error {
   398  	store.Lock()
   399  	defer store.Unlock()
   400  	switch kind {
   401  	case "pull":
   402  		if c, exists := store.pullingPool[key]; exists {
   403  			close(c)
   404  			delete(store.pullingPool, key)
   405  		}
   406  	case "push":
   407  		if c, exists := store.pushingPool[key]; exists {
   408  			close(c)
   409  			delete(store.pushingPool, key)
   410  		}
   411  	default:
   412  		return fmt.Errorf("Unknown pool type")
   413  	}
   414  	return nil
   415  }