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