graph/tags.go
01b6b2be
 package graph
680f40c3
 
 import (
 	"encoding/json"
49a78929
 	"fmt"
680f40c3
 	"io/ioutil"
d0c77652
 	"os"
680f40c3
 	"path/filepath"
46eb4140
 	"regexp"
846a9e89
 	"sort"
49a78929
 	"strings"
c4990ab9
 	"sync"
 
b3ee9ac7
 	"github.com/docker/docker/image"
43981084
 	"github.com/docker/docker/pkg/parsers"
b3ee9ac7
 	"github.com/docker/docker/utils"
680f40c3
 )
 
fd224ee5
 const DEFAULTTAG = "latest"
640026ec
 
46eb4140
 var (
c95b0e02
 	validTagName = regexp.MustCompile(`^[\w][\w.-]{0,127}$`)
46eb4140
 )
 
44faa07b
 type TagStore struct {
680f40c3
 	path         string
 	graph        *Graph
69a75c67
 	mirrors      []string
44faa07b
 	Repositories map[string]Repository
c4990ab9
 	sync.Mutex
6856a6b1
 	// FIXME: move push/pull-related fields
 	// to a helper type
 	pullingPool map[string]chan struct{}
 	pushingPool map[string]chan struct{}
680f40c3
 }
 
44faa07b
 type Repository map[string]string
680f40c3
 
e64131d1
 // update Repository mapping with content of u
 func (r Repository) Update(u Repository) {
 	for k, v := range u {
 		r[k] = v
 	}
 }
 
 // return true if the contents of u Repository, are wholly contained in r Repository
 func (r Repository) Contains(u Repository) bool {
 	for k, v := range u {
 		// if u's key is not present in r OR u's key is present, but not the same value
 		if rv, ok := r[k]; !ok || (ok && rv != v) {
 			return false
 		}
 	}
 	return true
 }
 
69a75c67
 func NewTagStore(path string, graph *Graph, mirrors []string) (*TagStore, error) {
680f40c3
 	abspath, err := filepath.Abs(path)
 	if err != nil {
 		return nil, err
 	}
44faa07b
 	store := &TagStore{
680f40c3
 		path:         abspath,
 		graph:        graph,
69a75c67
 		mirrors:      mirrors,
44faa07b
 		Repositories: make(map[string]Repository),
6856a6b1
 		pullingPool:  make(map[string]chan struct{}),
 		pushingPool:  make(map[string]chan struct{}),
680f40c3
 	}
d0c77652
 	// Load the json file if it exists, otherwise create it.
c4990ab9
 	if err := store.reload(); os.IsNotExist(err) {
 		if err := store.save(); err != nil {
d0c77652
 			return nil, err
 		}
 	} else if err != nil {
680f40c3
 		return nil, err
 	}
 	return store, nil
 }
 
c4990ab9
 func (store *TagStore) save() error {
680f40c3
 	// Store the json ball
 	jsonData, err := json.Marshal(store)
 	if err != nil {
 		return err
 	}
 	if err := ioutil.WriteFile(store.path, jsonData, 0600); err != nil {
 		return err
 	}
 	return nil
 }
 
c4990ab9
 func (store *TagStore) reload() error {
680f40c3
 	jsonData, err := ioutil.ReadFile(store.path)
 	if err != nil {
 		return err
 	}
 	if err := json.Unmarshal(jsonData, store); err != nil {
 		return err
 	}
 	return nil
 }
 
82a54398
 func (store *TagStore) LookupImage(name string) (*image.Image, error) {
ca98434a
 	// FIXME: standardize on returning nil when the image doesn't exist, and err for everything else
 	// (so we can pass all errors here)
43981084
 	repos, tag := parsers.ParseRepositoryTag(name)
ca98434a
 	if tag == "" {
 		tag = DEFAULTTAG
 	}
 	img, err := store.GetImage(repos, tag)
c4990ab9
 	store.Lock()
 	defer store.Unlock()
640026ec
 	if err != nil {
ca98434a
 		return nil, err
 	} else if img == nil {
 		if img, err = store.graph.Get(name); err != nil {
640026ec
 			return nil, err
 		}
 	}
 	return img, nil
 }
 
12049f95
 // Return a reverse-lookup table of all the names which refer to each image
 // Eg. {"43b5f19b10584": {"base:latest", "base:v1"}}
fd224ee5
 func (store *TagStore) ByID() map[string][]string {
c4990ab9
 	store.Lock()
 	defer store.Unlock()
fd224ee5
 	byID := make(map[string][]string)
12049f95
 	for repoName, repository := range store.Repositories {
 		for tag, id := range repository {
 			name := repoName + ":" + tag
fd224ee5
 			if _, exists := byID[id]; !exists {
 				byID[id] = []string{name}
12049f95
 			} else {
fd224ee5
 				byID[id] = append(byID[id], name)
 				sort.Strings(byID[id])
12049f95
 			}
 		}
 	}
fd224ee5
 	return byID
12049f95
 }
 
 func (store *TagStore) ImageName(id string) string {
fd224ee5
 	if names, exists := store.ByID()[id]; exists && len(names) > 0 {
12049f95
 		return names[0]
 	}
fd224ee5
 	return utils.TruncateID(id)
12049f95
 }
 
5aa95b66
 func (store *TagStore) DeleteAll(id string) error {
66d9a733
 	names, exists := store.ByID()[id]
5aa95b66
 	if !exists || len(names) == 0 {
 		return nil
 	}
 	for _, name := range names {
 		if strings.Contains(name, ":") {
 			nameParts := strings.Split(name, ":")
9060b5c2
 			if _, err := store.Delete(nameParts[0], nameParts[1]); err != nil {
5aa95b66
 				return err
 			}
 		} else {
9060b5c2
 			if _, err := store.Delete(name, ""); err != nil {
5aa95b66
 				return err
 			}
 		}
 	}
 	return nil
 }
 
9060b5c2
 func (store *TagStore) Delete(repoName, tag string) (bool, error) {
c4990ab9
 	store.Lock()
 	defer store.Unlock()
9060b5c2
 	deleted := false
c4990ab9
 	if err := store.reload(); err != nil {
9060b5c2
 		return false, err
c80448c4
 	}
 	if r, exists := store.Repositories[repoName]; exists {
 		if tag != "" {
 			if _, exists2 := r[tag]; exists2 {
 				delete(r, tag)
 				if len(r) == 0 {
 					delete(store.Repositories, repoName)
 				}
9060b5c2
 				deleted = true
c80448c4
 			} else {
9060b5c2
 				return false, fmt.Errorf("No such tag: %s:%s", repoName, tag)
c80448c4
 			}
 		} else {
 			delete(store.Repositories, repoName)
9060b5c2
 			deleted = true
c80448c4
 		}
 	} else {
c4990ab9
 		return false, fmt.Errorf("No such repository: %s", repoName)
c80448c4
 	}
c4990ab9
 	return deleted, store.save()
12049f95
 }
 
bf7602bc
 func (store *TagStore) Set(repoName, tag, imageName string, force bool) error {
 	img, err := store.LookupImage(imageName)
c4990ab9
 	store.Lock()
 	defer store.Unlock()
bf7602bc
 	if err != nil {
 		return err
 	}
640026ec
 	if tag == "" {
fd224ee5
 		tag = DEFAULTTAG
49a78929
 	}
640026ec
 	if err := validateRepoName(repoName); err != nil {
 		return err
 	}
8833d800
 	if err := ValidateTagName(tag); err != nil {
640026ec
 		return err
49a78929
 	}
c4990ab9
 	if err := store.reload(); err != nil {
680f40c3
 		return err
 	}
44faa07b
 	var repo Repository
680f40c3
 	if r, exists := store.Repositories[repoName]; exists {
 		repo = r
 	} else {
44faa07b
 		repo = make(map[string]string)
bf7602bc
 		if old, exists := store.Repositories[repoName]; exists && !force {
67b20f2c
 			return fmt.Errorf("Conflict: Tag %s:%s is already set to %s", repoName, tag, old)
bf7602bc
 		}
680f40c3
 		store.Repositories[repoName] = repo
 	}
fd224ee5
 	repo[tag] = img.ID
c4990ab9
 	return store.save()
680f40c3
 }
 
44faa07b
 func (store *TagStore) Get(repoName string) (Repository, error) {
c4990ab9
 	store.Lock()
 	defer store.Unlock()
 	if err := store.reload(); err != nil {
680f40c3
 		return nil, err
 	}
 	if r, exists := store.Repositories[repoName]; exists {
 		return r, nil
 	}
 	return nil, nil
 }
 
82a54398
 func (store *TagStore) GetImage(repoName, tagOrID string) (*image.Image, error) {
680f40c3
 	repo, err := store.Get(repoName)
c4990ab9
 	store.Lock()
 	defer store.Unlock()
680f40c3
 	if err != nil {
 		return nil, err
 	} else if repo == nil {
 		return nil, nil
 	}
44b3e8d5
 	if revision, exists := repo[tagOrID]; exists {
 		return store.graph.Get(revision)
 	}
 	// If no matching tag is found, search through images for a matching image id
d26a3b37
 	for _, revision := range repo {
19121c16
 		if strings.HasPrefix(revision, tagOrID) {
d26a3b37
 			return store.graph.Get(revision)
 		}
 	}
680f40c3
 	return nil, nil
 }
640026ec
 
c4990ab9
 func (store *TagStore) GetRepoRefs() map[string][]string {
 	store.Lock()
 	reporefs := make(map[string][]string)
 
 	for name, repository := range store.Repositories {
 		for tag, id := range repository {
 			shortID := utils.TruncateID(id)
 			reporefs[shortID] = append(reporefs[shortID], fmt.Sprintf("%s:%s", name, tag))
 		}
 	}
 	store.Unlock()
 	return reporefs
 }
 
7c88e8f1
 // isOfficialName returns whether a repo name is considered an official
 // repository.  Official repositories are repos with names within
 // the library namespace or which default to the library namespace
 // by not providing one.
 func isOfficialName(name string) bool {
 	if strings.HasPrefix(name, "library/") {
 		return true
 	}
 	if strings.IndexRune(name, '/') == -1 {
 		return true
 	}
 	return false
 }
 
640026ec
 // Validate the name of a repository
 func validateRepoName(name string) error {
 	if name == "" {
 		return fmt.Errorf("Repository name can't be empty")
 	}
 	return nil
 }
 
 // Validate the name of a tag
8833d800
 func ValidateTagName(name string) error {
640026ec
 	if name == "" {
 		return fmt.Errorf("Tag name can't be empty")
 	}
46eb4140
 	if !validTagName.MatchString(name) {
 		return fmt.Errorf("Illegal tag name (%s): only [A-Za-z0-9_.-] are allowed, minimum 2, maximum 30 in length", name)
640026ec
 	}
 	return nil
 }
6856a6b1
 
114838cb
 func (store *TagStore) poolAdd(kind, key string) (chan struct{}, error) {
 	store.Lock()
 	defer store.Unlock()
6856a6b1
 
114838cb
 	if c, exists := store.pullingPool[key]; exists {
6856a6b1
 		return c, fmt.Errorf("pull %s is already in progress", key)
 	}
114838cb
 	if c, exists := store.pushingPool[key]; exists {
6856a6b1
 		return c, fmt.Errorf("push %s is already in progress", key)
 	}
 
 	c := make(chan struct{})
 	switch kind {
 	case "pull":
114838cb
 		store.pullingPool[key] = c
6856a6b1
 	case "push":
114838cb
 		store.pushingPool[key] = c
6856a6b1
 	default:
 		return nil, fmt.Errorf("Unknown pool type")
 	}
 	return c, nil
 }
 
114838cb
 func (store *TagStore) poolRemove(kind, key string) error {
 	store.Lock()
 	defer store.Unlock()
6856a6b1
 	switch kind {
 	case "pull":
114838cb
 		if c, exists := store.pullingPool[key]; exists {
6856a6b1
 			close(c)
114838cb
 			delete(store.pullingPool, key)
6856a6b1
 		}
 	case "push":
114838cb
 		if c, exists := store.pushingPool[key]; exists {
6856a6b1
 			close(c)
114838cb
 			delete(store.pushingPool, key)
6856a6b1
 		}
 	default:
 		return fmt.Errorf("Unknown pool type")
 	}
 	return nil
 }