store_test.go 12 KB

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