tags.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. package docker
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io/ioutil"
  6. "os"
  7. "path/filepath"
  8. "sort"
  9. "strings"
  10. )
  11. const DEFAULT_TAG = "latest"
  12. type TagStore struct {
  13. path string
  14. graph *Graph
  15. Repositories map[string]Repository
  16. }
  17. type Repository map[string]string
  18. func NewTagStore(path string, graph *Graph) (*TagStore, error) {
  19. abspath, err := filepath.Abs(path)
  20. if err != nil {
  21. return nil, err
  22. }
  23. store := &TagStore{
  24. path: abspath,
  25. graph: graph,
  26. Repositories: make(map[string]Repository),
  27. }
  28. // Load the json file if it exists, otherwise create it.
  29. if err := store.Reload(); os.IsNotExist(err) {
  30. if err := store.Save(); err != nil {
  31. return nil, err
  32. }
  33. } else if err != nil {
  34. return nil, err
  35. }
  36. return store, nil
  37. }
  38. func (store *TagStore) Save() error {
  39. // Store the json ball
  40. jsonData, err := json.Marshal(store)
  41. if err != nil {
  42. return err
  43. }
  44. if err := ioutil.WriteFile(store.path, jsonData, 0600); err != nil {
  45. return err
  46. }
  47. return nil
  48. }
  49. func (store *TagStore) Reload() error {
  50. jsonData, err := ioutil.ReadFile(store.path)
  51. if err != nil {
  52. return err
  53. }
  54. if err := json.Unmarshal(jsonData, store); err != nil {
  55. return err
  56. }
  57. return nil
  58. }
  59. func (store *TagStore) LookupImage(name string) (*Image, error) {
  60. img, err := store.graph.Get(name)
  61. if err != nil {
  62. // FIXME: standardize on returning nil when the image doesn't exist, and err for everything else
  63. // (so we can pass all errors here)
  64. repoAndTag := strings.SplitN(name, ":", 2)
  65. if len(repoAndTag) == 1 {
  66. repoAndTag = append(repoAndTag, DEFAULT_TAG)
  67. }
  68. if i, err := store.GetImage(repoAndTag[0], repoAndTag[1]); err != nil {
  69. return nil, err
  70. } else if i == nil {
  71. return nil, fmt.Errorf("Image does not exist: %s", name)
  72. } else {
  73. img = i
  74. }
  75. }
  76. return img, nil
  77. }
  78. // Return a reverse-lookup table of all the names which refer to each image
  79. // Eg. {"43b5f19b10584": {"base:latest", "base:v1"}}
  80. func (store *TagStore) ById() map[string][]string {
  81. byId := make(map[string][]string)
  82. for repoName, repository := range store.Repositories {
  83. for tag, id := range repository {
  84. name := repoName + ":" + tag
  85. if _, exists := byId[id]; !exists {
  86. byId[id] = []string{name}
  87. } else {
  88. byId[id] = append(byId[id], name)
  89. sort.Strings(byId[id])
  90. }
  91. }
  92. }
  93. return byId
  94. }
  95. func (store *TagStore) ImageName(id string) string {
  96. if names, exists := store.ById()[id]; exists && len(names) > 0 {
  97. return names[0]
  98. }
  99. return TruncateId(id)
  100. }
  101. func (store *TagStore) Set(repoName, tag, imageName string, force bool) error {
  102. img, err := store.LookupImage(imageName)
  103. if err != nil {
  104. return err
  105. }
  106. if tag == "" {
  107. tag = DEFAULT_TAG
  108. }
  109. if err := validateRepoName(repoName); err != nil {
  110. return err
  111. }
  112. if err := validateTagName(tag); err != nil {
  113. return err
  114. }
  115. if err := store.Reload(); err != nil {
  116. return err
  117. }
  118. var repo Repository
  119. if r, exists := store.Repositories[repoName]; exists {
  120. repo = r
  121. } else {
  122. repo = make(map[string]string)
  123. if old, exists := store.Repositories[repoName]; exists && !force {
  124. return fmt.Errorf("Tag %s:%s is already set to %s", repoName, tag, old)
  125. }
  126. store.Repositories[repoName] = repo
  127. }
  128. repo[tag] = img.Id
  129. return store.Save()
  130. }
  131. func (store *TagStore) Get(repoName string) (Repository, error) {
  132. if err := store.Reload(); err != nil {
  133. return nil, err
  134. }
  135. if r, exists := store.Repositories[repoName]; exists {
  136. return r, nil
  137. }
  138. return nil, nil
  139. }
  140. func (store *TagStore) GetImage(repoName, tag string) (*Image, error) {
  141. repo, err := store.Get(repoName)
  142. if err != nil {
  143. return nil, err
  144. } else if repo == nil {
  145. return nil, nil
  146. }
  147. if revision, exists := repo[tag]; exists {
  148. return store.graph.Get(revision)
  149. }
  150. return nil, nil
  151. }
  152. // Validate the name of a repository
  153. func validateRepoName(name string) error {
  154. if name == "" {
  155. return fmt.Errorf("Repository name can't be empty")
  156. }
  157. if strings.Contains(name, ":") {
  158. return fmt.Errorf("Illegal repository name: %s", name)
  159. }
  160. return nil
  161. }
  162. // Validate the name of a tag
  163. func validateTagName(name string) error {
  164. if name == "" {
  165. return fmt.Errorf("Tag name can't be empty")
  166. }
  167. if strings.Contains(name, "/") || strings.Contains(name, ":") {
  168. return fmt.Errorf("Illegal tag name: %s", name)
  169. }
  170. return nil
  171. }