01b6b2be |
package graph |
680f40c3 |
import (
"encoding/json" |
a2b0c977 |
"errors" |
49a78929 |
"fmt" |
7d0053d5 |
"io" |
680f40c3 |
"io/ioutil" |
d0c77652 |
"os" |
680f40c3 |
"path/filepath" |
846a9e89 |
"sort" |
49a78929 |
"strings" |
c4990ab9 |
"sync"
|
1ec25653 |
"github.com/docker/distribution/digest" |
c9eb37f9 |
"github.com/docker/docker/daemon/events" |
aed8f9f7 |
"github.com/docker/docker/graph/tags" |
9001ea26 |
"github.com/docker/docker/image" |
43981084 |
"github.com/docker/docker/pkg/parsers" |
b80fae73 |
"github.com/docker/docker/pkg/stringid" |
568f86eb |
"github.com/docker/docker/registry" |
9e50bf62 |
"github.com/docker/docker/trust" |
a2b0c977 |
"github.com/docker/docker/utils" |
8ceb9d20 |
"github.com/docker/libtrust" |
680f40c3 |
)
|
fd224ee5 |
const DEFAULTTAG = "latest" |
640026ec |
|
44faa07b |
type TagStore struct { |
568f86eb |
path string
graph *Graph
Repositories map[string]Repository |
8ceb9d20 |
trustKey libtrust.PrivateKey |
c4990ab9 |
sync.Mutex |
6856a6b1 |
// FIXME: move push/pull-related fields
// to a helper type |
03d3d79b |
pullingPool map[string]chan struct{}
pushingPool map[string]chan struct{}
registryService *registry.Service |
c9eb37f9 |
eventsService *events.Events |
9e50bf62 |
trustService *trust.TrustStore |
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
}
|
9e50bf62 |
type TagStoreConfig struct {
Graph *Graph
Key libtrust.PrivateKey
Registry *registry.Service
Events *events.Events
Trust *trust.TrustStore
}
func NewTagStore(path string, cfg *TagStoreConfig) (*TagStore, error) { |
680f40c3 |
abspath, err := filepath.Abs(path)
if err != nil {
return nil, err
} |
f29b2e48 |
|
44faa07b |
store := &TagStore{ |
03d3d79b |
path: abspath, |
9e50bf62 |
graph: cfg.Graph,
trustKey: cfg.Key, |
03d3d79b |
Repositories: make(map[string]Repository),
pullingPool: make(map[string]chan struct{}),
pushingPool: make(map[string]chan struct{}), |
9e50bf62 |
registryService: cfg.Registry,
eventsService: cfg.Events,
trustService: cfg.Trust, |
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 { |
26543e03 |
f, err := os.Open(store.path) |
680f40c3 |
if err != nil {
return err
} |
26543e03 |
defer f.Close()
if err := json.NewDecoder(f).Decode(&store); err != nil { |
680f40c3 |
return err
}
return nil
}
|
9001ea26 |
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) |
a2b0c977 |
repoName, ref := parsers.ParseRepositoryTag(name)
if ref == "" {
ref = DEFAULTTAG
}
var (
err error |
9001ea26 |
img *image.Image |
a2b0c977 |
)
img, err = store.GetImage(repoName, ref)
if err != nil {
return nil, err
}
if img != nil {
return img, err |
ca98434a |
} |
a2b0c977 |
// name must be an image ID. |
c4990ab9 |
store.Lock()
defer store.Unlock() |
a2b0c977 |
if img, err = store.graph.Get(name); err != nil { |
ca98434a |
return nil, err |
640026ec |
} |
a2b0c977 |
|
640026ec |
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 { |
a2b0c977 |
name := utils.ImageReference(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]
} |
b80fae73 |
return stringid.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
}
|
a2b0c977 |
func (store *TagStore) Delete(repoName, ref string) (bool, error) { |
c4990ab9 |
store.Lock()
defer store.Unlock() |
9060b5c2 |
deleted := false |
c4990ab9 |
if err := store.reload(); err != nil { |
9060b5c2 |
return false, err |
c80448c4 |
} |
a2b0c977 |
|
568f86eb |
repoName = registry.NormalizeLocalName(repoName) |
a2b0c977 |
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 { |
c80448c4 |
delete(store.Repositories, repoName)
} |
a2b0c977 |
deleted = true |
c80448c4 |
} |
a2b0c977 |
|
c4990ab9 |
return deleted, store.save() |
12049f95 |
}
|
99f6309b |
func (store *TagStore) Tag(repoName, tag, imageName string, force bool) error { |
7d0053d5 |
return store.SetLoad(repoName, tag, imageName, force, nil)
}
func (store *TagStore) SetLoad(repoName, tag, imageName string, force bool, out io.Writer) error { |
bf7602bc |
img, err := store.LookupImage(imageName) |
c4990ab9 |
store.Lock()
defer store.Unlock() |
bf7602bc |
if err != nil {
return err
} |
640026ec |
if tag == "" { |
aed8f9f7 |
tag = tags.DEFAULTTAG |
49a78929 |
} |
640026ec |
if err := validateRepoName(repoName); err != nil {
return err
} |
aed8f9f7 |
if err := tags.ValidateTagName(tag); err != nil { |
1ec25653 |
if _, formatError := err.(tags.ErrTagInvalidFormat); !formatError {
return err
}
if _, dErr := digest.ParseDigest(tag); dErr != nil {
// Still return the tag validation error.
// It's more likely to be a user generated issue.
return err
} |
49a78929 |
} |
c4990ab9 |
if err := store.reload(); err != nil { |
680f40c3 |
return err
} |
44faa07b |
var repo Repository |
568f86eb |
repoName = registry.NormalizeLocalName(repoName) |
680f40c3 |
if r, exists := store.Repositories[repoName]; exists {
repo = r |
7d0053d5 |
if old, exists := store.Repositories[repoName][tag]; exists {
if !force {
return fmt.Errorf("Conflict: Tag %s is already set to image %s, if you want to replace it, please use -f option", tag, old)
}
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])
} |
5e6f16e3 |
} |
680f40c3 |
} else { |
44faa07b |
repo = make(map[string]string) |
680f40c3 |
store.Repositories[repoName] = repo
} |
fd224ee5 |
repo[tag] = img.ID |
c4990ab9 |
return store.save() |
680f40c3 |
}
|
a2b0c977 |
// 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()
}
|
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
} |
568f86eb |
repoName = registry.NormalizeLocalName(repoName) |
680f40c3 |
if r, exists := store.Repositories[repoName]; exists {
return r, nil
}
return nil, nil
}
|
9001ea26 |
func (store *TagStore) GetImage(repoName, refOrID string) (*image.Image, error) { |
680f40c3 |
repo, err := store.Get(repoName) |
a2b0c977 |
|
680f40c3 |
if err != nil {
return nil, err |
a2b0c977 |
}
if repo == nil { |
680f40c3 |
return nil, nil
} |
a2b0c977 |
store.Lock()
defer store.Unlock()
if imgID, exists := repo[refOrID]; exists {
return store.graph.Get(imgID) |
44b3e8d5 |
} |
a2b0c977 |
|
44b3e8d5 |
// If no matching tag is found, search through images for a matching image id |
8d1a5003 |
// 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)
} |
d26a3b37 |
}
} |
a2b0c977 |
|
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 { |
b80fae73 |
shortID := stringid.TruncateID(id) |
a2b0c977 |
reporefs[shortID] = append(reporefs[shortID], utils.ImageReference(name, tag)) |
c4990ab9 |
}
}
store.Unlock()
return reporefs
}
|
640026ec |
// Validate the name of a repository
func validateRepoName(name string) error {
if name == "" {
return fmt.Errorf("Repository name can't be empty")
} |
89367899 |
if name == "scratch" {
return fmt.Errorf("'scratch' is a reserved name")
} |
640026ec |
return nil
}
|
a2b0c977 |
func validateDigest(dgst string) error {
if dgst == "" {
return errors.New("digest can't be empty")
} |
1ec25653 |
if _, err := digest.ParseDigest(dgst); err != nil {
return err |
a2b0c977 |
}
return nil
}
|
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
} |