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