store.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. package reference
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "os"
  7. "path/filepath"
  8. "sort"
  9. "sync"
  10. "github.com/docker/distribution/reference"
  11. "github.com/docker/docker/pkg/ioutils"
  12. "github.com/opencontainers/go-digest"
  13. )
  14. var (
  15. // ErrDoesNotExist is returned if a reference is not found in the
  16. // store.
  17. ErrDoesNotExist = errors.New("reference does not exist")
  18. )
  19. // An Association is a tuple associating a reference with an image ID.
  20. type Association struct {
  21. Ref reference.Named
  22. ID digest.Digest
  23. }
  24. // Store provides the set of methods which can operate on a tag store.
  25. type Store interface {
  26. References(id digest.Digest) []reference.Named
  27. ReferencesByName(ref reference.Named) []Association
  28. AddTag(ref reference.Named, id digest.Digest, force bool) error
  29. AddDigest(ref reference.Canonical, id digest.Digest, force bool) error
  30. Delete(ref reference.Named) (bool, error)
  31. Get(ref reference.Named) (digest.Digest, error)
  32. }
  33. type store struct {
  34. mu sync.RWMutex
  35. // jsonPath is the path to the file where the serialized tag data is
  36. // stored.
  37. jsonPath string
  38. // Repositories is a map of repositories, indexed by name.
  39. Repositories map[string]repository
  40. // referencesByIDCache is a cache of references indexed by ID, to speed
  41. // up References.
  42. referencesByIDCache map[digest.Digest]map[string]reference.Named
  43. }
  44. // Repository maps tags to digests. The key is a stringified Reference,
  45. // including the repository name.
  46. type repository map[string]digest.Digest
  47. type lexicalRefs []reference.Named
  48. func (a lexicalRefs) Len() int { return len(a) }
  49. func (a lexicalRefs) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
  50. func (a lexicalRefs) Less(i, j int) bool {
  51. return a[i].String() < a[j].String()
  52. }
  53. type lexicalAssociations []Association
  54. func (a lexicalAssociations) Len() int { return len(a) }
  55. func (a lexicalAssociations) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
  56. func (a lexicalAssociations) Less(i, j int) bool {
  57. return a[i].Ref.String() < a[j].Ref.String()
  58. }
  59. // NewReferenceStore creates a new reference store, tied to a file path where
  60. // the set of references are serialized in JSON format.
  61. func NewReferenceStore(jsonPath string) (Store, error) {
  62. abspath, err := filepath.Abs(jsonPath)
  63. if err != nil {
  64. return nil, err
  65. }
  66. store := &store{
  67. jsonPath: abspath,
  68. Repositories: make(map[string]repository),
  69. referencesByIDCache: make(map[digest.Digest]map[string]reference.Named),
  70. }
  71. // Load the json file if it exists, otherwise create it.
  72. if err := store.reload(); os.IsNotExist(err) {
  73. if err := store.save(); err != nil {
  74. return nil, err
  75. }
  76. } else if err != nil {
  77. return nil, err
  78. }
  79. return store, nil
  80. }
  81. // AddTag adds a tag reference to the store. If force is set to true, existing
  82. // references can be overwritten. This only works for tags, not digests.
  83. func (store *store) AddTag(ref reference.Named, id digest.Digest, force bool) error {
  84. if _, isCanonical := ref.(reference.Canonical); isCanonical {
  85. return errors.New("refusing to create a tag with a digest reference")
  86. }
  87. return store.addReference(reference.TagNameOnly(ref), id, force)
  88. }
  89. // AddDigest adds a digest reference to the store.
  90. func (store *store) AddDigest(ref reference.Canonical, id digest.Digest, force bool) error {
  91. return store.addReference(ref, id, force)
  92. }
  93. func favorDigest(originalRef reference.Named) (reference.Named, error) {
  94. ref := originalRef
  95. // If the reference includes a digest and a tag, we must store only the
  96. // digest.
  97. canonical, isCanonical := originalRef.(reference.Canonical)
  98. _, isNamedTagged := originalRef.(reference.NamedTagged)
  99. if isCanonical && isNamedTagged {
  100. trimmed, err := reference.WithDigest(reference.TrimNamed(canonical), canonical.Digest())
  101. if err != nil {
  102. // should never happen
  103. return originalRef, err
  104. }
  105. ref = trimmed
  106. }
  107. return ref, nil
  108. }
  109. func (store *store) addReference(ref reference.Named, id digest.Digest, force bool) error {
  110. ref, err := favorDigest(ref)
  111. if err != nil {
  112. return err
  113. }
  114. refName := reference.FamiliarName(ref)
  115. refStr := reference.FamiliarString(ref)
  116. if refName == string(digest.Canonical) {
  117. return errors.New("refusing to create an ambiguous tag using digest algorithm as name")
  118. }
  119. store.mu.Lock()
  120. defer store.mu.Unlock()
  121. repository, exists := store.Repositories[refName]
  122. if !exists || repository == nil {
  123. repository = make(map[string]digest.Digest)
  124. store.Repositories[refName] = repository
  125. }
  126. oldID, exists := repository[refStr]
  127. if exists {
  128. // force only works for tags
  129. if digested, isDigest := ref.(reference.Canonical); isDigest {
  130. return fmt.Errorf("Cannot overwrite digest %s", digested.Digest().String())
  131. }
  132. if !force {
  133. return fmt.Errorf("Conflict: Tag %s is already set to image %s, if you want to replace it, please use -f option", refStr, oldID.String())
  134. }
  135. if store.referencesByIDCache[oldID] != nil {
  136. delete(store.referencesByIDCache[oldID], refStr)
  137. if len(store.referencesByIDCache[oldID]) == 0 {
  138. delete(store.referencesByIDCache, oldID)
  139. }
  140. }
  141. }
  142. repository[refStr] = id
  143. if store.referencesByIDCache[id] == nil {
  144. store.referencesByIDCache[id] = make(map[string]reference.Named)
  145. }
  146. store.referencesByIDCache[id][refStr] = ref
  147. return store.save()
  148. }
  149. // Delete deletes a reference from the store. It returns true if a deletion
  150. // happened, or false otherwise.
  151. func (store *store) Delete(ref reference.Named) (bool, error) {
  152. ref, err := favorDigest(ref)
  153. if err != nil {
  154. return false, err
  155. }
  156. ref = reference.TagNameOnly(ref)
  157. refName := reference.FamiliarName(ref)
  158. refStr := reference.FamiliarString(ref)
  159. store.mu.Lock()
  160. defer store.mu.Unlock()
  161. repository, exists := store.Repositories[refName]
  162. if !exists {
  163. return false, ErrDoesNotExist
  164. }
  165. if id, exists := repository[refStr]; exists {
  166. delete(repository, refStr)
  167. if len(repository) == 0 {
  168. delete(store.Repositories, refName)
  169. }
  170. if store.referencesByIDCache[id] != nil {
  171. delete(store.referencesByIDCache[id], refStr)
  172. if len(store.referencesByIDCache[id]) == 0 {
  173. delete(store.referencesByIDCache, id)
  174. }
  175. }
  176. return true, store.save()
  177. }
  178. return false, ErrDoesNotExist
  179. }
  180. // Get retrieves an item from the store by reference
  181. func (store *store) Get(ref reference.Named) (digest.Digest, error) {
  182. if canonical, ok := ref.(reference.Canonical); ok {
  183. // If reference contains both tag and digest, only
  184. // lookup by digest as it takes precendent over
  185. // tag, until tag/digest combos are stored.
  186. if _, ok := ref.(reference.Tagged); ok {
  187. var err error
  188. ref, err = reference.WithDigest(reference.TrimNamed(canonical), canonical.Digest())
  189. if err != nil {
  190. return "", err
  191. }
  192. }
  193. } else {
  194. ref = reference.TagNameOnly(ref)
  195. }
  196. refName := reference.FamiliarName(ref)
  197. refStr := reference.FamiliarString(ref)
  198. store.mu.RLock()
  199. defer store.mu.RUnlock()
  200. repository, exists := store.Repositories[refName]
  201. if !exists || repository == nil {
  202. return "", ErrDoesNotExist
  203. }
  204. id, exists := repository[refStr]
  205. if !exists {
  206. return "", ErrDoesNotExist
  207. }
  208. return id, nil
  209. }
  210. // References returns a slice of references to the given ID. The slice
  211. // will be nil if there are no references to this ID.
  212. func (store *store) References(id digest.Digest) []reference.Named {
  213. store.mu.RLock()
  214. defer store.mu.RUnlock()
  215. // Convert the internal map to an array for two reasons:
  216. // 1) We must not return a mutable
  217. // 2) It would be ugly to expose the extraneous map keys to callers.
  218. var references []reference.Named
  219. for _, ref := range store.referencesByIDCache[id] {
  220. references = append(references, ref)
  221. }
  222. sort.Sort(lexicalRefs(references))
  223. return references
  224. }
  225. // ReferencesByName returns the references for a given repository name.
  226. // If there are no references known for this repository name,
  227. // ReferencesByName returns nil.
  228. func (store *store) ReferencesByName(ref reference.Named) []Association {
  229. refName := reference.FamiliarName(ref)
  230. store.mu.RLock()
  231. defer store.mu.RUnlock()
  232. repository, exists := store.Repositories[refName]
  233. if !exists {
  234. return nil
  235. }
  236. var associations []Association
  237. for refStr, refID := range repository {
  238. ref, err := reference.ParseNormalizedNamed(refStr)
  239. if err != nil {
  240. // Should never happen
  241. return nil
  242. }
  243. associations = append(associations,
  244. Association{
  245. Ref: ref,
  246. ID: refID,
  247. })
  248. }
  249. sort.Sort(lexicalAssociations(associations))
  250. return associations
  251. }
  252. func (store *store) save() error {
  253. // Store the json
  254. jsonData, err := json.Marshal(store)
  255. if err != nil {
  256. return err
  257. }
  258. return ioutils.AtomicWriteFile(store.jsonPath, jsonData, 0600)
  259. }
  260. func (store *store) reload() error {
  261. f, err := os.Open(store.jsonPath)
  262. if err != nil {
  263. return err
  264. }
  265. defer f.Close()
  266. if err := json.NewDecoder(f).Decode(&store); err != nil {
  267. return err
  268. }
  269. for _, repository := range store.Repositories {
  270. for refStr, refID := range repository {
  271. ref, err := reference.ParseNormalizedNamed(refStr)
  272. if err != nil {
  273. // Should never happen
  274. continue
  275. }
  276. if store.referencesByIDCache[refID] == nil {
  277. store.referencesByIDCache[refID] = make(map[string]reference.Named)
  278. }
  279. store.referencesByIDCache[refID][refStr] = ref
  280. }
  281. }
  282. return nil
  283. }