| package graph |
| |
| import ( |
| "encoding/json" |
| "errors" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "os" |
| "path/filepath" |
| "sort" |
| "strings" |
| "sync" |
| |
| "github.com/docker/distribution/digest" |
| "github.com/docker/docker/daemon/events" |
| "github.com/docker/docker/graph/tags" |
| "github.com/docker/docker/image" |
| "github.com/docker/docker/pkg/broadcaster" |
| "github.com/docker/docker/pkg/parsers" |
| "github.com/docker/docker/pkg/stringid" |
| "github.com/docker/docker/registry" |
| "github.com/docker/docker/utils" |
| "github.com/docker/libtrust" |
| ) |
| |
| // TagStore manages repositories. It encompasses the Graph used for versioned |
| // storage, as well as various services involved in pushing and pulling |
| // repositories. |
| type TagStore struct { |
| path string |
| graph *Graph |
| // Repositories is a map of repositories, indexed by name. |
| Repositories map[string]Repository |
| trustKey libtrust.PrivateKey |
| sync.Mutex |
| // FIXME: move push/pull-related fields |
| // to a helper type |
| pullingPool map[string]*broadcaster.Buffered |
| pushingPool map[string]*broadcaster.Buffered |
| registryService *registry.Service |
| eventsService *events.Events |
| } |
| |
| // Repository maps tags to image IDs. |
| type Repository map[string]string |
| |
| // Update updates repository mapping with content of repository 'u'. |
| func (r Repository) Update(u Repository) { |
| for k, v := range u { |
| r[k] = v |
| } |
| } |
| |
| // Contains returns true if the contents of Repository u are wholly contained |
| // in Repository r. |
| 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 |
| } |
| |
| // TagStoreConfig provides parameters for a new TagStore. |
| type TagStoreConfig struct { |
| // Graph is the versioned image store |
| Graph *Graph |
| // Key is the private key to use for signing manifests. |
| Key libtrust.PrivateKey |
| // Registry is the registry service to use for TLS configuration and |
| // endpoint lookup. |
| Registry *registry.Service |
| // Events is the events service to use for logging. |
| Events *events.Events |
| } |
| |
| // NewTagStore creates a new TagStore at specified path, using the parameters |
| // and services provided in cfg. |
| func NewTagStore(path string, cfg *TagStoreConfig) (*TagStore, error) { |
| abspath, err := filepath.Abs(path) |
| if err != nil { |
| return nil, err |
| } |
| |
| store := &TagStore{ |
| path: abspath, |
| graph: cfg.Graph, |
| trustKey: cfg.Key, |
| Repositories: make(map[string]Repository), |
| pullingPool: make(map[string]*broadcaster.Buffered), |
| pushingPool: make(map[string]*broadcaster.Buffered), |
| registryService: cfg.Registry, |
| eventsService: cfg.Events, |
| } |
| // Load the json file if it exists, otherwise create it. |
| if err := store.reload(); os.IsNotExist(err) { |
| if err := store.save(); err != nil { |
| return nil, err |
| } |
| } else if err != nil { |
| return nil, err |
| } |
| return store, nil |
| } |
| |
| func (store *TagStore) save() error { |
| // 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 |
| } |
| |
| func (store *TagStore) reload() error { |
| f, err := os.Open(store.path) |
| if err != nil { |
| return err |
| } |
| defer f.Close() |
| if err := json.NewDecoder(f).Decode(&store); err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| // LookupImage returns pointer to an Image struct corresponding to the given |
| // name. The name can include an optional tag; otherwise the default tag will |
| // be used. |
| func (store *TagStore) LookupImage(name string) (*image.Image, error) { |
| // FIXME: standardize on returning nil when the image doesn't exist, and err for everything else |
| // (so we can pass all errors here) |
| repoName, ref := parsers.ParseRepositoryTag(name) |
| if ref == "" { |
| ref = tags.DefaultTag |
| } |
| var ( |
| err error |
| img *image.Image |
| ) |
| |
| img, err = store.GetImage(repoName, ref) |
| if err != nil { |
| return nil, err |
| } |
| |
| if img != nil { |
| return img, nil |
| } |
| |
| // name must be an image ID. |
| store.Lock() |
| defer store.Unlock() |
| if img, err = store.graph.Get(name); err != nil { |
| return nil, err |
| } |
| |
| return img, nil |
| } |
| |
| // ByID returns a reverse-lookup table of all the names which refer to each |
| // image - e.g. {"43b5f19b10584": {"base:latest", "base:v1"}} |
| func (store *TagStore) ByID() map[string][]string { |
| store.Lock() |
| defer store.Unlock() |
| byID := make(map[string][]string) |
| for repoName, repository := range store.Repositories { |
| for tag, id := range repository { |
| name := utils.ImageReference(repoName, tag) |
| if _, exists := byID[id]; !exists { |
| byID[id] = []string{name} |
| } else { |
| byID[id] = append(byID[id], name) |
| sort.Strings(byID[id]) |
| } |
| } |
| } |
| return byID |
| } |
| |
| // HasReferences returns whether or not the given image is referenced in one or |
| // more repositories. |
| func (store *TagStore) HasReferences(img *image.Image) bool { |
| return len(store.ByID()[img.ID]) > 0 |
| } |
| |
| // ImageName returns name of an image, given the image's ID. |
| func (store *TagStore) ImageName(id string) string { |
| if names, exists := store.ByID()[id]; exists && len(names) > 0 { |
| return names[0] |
| } |
| return stringid.TruncateID(id) |
| } |
| |
| // DeleteAll removes images identified by a specific ID from the store. |
| func (store *TagStore) DeleteAll(id string) error { |
| names, exists := store.ByID()[id] |
| if !exists || len(names) == 0 { |
| return nil |
| } |
| for _, name := range names { |
| if strings.Contains(name, ":") { |
| nameParts := strings.Split(name, ":") |
| if _, err := store.Delete(nameParts[0], nameParts[1]); err != nil { |
| return err |
| } |
| } else { |
| if _, err := store.Delete(name, ""); err != nil { |
| return err |
| } |
| } |
| } |
| return nil |
| } |
| |
| // Delete deletes a repository or a specific tag. If ref is empty, the entire |
| // repository named repoName will be deleted; otherwise only the tag named by |
| // ref will be deleted. |
| func (store *TagStore) Delete(repoName, ref string) (bool, error) { |
| store.Lock() |
| defer store.Unlock() |
| deleted := false |
| if err := store.reload(); err != nil { |
| return false, err |
| } |
| |
| repoName = registry.NormalizeLocalName(repoName) |
| |
| if ref == "" { |
| // Delete the whole repository. |
| delete(store.Repositories, repoName) |
| return true, store.save() |
| } |
| |
| repoRefs, exists := store.Repositories[repoName] |
| if !exists { |
| return false, fmt.Errorf("No such repository: %s", repoName) |
| } |
| |
| if _, exists := repoRefs[ref]; exists { |
| delete(repoRefs, ref) |
| if len(repoRefs) == 0 { |
| delete(store.Repositories, repoName) |
| } |
| deleted = true |
| } |
| |
| return deleted, store.save() |
| } |
| |
| // Tag creates a tag in the repository reponame, pointing to the image named |
| // imageName. If force is true, an existing tag with the same name may be |
| // overwritten. |
| func (store *TagStore) Tag(repoName, tag, imageName string, force bool) error { |
| return store.setLoad(repoName, tag, imageName, force, nil) |
| } |
| |
| // setLoad stores the image to the store. |
| // If the imageName is already in the repo then a '-f' flag should be used to replace existing image. |
| func (store *TagStore) setLoad(repoName, tag, imageName string, force bool, out io.Writer) error { |
| img, err := store.LookupImage(imageName) |
| store.Lock() |
| defer store.Unlock() |
| if err != nil { |
| return err |
| } |
| if tag == "" { |
| tag = tags.DefaultTag |
| } |
| if err := validateRepoName(repoName); err != nil { |
| return err |
| } |
| if err := tags.ValidateTagName(tag); err != nil { |
| return err |
| } |
| if err := store.reload(); err != nil { |
| return err |
| } |
| var repo Repository |
| repoName = registry.NormalizeLocalName(repoName) |
| if r, exists := store.Repositories[repoName]; exists { |
| repo = r |
| if old, exists := store.Repositories[repoName][tag]; exists { |
| |
| if !force { |
| return fmt.Errorf("Conflict: Tag %s:%s is already set to image %s, if you want to replace it, please use -f option", repoName, tag, old[:12]) |
| } |
| |
| if old != img.ID && out != nil { |
| |
| fmt.Fprintf(out, "The image %s:%s already exists, renaming the old one with ID %s to empty string\n", repoName, tag, old[:12]) |
| |
| } |
| } |
| } else { |
| repo = make(map[string]string) |
| store.Repositories[repoName] = repo |
| } |
| repo[tag] = img.ID |
| return store.save() |
| } |
| |
| // SetDigest creates a digest reference to an image ID. |
| func (store *TagStore) SetDigest(repoName, digest, imageName string) error { |
| img, err := store.LookupImage(imageName) |
| if err != nil { |
| return err |
| } |
| |
| if err := validateRepoName(repoName); err != nil { |
| return err |
| } |
| |
| if err := validateDigest(digest); err != nil { |
| return err |
| } |
| |
| store.Lock() |
| defer store.Unlock() |
| if err := store.reload(); err != nil { |
| return err |
| } |
| |
| repoName = registry.NormalizeLocalName(repoName) |
| repoRefs, exists := store.Repositories[repoName] |
| if !exists { |
| repoRefs = Repository{} |
| store.Repositories[repoName] = repoRefs |
| } else if oldID, exists := repoRefs[digest]; exists && oldID != img.ID { |
| return fmt.Errorf("Conflict: Digest %s is already set to image %s", digest, oldID) |
| } |
| |
| repoRefs[digest] = img.ID |
| return store.save() |
| } |
| |
| // Get returns the Repository tag/image map for a given repository. |
| func (store *TagStore) Get(repoName string) (Repository, error) { |
| store.Lock() |
| defer store.Unlock() |
| if err := store.reload(); err != nil { |
| return nil, err |
| } |
| repoName = registry.NormalizeLocalName(repoName) |
| if r, exists := store.Repositories[repoName]; exists { |
| return r, nil |
| } |
| return nil, nil |
| } |
| |
| // GetImage returns a pointer to an Image structure describing the image |
| // referred to by refOrID inside repository repoName. |
| func (store *TagStore) GetImage(repoName, refOrID string) (*image.Image, error) { |
| repo, err := store.Get(repoName) |
| |
| if err != nil { |
| return nil, err |
| } |
| if repo == nil { |
| return nil, nil |
| } |
| |
| store.Lock() |
| defer store.Unlock() |
| if imgID, exists := repo[refOrID]; exists { |
| return store.graph.Get(imgID) |
| } |
| |
| // If no matching tag is found, search through images for a matching image id |
| // iff it looks like a short ID or would look like a short ID |
| if stringid.IsShortID(stringid.TruncateID(refOrID)) { |
| for _, revision := range repo { |
| if strings.HasPrefix(revision, refOrID) { |
| return store.graph.Get(revision) |
| } |
| } |
| } |
| |
| return nil, nil |
| } |
| |
| // GetRepoRefs returns a map with image IDs as keys, and slices listing |
| // repo/tag references as the values. It covers all repositories. |
| 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 := stringid.TruncateID(id) |
| reporefs[shortID] = append(reporefs[shortID], utils.ImageReference(name, tag)) |
| } |
| } |
| store.Unlock() |
| return reporefs |
| } |
| |
| // validateRepoName validates the name of a repository. |
| func validateRepoName(name string) error { |
| if name == "" { |
| return fmt.Errorf("Repository name can't be empty") |
| } |
| if name == "scratch" { |
| return fmt.Errorf("'scratch' is a reserved name") |
| } |
| return nil |
| } |
| |
| func validateDigest(dgst string) error { |
| if dgst == "" { |
| return errors.New("digest can't be empty") |
| } |
| if _, err := digest.ParseDigest(dgst); err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| // poolAdd checks if a push or pull is already running, and returns |
| // (broadcaster, true) if a running operation is found. Otherwise, it creates a |
| // new one and returns (broadcaster, false). |
| func (store *TagStore) poolAdd(kind, key string) (*broadcaster.Buffered, bool) { |
| store.Lock() |
| defer store.Unlock() |
| |
| if p, exists := store.pullingPool[key]; exists { |
| return p, true |
| } |
| if p, exists := store.pushingPool[key]; exists { |
| return p, true |
| } |
| |
| broadcaster := broadcaster.NewBuffered() |
| |
| switch kind { |
| case "pull": |
| store.pullingPool[key] = broadcaster |
| case "push": |
| store.pushingPool[key] = broadcaster |
| default: |
| panic("Unknown pool type") |
| } |
| |
| return broadcaster, false |
| } |
| |
| func (store *TagStore) poolRemoveWithError(kind, key string, broadcasterResult error) error { |
| store.Lock() |
| defer store.Unlock() |
| switch kind { |
| case "pull": |
| if broadcaster, exists := store.pullingPool[key]; exists { |
| broadcaster.CloseWithError(broadcasterResult) |
| delete(store.pullingPool, key) |
| } |
| case "push": |
| if broadcaster, exists := store.pushingPool[key]; exists { |
| broadcaster.CloseWithError(broadcasterResult) |
| delete(store.pushingPool, key) |
| } |
| default: |
| return fmt.Errorf("Unknown pool type") |
| } |
| return nil |
| } |
| |
| func (store *TagStore) poolRemove(kind, key string) error { |
| return store.poolRemoveWithError(kind, key, nil) |
| } |