store.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. package reference
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "os"
  6. "path/filepath"
  7. "sort"
  8. "sync"
  9. "github.com/docker/distribution/reference"
  10. "github.com/docker/docker/pkg/ioutils"
  11. "github.com/opencontainers/go-digest"
  12. "github.com/pkg/errors"
  13. )
  14. var (
  15. // ErrDoesNotExist is returned if a reference is not found in the
  16. // store.
  17. ErrDoesNotExist notFoundError = "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 reference 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.WithStack(invalidTagError("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.WithStack(invalidTagError("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 errors.WithStack(conflictingTagError("Cannot overwrite digest " + digested.Digest().String()))
  131. }
  132. if !force {
  133. return errors.WithStack(
  134. conflictingTagError(
  135. fmt.Sprintf("Conflict: Tag %s is already set to image %s, if you want to replace it, please use the force option", refStr, oldID.String()),
  136. ),
  137. )
  138. }
  139. if store.referencesByIDCache[oldID] != nil {
  140. delete(store.referencesByIDCache[oldID], refStr)
  141. if len(store.referencesByIDCache[oldID]) == 0 {
  142. delete(store.referencesByIDCache, oldID)
  143. }
  144. }
  145. }
  146. repository[refStr] = id
  147. if store.referencesByIDCache[id] == nil {
  148. store.referencesByIDCache[id] = make(map[string]reference.Named)
  149. }
  150. store.referencesByIDCache[id][refStr] = ref
  151. return store.save()
  152. }
  153. // Delete deletes a reference from the store. It returns true if a deletion
  154. // happened, or false otherwise.
  155. func (store *store) Delete(ref reference.Named) (bool, error) {
  156. ref, err := favorDigest(ref)
  157. if err != nil {
  158. return false, err
  159. }
  160. ref = reference.TagNameOnly(ref)
  161. refName := reference.FamiliarName(ref)
  162. refStr := reference.FamiliarString(ref)
  163. store.mu.Lock()
  164. defer store.mu.Unlock()
  165. repository, exists := store.Repositories[refName]
  166. if !exists {
  167. return false, ErrDoesNotExist
  168. }
  169. if id, exists := repository[refStr]; exists {
  170. delete(repository, refStr)
  171. if len(repository) == 0 {
  172. delete(store.Repositories, refName)
  173. }
  174. if store.referencesByIDCache[id] != nil {
  175. delete(store.referencesByIDCache[id], refStr)
  176. if len(store.referencesByIDCache[id]) == 0 {
  177. delete(store.referencesByIDCache, id)
  178. }
  179. }
  180. return true, store.save()
  181. }
  182. return false, ErrDoesNotExist
  183. }
  184. // Get retrieves an item from the store by reference
  185. func (store *store) Get(ref reference.Named) (digest.Digest, error) {
  186. if canonical, ok := ref.(reference.Canonical); ok {
  187. // If reference contains both tag and digest, only
  188. // lookup by digest as it takes precedence over
  189. // tag, until tag/digest combos are stored.
  190. if _, ok := ref.(reference.Tagged); ok {
  191. var err error
  192. ref, err = reference.WithDigest(reference.TrimNamed(canonical), canonical.Digest())
  193. if err != nil {
  194. return "", err
  195. }
  196. }
  197. } else {
  198. ref = reference.TagNameOnly(ref)
  199. }
  200. refName := reference.FamiliarName(ref)
  201. refStr := reference.FamiliarString(ref)
  202. store.mu.RLock()
  203. defer store.mu.RUnlock()
  204. repository, exists := store.Repositories[refName]
  205. if !exists || repository == nil {
  206. return "", ErrDoesNotExist
  207. }
  208. id, exists := repository[refStr]
  209. if !exists {
  210. return "", ErrDoesNotExist
  211. }
  212. return id, nil
  213. }
  214. // References returns a slice of references to the given ID. The slice
  215. // will be nil if there are no references to this ID.
  216. func (store *store) References(id digest.Digest) []reference.Named {
  217. store.mu.RLock()
  218. defer store.mu.RUnlock()
  219. // Convert the internal map to an array for two reasons:
  220. // 1) We must not return a mutable
  221. // 2) It would be ugly to expose the extraneous map keys to callers.
  222. var references []reference.Named
  223. for _, ref := range store.referencesByIDCache[id] {
  224. references = append(references, ref)
  225. }
  226. sort.Sort(lexicalRefs(references))
  227. return references
  228. }
  229. // ReferencesByName returns the references for a given repository name.
  230. // If there are no references known for this repository name,
  231. // ReferencesByName returns nil.
  232. func (store *store) ReferencesByName(ref reference.Named) []Association {
  233. refName := reference.FamiliarName(ref)
  234. store.mu.RLock()
  235. defer store.mu.RUnlock()
  236. repository, exists := store.Repositories[refName]
  237. if !exists {
  238. return nil
  239. }
  240. var associations []Association
  241. for refStr, refID := range repository {
  242. ref, err := reference.ParseNormalizedNamed(refStr)
  243. if err != nil {
  244. // Should never happen
  245. return nil
  246. }
  247. associations = append(associations,
  248. Association{
  249. Ref: ref,
  250. ID: refID,
  251. })
  252. }
  253. sort.Sort(lexicalAssociations(associations))
  254. return associations
  255. }
  256. func (store *store) save() error {
  257. // Store the json
  258. jsonData, err := json.Marshal(store)
  259. if err != nil {
  260. return err
  261. }
  262. return ioutils.AtomicWriteFile(store.jsonPath, jsonData, 0600)
  263. }
  264. func (store *store) reload() error {
  265. f, err := os.Open(store.jsonPath)
  266. if err != nil {
  267. return err
  268. }
  269. defer f.Close()
  270. if err := json.NewDecoder(f).Decode(&store); err != nil {
  271. return err
  272. }
  273. for _, repository := range store.Repositories {
  274. for refStr, refID := range repository {
  275. ref, err := reference.ParseNormalizedNamed(refStr)
  276. if err != nil {
  277. // Should never happen
  278. continue
  279. }
  280. if store.referencesByIDCache[refID] == nil {
  281. store.referencesByIDCache[refID] = make(map[string]reference.Named)
  282. }
  283. store.referencesByIDCache[refID][refStr] = ref
  284. }
  285. }
  286. return nil
  287. }