package docker import ( "encoding/json" "fmt" "github.com/dotcloud/docker/utils" "io/ioutil" "os" "path/filepath" "sort" "strings" ) const DEFAULT_TAG = "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, error) { img, err := store.graph.Get(name) if err != nil { // FIXME: standardize on returning nil when the image doesn't exist, and err for everything else // (so we can pass all errors here) repoAndTag := strings.SplitN(name, ":", 2) if len(repoAndTag) == 1 { repoAndTag = append(repoAndTag, DEFAULT_TAG) } if i, err := store.GetImage(repoAndTag[0], repoAndTag[1]); err != nil { return nil, err } else if i == nil { return nil, fmt.Errorf("Image does not exist: %s", name) } else { img = i } } 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 = DEFAULT_TAG } 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, tag string) (*Image, error) { repo, err := store.Get(repoName) if err != nil { return nil, err } else if repo == nil { return nil, nil } if revision, exists := repo[tag]; exists { 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") } if strings.Contains(name, ":") { return fmt.Errorf("Illegal repository name: %s", name) } 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 }