github.com/dougm/docker@v1.5.0/graph/tags.go (about)

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