tags.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. package docker
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "github.com/dotcloud/docker/utils"
  6. "io/ioutil"
  7. "os"
  8. "path/filepath"
  9. "sort"
  10. "strings"
  11. )
  12. const DEFAULTTAG = "latest"
  13. type TagStore struct {
  14. path string
  15. graph *Graph
  16. Repositories map[string]Repository
  17. }
  18. type Repository map[string]string
  19. func NewTagStore(path string, graph *Graph) (*TagStore, error) {
  20. abspath, err := filepath.Abs(path)
  21. if err != nil {
  22. return nil, err
  23. }
  24. store := &TagStore{
  25. path: abspath,
  26. graph: graph,
  27. Repositories: make(map[string]Repository),
  28. }
  29. // Load the json file if it exists, otherwise create it.
  30. if err := store.Reload(); os.IsNotExist(err) {
  31. if err := store.Save(); err != nil {
  32. return nil, err
  33. }
  34. } else if err != nil {
  35. return nil, err
  36. }
  37. return store, nil
  38. }
  39. func (store *TagStore) Save() error {
  40. // Store the json ball
  41. jsonData, err := json.Marshal(store)
  42. if err != nil {
  43. return err
  44. }
  45. if err := ioutil.WriteFile(store.path, jsonData, 0600); err != nil {
  46. return err
  47. }
  48. return nil
  49. }
  50. func (store *TagStore) Reload() error {
  51. jsonData, err := ioutil.ReadFile(store.path)
  52. if err != nil {
  53. return err
  54. }
  55. if err := json.Unmarshal(jsonData, store); err != nil {
  56. return err
  57. }
  58. return nil
  59. }
  60. func (store *TagStore) LookupImage(name string) (*Image, error) {
  61. img, err := store.graph.Get(name)
  62. if err != nil {
  63. // FIXME: standardize on returning nil when the image doesn't exist, and err for everything else
  64. // (so we can pass all errors here)
  65. repos, tag := utils.ParseRepositoryTag(name)
  66. if tag == "" {
  67. tag = DEFAULTTAG
  68. }
  69. if i, err := store.GetImage(repos, tag); err != nil {
  70. return nil, err
  71. } else if i == nil {
  72. return nil, fmt.Errorf("Image does not exist: %s", name)
  73. } else {
  74. img = i
  75. }
  76. }
  77. return img, nil
  78. }
  79. // Return a reverse-lookup table of all the names which refer to each image
  80. // Eg. {"43b5f19b10584": {"base:latest", "base:v1"}}
  81. func (store *TagStore) ByID() map[string][]string {
  82. byID := make(map[string][]string)
  83. for repoName, repository := range store.Repositories {
  84. for tag, id := range repository {
  85. name := repoName + ":" + tag
  86. if _, exists := byID[id]; !exists {
  87. byID[id] = []string{name}
  88. } else {
  89. byID[id] = append(byID[id], name)
  90. sort.Strings(byID[id])
  91. }
  92. }
  93. }
  94. return byID
  95. }
  96. func (store *TagStore) ImageName(id string) string {
  97. if names, exists := store.ByID()[id]; exists && len(names) > 0 {
  98. return names[0]
  99. }
  100. return utils.TruncateID(id)
  101. }
  102. func (store *TagStore) DeleteAll(id string) error {
  103. names, exists := store.ByID()[id]
  104. if !exists || len(names) == 0 {
  105. return nil
  106. }
  107. for _, name := range names {
  108. if strings.Contains(name, ":") {
  109. nameParts := strings.Split(name, ":")
  110. if _, err := store.Delete(nameParts[0], nameParts[1]); err != nil {
  111. return err
  112. }
  113. } else {
  114. if _, err := store.Delete(name, ""); err != nil {
  115. return err
  116. }
  117. }
  118. }
  119. return nil
  120. }
  121. func (store *TagStore) Delete(repoName, tag string) (bool, error) {
  122. deleted := false
  123. if err := store.Reload(); err != nil {
  124. return false, err
  125. }
  126. if r, exists := store.Repositories[repoName]; exists {
  127. if tag != "" {
  128. if _, exists2 := r[tag]; exists2 {
  129. delete(r, tag)
  130. if len(r) == 0 {
  131. delete(store.Repositories, repoName)
  132. }
  133. deleted = true
  134. } else {
  135. return false, fmt.Errorf("No such tag: %s:%s", repoName, tag)
  136. }
  137. } else {
  138. delete(store.Repositories, repoName)
  139. deleted = true
  140. }
  141. } else {
  142. fmt.Errorf("No such repository: %s", repoName)
  143. }
  144. return deleted, store.Save()
  145. }
  146. func (store *TagStore) Set(repoName, tag, imageName string, force bool) error {
  147. img, err := store.LookupImage(imageName)
  148. if err != nil {
  149. return err
  150. }
  151. if tag == "" {
  152. tag = DEFAULTTAG
  153. }
  154. if err := validateRepoName(repoName); err != nil {
  155. return err
  156. }
  157. if err := validateTagName(tag); err != nil {
  158. return err
  159. }
  160. if err := store.Reload(); err != nil {
  161. return err
  162. }
  163. var repo Repository
  164. if r, exists := store.Repositories[repoName]; exists {
  165. repo = r
  166. } else {
  167. repo = make(map[string]string)
  168. if old, exists := store.Repositories[repoName]; exists && !force {
  169. return fmt.Errorf("Conflict: Tag %s:%s is already set to %s", repoName, tag, old)
  170. }
  171. store.Repositories[repoName] = repo
  172. }
  173. repo[tag] = img.ID
  174. return store.Save()
  175. }
  176. func (store *TagStore) Get(repoName string) (Repository, error) {
  177. if err := store.Reload(); err != nil {
  178. return nil, err
  179. }
  180. if r, exists := store.Repositories[repoName]; exists {
  181. return r, nil
  182. }
  183. return nil, nil
  184. }
  185. func (store *TagStore) GetImage(repoName, tagOrID string) (*Image, error) {
  186. repo, err := store.Get(repoName)
  187. if err != nil {
  188. return nil, err
  189. } else if repo == nil {
  190. return nil, nil
  191. }
  192. if revision, exists := repo[tagOrID]; exists {
  193. return store.graph.Get(revision)
  194. }
  195. // If no matching tag is found, search through images for a matching image id
  196. for _, revision := range repo {
  197. if strings.HasPrefix(revision, tagOrID) {
  198. return store.graph.Get(revision)
  199. }
  200. }
  201. return nil, nil
  202. }
  203. // Validate the name of a repository
  204. func validateRepoName(name string) error {
  205. if name == "" {
  206. return fmt.Errorf("Repository name can't be empty")
  207. }
  208. return nil
  209. }
  210. // Validate the name of a tag
  211. func validateTagName(name string) error {
  212. if name == "" {
  213. return fmt.Errorf("Tag name can't be empty")
  214. }
  215. if strings.Contains(name, "/") || strings.Contains(name, ":") {
  216. return fmt.Errorf("Illegal tag name: %s", name)
  217. }
  218. return nil
  219. }