store_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. package reference // import "github.com/docker/docker/reference"
  2. import (
  3. "bytes"
  4. "os"
  5. "path/filepath"
  6. "testing"
  7. "github.com/distribution/reference"
  8. "github.com/docker/docker/errdefs"
  9. "github.com/opencontainers/go-digest"
  10. "gotest.tools/v3/assert"
  11. is "gotest.tools/v3/assert/cmp"
  12. )
  13. var (
  14. saveLoadTestCases = map[string]digest.Digest{
  15. "registry:5000/foobar:HEAD": "sha256:470022b8af682154f57a2163d030eb369549549cba00edc69e1b99b46bb924d6",
  16. "registry:5000/foobar:alternate": "sha256:ae300ebc4a4f00693702cfb0a5e0b7bc527b353828dc86ad09fb95c8a681b793",
  17. "registry:5000/foobar:latest": "sha256:6153498b9ac00968d71b66cca4eac37e990b5f9eb50c26877eb8799c8847451b",
  18. "registry:5000/foobar:master": "sha256:6c9917af4c4e05001b346421959d7ea81b6dc9d25718466a37a6add865dfd7fc",
  19. "jess/hollywood:latest": "sha256:ae7a5519a0a55a2d4ef20ddcbd5d0ca0888a1f7ab806acc8e2a27baf46f529fe",
  20. "registry@sha256:367eb40fd0330a7e464777121e39d2f5b3e8e23a1e159342e53ab05c9e4d94e6": "sha256:24126a56805beb9711be5f4590cc2eb55ab8d4a85ebd618eed72bb19fc50631c",
  21. "busybox:latest": "sha256:91e54dfb11794fad694460162bf0cb0a4fa710cfa3f60979c177d920813e267c",
  22. }
  23. marshalledSaveLoadTestCases = []byte(`{"Repositories":{"busybox":{"busybox:latest":"sha256:91e54dfb11794fad694460162bf0cb0a4fa710cfa3f60979c177d920813e267c"},"jess/hollywood":{"jess/hollywood:latest":"sha256:ae7a5519a0a55a2d4ef20ddcbd5d0ca0888a1f7ab806acc8e2a27baf46f529fe"},"registry":{"registry@sha256:367eb40fd0330a7e464777121e39d2f5b3e8e23a1e159342e53ab05c9e4d94e6":"sha256:24126a56805beb9711be5f4590cc2eb55ab8d4a85ebd618eed72bb19fc50631c"},"registry:5000/foobar":{"registry:5000/foobar:HEAD":"sha256:470022b8af682154f57a2163d030eb369549549cba00edc69e1b99b46bb924d6","registry:5000/foobar:alternate":"sha256:ae300ebc4a4f00693702cfb0a5e0b7bc527b353828dc86ad09fb95c8a681b793","registry:5000/foobar:latest":"sha256:6153498b9ac00968d71b66cca4eac37e990b5f9eb50c26877eb8799c8847451b","registry:5000/foobar:master":"sha256:6c9917af4c4e05001b346421959d7ea81b6dc9d25718466a37a6add865dfd7fc"}}}`)
  24. )
  25. func TestLoad(t *testing.T) {
  26. jsonFile := filepath.Join(t.TempDir(), "repositories.json")
  27. err := os.WriteFile(jsonFile, marshalledSaveLoadTestCases, 0o666)
  28. assert.NilError(t, err)
  29. store, err := NewReferenceStore(jsonFile)
  30. if err != nil {
  31. t.Fatalf("error creating tag store: %v", err)
  32. }
  33. for refStr, expectedID := range saveLoadTestCases {
  34. ref, err := reference.ParseNormalizedNamed(refStr)
  35. if err != nil {
  36. t.Fatalf("failed to parse reference: %v", err)
  37. }
  38. id, err := store.Get(ref)
  39. if err != nil {
  40. t.Fatalf("could not find reference %s: %v", refStr, err)
  41. }
  42. if id != expectedID {
  43. t.Fatalf("expected %s - got %s", expectedID, id)
  44. }
  45. }
  46. }
  47. func TestSave(t *testing.T) {
  48. jsonFile := filepath.Join(t.TempDir(), "repositories.json")
  49. err := os.WriteFile(jsonFile, []byte(`{}`), 0o666)
  50. assert.NilError(t, err)
  51. store, err := NewReferenceStore(jsonFile)
  52. if err != nil {
  53. t.Fatalf("error creating tag store: %v", err)
  54. }
  55. for refStr, id := range saveLoadTestCases {
  56. ref, err := reference.ParseNormalizedNamed(refStr)
  57. if err != nil {
  58. t.Fatalf("failed to parse reference: %v", err)
  59. }
  60. if canonical, ok := ref.(reference.Canonical); ok {
  61. err = store.AddDigest(canonical, id, false)
  62. if err != nil {
  63. t.Fatalf("could not add digest reference %s: %v", refStr, err)
  64. }
  65. } else {
  66. err = store.AddTag(ref, id, false)
  67. if err != nil {
  68. t.Fatalf("could not add reference %s: %v", refStr, err)
  69. }
  70. }
  71. }
  72. jsonBytes, err := os.ReadFile(jsonFile)
  73. if err != nil {
  74. t.Fatalf("could not read json file: %v", err)
  75. }
  76. if !bytes.Equal(jsonBytes, marshalledSaveLoadTestCases) {
  77. t.Fatalf("save output did not match expectations\nexpected:\n%s\ngot:\n%s", marshalledSaveLoadTestCases, jsonBytes)
  78. }
  79. }
  80. func TestAddDeleteGet(t *testing.T) {
  81. jsonFile := filepath.Join(t.TempDir(), "repositories.json")
  82. err := os.WriteFile(jsonFile, []byte(`{}`), 0o666)
  83. assert.NilError(t, err)
  84. store, err := NewReferenceStore(jsonFile)
  85. if err != nil {
  86. t.Fatalf("error creating tag store: %v", err)
  87. }
  88. testImageID1 := digest.Digest("sha256:9655aef5fd742a1b4e1b7b163aa9f1c76c186304bf39102283d80927c916ca9c")
  89. testImageID2 := digest.Digest("sha256:9655aef5fd742a1b4e1b7b163aa9f1c76c186304bf39102283d80927c916ca9d")
  90. testImageID3 := digest.Digest("sha256:9655aef5fd742a1b4e1b7b163aa9f1c76c186304bf39102283d80927c916ca9e")
  91. // Try adding a reference with no tag or digest
  92. nameOnly, err := reference.ParseNormalizedNamed("username/repo")
  93. if err != nil {
  94. t.Fatalf("could not parse reference: %v", err)
  95. }
  96. if err = store.AddTag(nameOnly, testImageID1, false); err != nil {
  97. t.Fatalf("error adding to store: %v", err)
  98. }
  99. // Add a few references
  100. ref1, err := reference.ParseNormalizedNamed("username/repo1:latest")
  101. if err != nil {
  102. t.Fatalf("could not parse reference: %v", err)
  103. }
  104. if err = store.AddTag(ref1, testImageID1, false); err != nil {
  105. t.Fatalf("error adding to store: %v", err)
  106. }
  107. ref2, err := reference.ParseNormalizedNamed("username/repo1:old")
  108. if err != nil {
  109. t.Fatalf("could not parse reference: %v", err)
  110. }
  111. if err = store.AddTag(ref2, testImageID2, false); err != nil {
  112. t.Fatalf("error adding to store: %v", err)
  113. }
  114. ref3, err := reference.ParseNormalizedNamed("username/repo1:alias")
  115. if err != nil {
  116. t.Fatalf("could not parse reference: %v", err)
  117. }
  118. if err = store.AddTag(ref3, testImageID1, false); err != nil {
  119. t.Fatalf("error adding to store: %v", err)
  120. }
  121. ref4, err := reference.ParseNormalizedNamed("username/repo2:latest")
  122. if err != nil {
  123. t.Fatalf("could not parse reference: %v", err)
  124. }
  125. if err = store.AddTag(ref4, testImageID2, false); err != nil {
  126. t.Fatalf("error adding to store: %v", err)
  127. }
  128. // Write the same values again; should silently succeed
  129. if err = store.AddTag(ref4, testImageID2, false); err != nil {
  130. t.Fatalf("error redundantly adding to store: %v", err)
  131. }
  132. ref5, err := reference.ParseNormalizedNamed("username/repo3@sha256:58153dfb11794fad694460162bf0cb0a4fa710cfa3f60979c177d920813e267c")
  133. if err != nil {
  134. t.Fatalf("could not parse reference: %v", err)
  135. }
  136. if err = store.AddDigest(ref5.(reference.Canonical), testImageID2, false); err != nil {
  137. t.Fatalf("error adding to store: %v", err)
  138. }
  139. // Write the same values again; should silently succeed
  140. if err = store.AddDigest(ref5.(reference.Canonical), testImageID2, false); err != nil {
  141. t.Fatalf("error redundantly adding to store: %v", err)
  142. }
  143. err = store.AddDigest(ref5.(reference.Canonical), testImageID3, false)
  144. assert.Check(t, is.ErrorType(err, errdefs.IsConflict), "overwriting a digest with a different digest should fail")
  145. err = store.AddDigest(ref5.(reference.Canonical), testImageID3, true)
  146. assert.Check(t, is.ErrorType(err, errdefs.IsConflict), "overwriting a digest cannot be forced")
  147. // Attempt to overwrite with force == false
  148. err = store.AddTag(ref4, testImageID3, false)
  149. assert.Check(t, is.ErrorType(err, errdefs.IsConflict), "did not get expected error on overwrite attempt")
  150. // Repeat to overwrite with force == true
  151. if err = store.AddTag(ref4, testImageID3, true); err != nil {
  152. t.Fatalf("failed to force tag overwrite: %v", err)
  153. }
  154. // Check references so far
  155. id, err := store.Get(nameOnly)
  156. if err != nil {
  157. t.Fatalf("Get returned error: %v", err)
  158. }
  159. if id != testImageID1 {
  160. t.Fatalf("id mismatch: got %s instead of %s", id.String(), testImageID1.String())
  161. }
  162. id, err = store.Get(ref1)
  163. if err != nil {
  164. t.Fatalf("Get returned error: %v", err)
  165. }
  166. if id != testImageID1 {
  167. t.Fatalf("id mismatch: got %s instead of %s", id.String(), testImageID1.String())
  168. }
  169. id, err = store.Get(ref2)
  170. if err != nil {
  171. t.Fatalf("Get returned error: %v", err)
  172. }
  173. if id != testImageID2 {
  174. t.Fatalf("id mismatch: got %s instead of %s", id.String(), testImageID2.String())
  175. }
  176. id, err = store.Get(ref3)
  177. if err != nil {
  178. t.Fatalf("Get returned error: %v", err)
  179. }
  180. if id != testImageID1 {
  181. t.Fatalf("id mismatch: got %s instead of %s", id.String(), testImageID1.String())
  182. }
  183. id, err = store.Get(ref4)
  184. if err != nil {
  185. t.Fatalf("Get returned error: %v", err)
  186. }
  187. if id != testImageID3 {
  188. t.Fatalf("id mismatch: got %s instead of %s", id.String(), testImageID3.String())
  189. }
  190. id, err = store.Get(ref5)
  191. if err != nil {
  192. t.Fatalf("Get returned error: %v", err)
  193. }
  194. if id != testImageID2 {
  195. t.Fatalf("id mismatch: got %s instead of %s", id.String(), testImageID3.String())
  196. }
  197. // Get should return ErrDoesNotExist for a nonexistent repo
  198. nonExistRepo, err := reference.ParseNormalizedNamed("username/nonexistrepo:latest")
  199. if err != nil {
  200. t.Fatalf("could not parse reference: %v", err)
  201. }
  202. if _, err = store.Get(nonExistRepo); err != ErrDoesNotExist {
  203. t.Fatal("Expected ErrDoesNotExist from Get")
  204. }
  205. // Get should return ErrDoesNotExist for a nonexistent tag
  206. nonExistTag, err := reference.ParseNormalizedNamed("username/repo1:nonexist")
  207. if err != nil {
  208. t.Fatalf("could not parse reference: %v", err)
  209. }
  210. if _, err = store.Get(nonExistTag); err != ErrDoesNotExist {
  211. t.Fatal("Expected ErrDoesNotExist from Get")
  212. }
  213. // Check References
  214. refs := store.References(testImageID1)
  215. if len(refs) != 3 {
  216. t.Fatal("unexpected number of references")
  217. }
  218. // Looking for the references in this order verifies that they are
  219. // returned lexically sorted.
  220. if refs[0].String() != ref3.String() {
  221. t.Fatalf("unexpected reference: %v", refs[0].String())
  222. }
  223. if refs[1].String() != ref1.String() {
  224. t.Fatalf("unexpected reference: %v", refs[1].String())
  225. }
  226. if refs[2].String() != nameOnly.String()+":latest" {
  227. t.Fatalf("unexpected reference: %v", refs[2].String())
  228. }
  229. // Check ReferencesByName
  230. repoName, err := reference.ParseNormalizedNamed("username/repo1")
  231. if err != nil {
  232. t.Fatalf("could not parse reference: %v", err)
  233. }
  234. associations := store.ReferencesByName(repoName)
  235. if len(associations) != 3 {
  236. t.Fatal("unexpected number of associations")
  237. }
  238. // Looking for the associations in this order verifies that they are
  239. // returned lexically sorted.
  240. if associations[0].Ref.String() != ref3.String() {
  241. t.Fatalf("unexpected reference: %v", associations[0].Ref.String())
  242. }
  243. if associations[0].ID != testImageID1 {
  244. t.Fatalf("unexpected reference: %v", associations[0].Ref.String())
  245. }
  246. if associations[1].Ref.String() != ref1.String() {
  247. t.Fatalf("unexpected reference: %v", associations[1].Ref.String())
  248. }
  249. if associations[1].ID != testImageID1 {
  250. t.Fatalf("unexpected reference: %v", associations[1].Ref.String())
  251. }
  252. if associations[2].Ref.String() != ref2.String() {
  253. t.Fatalf("unexpected reference: %v", associations[2].Ref.String())
  254. }
  255. if associations[2].ID != testImageID2 {
  256. t.Fatalf("unexpected reference: %v", associations[2].Ref.String())
  257. }
  258. // Delete should return ErrDoesNotExist for a nonexistent repo
  259. if _, err = store.Delete(nonExistRepo); err != ErrDoesNotExist {
  260. t.Fatal("Expected ErrDoesNotExist from Delete")
  261. }
  262. // Delete should return ErrDoesNotExist for a nonexistent tag
  263. if _, err = store.Delete(nonExistTag); err != ErrDoesNotExist {
  264. t.Fatal("Expected ErrDoesNotExist from Delete")
  265. }
  266. // Delete a few references
  267. if deleted, err := store.Delete(ref1); err != nil || !deleted {
  268. t.Fatal("Delete failed")
  269. }
  270. if _, err := store.Get(ref1); err != ErrDoesNotExist {
  271. t.Fatal("Expected ErrDoesNotExist from Get")
  272. }
  273. if deleted, err := store.Delete(ref5); err != nil || !deleted {
  274. t.Fatal("Delete failed")
  275. }
  276. if _, err := store.Get(ref5); err != ErrDoesNotExist {
  277. t.Fatal("Expected ErrDoesNotExist from Get")
  278. }
  279. if deleted, err := store.Delete(nameOnly); err != nil || !deleted {
  280. t.Fatal("Delete failed")
  281. }
  282. if _, err := store.Get(nameOnly); err != ErrDoesNotExist {
  283. t.Fatal("Expected ErrDoesNotExist from Get")
  284. }
  285. }
  286. func TestInvalidTags(t *testing.T) {
  287. store, err := NewReferenceStore(filepath.Join(t.TempDir(), "repositories.json"))
  288. assert.NilError(t, err)
  289. id := digest.Digest("sha256:470022b8af682154f57a2163d030eb369549549cba00edc69e1b99b46bb924d6")
  290. // sha256 as repo name
  291. ref, err := reference.ParseNormalizedNamed("sha256:abc")
  292. assert.NilError(t, err)
  293. err = store.AddTag(ref, id, true)
  294. assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
  295. // setting digest as a tag
  296. ref, err = reference.ParseNormalizedNamed("registry@sha256:367eb40fd0330a7e464777121e39d2f5b3e8e23a1e159342e53ab05c9e4d94e6")
  297. assert.NilError(t, err)
  298. err = store.AddTag(ref, id, true)
  299. assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
  300. }