2013-03-21 20:47:23 -04:00
|
|
|
package docker
|
2013-03-21 15:18:47 -04:00
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
2013-03-21 23:06:20 -04:00
|
|
|
"fmt"
|
2013-05-14 18:37:35 -04:00
|
|
|
"github.com/dotcloud/docker/utils"
|
2013-03-21 15:18:47 -04:00
|
|
|
"io/ioutil"
|
2013-03-21 22:00:43 -04:00
|
|
|
"os"
|
2013-03-21 15:18:47 -04:00
|
|
|
"path/filepath"
|
2013-03-26 18:30:16 -04:00
|
|
|
"sort"
|
2013-03-21 23:06:20 -04:00
|
|
|
"strings"
|
2013-03-21 15:18:47 -04:00
|
|
|
)
|
|
|
|
|
2013-06-04 14:00:22 -04:00
|
|
|
const DEFAULTTAG = "latest"
|
2013-03-22 19:07:13 -04:00
|
|
|
|
2013-03-21 20:35:49 -04:00
|
|
|
type TagStore struct {
|
2013-03-21 15:18:47 -04:00
|
|
|
path string
|
|
|
|
graph *Graph
|
2013-03-21 20:35:49 -04:00
|
|
|
Repositories map[string]Repository
|
2013-03-21 15:18:47 -04:00
|
|
|
}
|
|
|
|
|
2013-03-21 20:35:49 -04:00
|
|
|
type Repository map[string]string
|
2013-03-21 15:18:47 -04:00
|
|
|
|
2013-03-21 20:35:49 -04:00
|
|
|
func NewTagStore(path string, graph *Graph) (*TagStore, error) {
|
2013-03-21 15:18:47 -04:00
|
|
|
abspath, err := filepath.Abs(path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2013-03-21 20:35:49 -04:00
|
|
|
store := &TagStore{
|
2013-03-21 15:18:47 -04:00
|
|
|
path: abspath,
|
|
|
|
graph: graph,
|
2013-03-21 20:35:49 -04:00
|
|
|
Repositories: make(map[string]Repository),
|
2013-03-21 15:18:47 -04:00
|
|
|
}
|
2013-03-21 22:00:43 -04:00
|
|
|
// 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 {
|
2013-03-21 15:18:47 -04:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return store, nil
|
|
|
|
}
|
|
|
|
|
2013-03-21 20:35:49 -04:00
|
|
|
func (store *TagStore) Save() error {
|
2013-03-21 15:18:47 -04:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2013-03-21 20:35:49 -04:00
|
|
|
func (store *TagStore) Reload() error {
|
2013-03-21 15:18:47 -04:00
|
|
|
jsonData, err := ioutil.ReadFile(store.path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := json.Unmarshal(jsonData, store); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-03-22 19:07:13 -04:00
|
|
|
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 {
|
2013-06-04 14:00:22 -04:00
|
|
|
repoAndTag = append(repoAndTag, DEFAULTTAG)
|
2013-03-22 19:07:13 -04:00
|
|
|
}
|
|
|
|
if i, err := store.GetImage(repoAndTag[0], repoAndTag[1]); err != nil {
|
|
|
|
return nil, err
|
|
|
|
} else if i == nil {
|
2013-03-26 08:28:17 -04:00
|
|
|
return nil, fmt.Errorf("Image does not exist: %s", name)
|
2013-03-22 19:07:13 -04:00
|
|
|
} else {
|
|
|
|
img = i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return img, nil
|
|
|
|
}
|
|
|
|
|
2013-03-22 22:22:06 -04:00
|
|
|
// Return a reverse-lookup table of all the names which refer to each image
|
|
|
|
// Eg. {"43b5f19b10584": {"base:latest", "base:v1"}}
|
2013-06-04 14:00:22 -04:00
|
|
|
func (store *TagStore) ByID() map[string][]string {
|
|
|
|
byID := make(map[string][]string)
|
2013-03-22 22:22:06 -04:00
|
|
|
for repoName, repository := range store.Repositories {
|
|
|
|
for tag, id := range repository {
|
|
|
|
name := repoName + ":" + tag
|
2013-06-04 14:00:22 -04:00
|
|
|
if _, exists := byID[id]; !exists {
|
|
|
|
byID[id] = []string{name}
|
2013-03-22 22:22:06 -04:00
|
|
|
} else {
|
2013-06-04 14:00:22 -04:00
|
|
|
byID[id] = append(byID[id], name)
|
|
|
|
sort.Strings(byID[id])
|
2013-03-22 22:22:06 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-06-04 14:00:22 -04:00
|
|
|
return byID
|
2013-03-22 22:22:06 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (store *TagStore) ImageName(id string) string {
|
2013-06-04 14:00:22 -04:00
|
|
|
if names, exists := store.ByID()[id]; exists && len(names) > 0 {
|
2013-03-22 22:22:06 -04:00
|
|
|
return names[0]
|
|
|
|
}
|
2013-06-04 14:00:22 -04:00
|
|
|
return utils.TruncateID(id)
|
2013-03-22 22:22:06 -04:00
|
|
|
}
|
|
|
|
|
2013-05-30 18:53:45 -04:00
|
|
|
func (store *TagStore) DeleteAll(id string) error {
|
2013-06-10 17:05:54 -04:00
|
|
|
names, exists := store.ByID()[id]
|
2013-05-30 18:53:45 -04:00
|
|
|
if !exists || len(names) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
for _, name := range names {
|
|
|
|
if strings.Contains(name, ":") {
|
|
|
|
nameParts := strings.Split(name, ":")
|
2013-05-31 10:37:02 -04:00
|
|
|
if _, err := store.Delete(nameParts[0], nameParts[1]); err != nil {
|
2013-05-30 18:53:45 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
2013-05-31 10:37:02 -04:00
|
|
|
if _, err := store.Delete(name, ""); err != nil {
|
2013-05-30 18:53:45 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-05-31 10:37:02 -04:00
|
|
|
func (store *TagStore) Delete(repoName, tag string) (bool, error) {
|
|
|
|
deleted := false
|
2013-05-15 09:11:39 -04:00
|
|
|
if err := store.Reload(); err != nil {
|
2013-05-31 10:37:02 -04:00
|
|
|
return false, err
|
2013-05-15 09:11:39 -04:00
|
|
|
}
|
|
|
|
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)
|
|
|
|
}
|
2013-05-31 10:37:02 -04:00
|
|
|
deleted = true
|
2013-05-15 09:11:39 -04:00
|
|
|
} else {
|
2013-05-31 10:37:02 -04:00
|
|
|
return false, fmt.Errorf("No such tag: %s:%s", repoName, tag)
|
2013-05-15 09:11:39 -04:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
delete(store.Repositories, repoName)
|
2013-05-31 10:37:02 -04:00
|
|
|
deleted = true
|
2013-05-15 09:11:39 -04:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
fmt.Errorf("No such repository: %s", repoName)
|
|
|
|
}
|
2013-05-31 10:37:02 -04:00
|
|
|
return deleted, store.Save()
|
2013-03-22 22:22:06 -04:00
|
|
|
}
|
|
|
|
|
2013-03-22 21:27:18 -04:00
|
|
|
func (store *TagStore) Set(repoName, tag, imageName string, force bool) error {
|
|
|
|
img, err := store.LookupImage(imageName)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-03-22 19:07:13 -04:00
|
|
|
if tag == "" {
|
2013-06-04 14:00:22 -04:00
|
|
|
tag = DEFAULTTAG
|
2013-03-21 23:06:20 -04:00
|
|
|
}
|
2013-03-22 19:07:13 -04:00
|
|
|
if err := validateRepoName(repoName); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := validateTagName(tag); err != nil {
|
|
|
|
return err
|
2013-03-21 23:06:20 -04:00
|
|
|
}
|
2013-03-21 15:18:47 -04:00
|
|
|
if err := store.Reload(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-03-21 20:35:49 -04:00
|
|
|
var repo Repository
|
2013-03-21 15:18:47 -04:00
|
|
|
if r, exists := store.Repositories[repoName]; exists {
|
|
|
|
repo = r
|
|
|
|
} else {
|
2013-03-21 20:35:49 -04:00
|
|
|
repo = make(map[string]string)
|
2013-03-22 21:27:18 -04:00
|
|
|
if old, exists := store.Repositories[repoName]; exists && !force {
|
2013-05-20 14:31:45 -04:00
|
|
|
return fmt.Errorf("Conflict: Tag %s:%s is already set to %s", repoName, tag, old)
|
2013-03-22 21:27:18 -04:00
|
|
|
}
|
2013-03-21 15:18:47 -04:00
|
|
|
store.Repositories[repoName] = repo
|
|
|
|
}
|
2013-06-04 14:00:22 -04:00
|
|
|
repo[tag] = img.ID
|
2013-03-21 15:18:47 -04:00
|
|
|
return store.Save()
|
|
|
|
}
|
|
|
|
|
2013-03-21 20:35:49 -04:00
|
|
|
func (store *TagStore) Get(repoName string) (Repository, error) {
|
2013-03-21 15:18:47 -04:00
|
|
|
if err := store.Reload(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if r, exists := store.Repositories[repoName]; exists {
|
|
|
|
return r, nil
|
|
|
|
}
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2013-06-03 16:00:15 -04:00
|
|
|
func (store *TagStore) GetImage(repoName, tagOrId string) (*Image, error) {
|
2013-03-21 15:18:47 -04:00
|
|
|
repo, err := store.Get(repoName)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
} else if repo == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
2013-06-03 16:00:15 -04:00
|
|
|
//go through all the tags, to see if tag is in fact an ID
|
|
|
|
for _, revision := range repo {
|
2013-06-13 09:17:56 -04:00
|
|
|
if strings.HasPrefix(revision, tagOrId) {
|
2013-06-03 16:00:15 -04:00
|
|
|
return store.graph.Get(revision)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if revision, exists := repo[tagOrId]; exists {
|
2013-03-21 15:18:47 -04:00
|
|
|
return store.graph.Get(revision)
|
|
|
|
}
|
|
|
|
return nil, nil
|
|
|
|
}
|
2013-03-22 19:07:13 -04:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|