diff options
Diffstat (limited to 'graph/tags.go')
-rw-r--r-- | graph/tags.go | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/graph/tags.go b/graph/tags.go new file mode 100644 index 0000000000..524e1a1f9d --- /dev/null +++ b/graph/tags.go @@ -0,0 +1,235 @@ +package graph + +import ( + "encoding/json" + "fmt" + "github.com/dotcloud/docker/image" + "github.com/dotcloud/docker/utils" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strings" +) + +const DEFAULTTAG = "latest" + +type TagStore struct { + path string + graph *Graph + Repositories map[string]Repository +} + +type Repository map[string]string + +func NewTagStore(path string, graph *Graph) (*TagStore, error) { + abspath, err := filepath.Abs(path) + if err != nil { + return nil, err + } + store := &TagStore{ + path: abspath, + graph: graph, + Repositories: make(map[string]Repository), + } + // 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 { + jsonData, err := ioutil.ReadFile(store.path) + if err != nil { + return err + } + if err := json.Unmarshal(jsonData, store); err != nil { + return err + } + return nil +} + +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) + repos, tag := utils.ParseRepositoryTag(name) + if tag == "" { + tag = DEFAULTTAG + } + img, err := store.GetImage(repos, tag) + if err != nil { + return nil, err + } else if img == nil { + if img, err = store.graph.Get(name); err != nil { + return nil, err + } + } + return img, nil +} + +// Return a reverse-lookup table of all the names which refer to each image +// Eg. {"43b5f19b10584": {"base:latest", "base:v1"}} +func (store *TagStore) ByID() map[string][]string { + byID := make(map[string][]string) + for repoName, repository := range store.Repositories { + for tag, id := range repository { + name := repoName + ":" + tag + if _, exists := byID[id]; !exists { + byID[id] = []string{name} + } else { + byID[id] = append(byID[id], name) + sort.Strings(byID[id]) + } + } + } + return byID +} + +func (store *TagStore) ImageName(id string) string { + if names, exists := store.ByID()[id]; exists && len(names) > 0 { + return names[0] + } + return utils.TruncateID(id) +} + +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 +} + +func (store *TagStore) Delete(repoName, tag string) (bool, error) { + deleted := false + if err := store.Reload(); err != nil { + return false, err + } + 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) + } + deleted = true + } else { + return false, fmt.Errorf("No such tag: %s:%s", repoName, tag) + } + } else { + delete(store.Repositories, repoName) + deleted = true + } + } else { + fmt.Errorf("No such repository: %s", repoName) + } + return deleted, store.Save() +} + +func (store *TagStore) Set(repoName, tag, imageName string, force bool) error { + img, err := store.LookupImage(imageName) + if err != nil { + return err + } + if tag == "" { + tag = DEFAULTTAG + } + if err := validateRepoName(repoName); err != nil { + return err + } + if err := validateTagName(tag); err != nil { + return err + } + if err := store.Reload(); err != nil { + return err + } + var repo Repository + if r, exists := store.Repositories[repoName]; exists { + repo = r + } else { + repo = make(map[string]string) + if old, exists := store.Repositories[repoName]; exists && !force { + return fmt.Errorf("Conflict: Tag %s:%s is already set to %s", repoName, tag, old) + } + store.Repositories[repoName] = repo + } + repo[tag] = img.ID + return store.Save() +} + +func (store *TagStore) Get(repoName string) (Repository, error) { + if err := store.Reload(); err != nil { + return nil, err + } + if r, exists := store.Repositories[repoName]; exists { + return r, nil + } + return nil, nil +} + +func (store *TagStore) GetImage(repoName, tagOrID string) (*image.Image, error) { + repo, err := store.Get(repoName) + if err != nil { + return nil, err + } else if repo == nil { + return nil, nil + } + 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 + for _, revision := range repo { + if strings.HasPrefix(revision, tagOrID) { + return store.graph.Get(revision) + } + } + return nil, nil +} + +// 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 +func validateTagName(name string) error { + if name == "" { + return fmt.Errorf("Tag name can't be empty") + } + if strings.Contains(name, "/") || strings.Contains(name, ":") { + return fmt.Errorf("Illegal tag name: %s", name) + } + return nil +} |