oras.land/oras-go/v2@v2.5.1-0.20240520045656-aef90e4d04c4/content/oci/oci_test.go (about) 1 /* 2 Copyright The ORAS Authors. 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 */ 15 16 package oci 17 18 import ( 19 "bytes" 20 "context" 21 _ "crypto/sha256" 22 "encoding/json" 23 "errors" 24 "fmt" 25 "io" 26 "os" 27 "path" 28 "path/filepath" 29 "reflect" 30 "strconv" 31 "strings" 32 "sync/atomic" 33 "testing" 34 35 "github.com/opencontainers/go-digest" 36 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 37 "golang.org/x/sync/errgroup" 38 "oras.land/oras-go/v2" 39 "oras.land/oras-go/v2/content" 40 "oras.land/oras-go/v2/content/memory" 41 "oras.land/oras-go/v2/errdef" 42 "oras.land/oras-go/v2/internal/cas" 43 "oras.land/oras-go/v2/internal/descriptor" 44 "oras.land/oras-go/v2/internal/spec" 45 "oras.land/oras-go/v2/registry" 46 ) 47 48 // storageTracker tracks storage API counts. 49 type storageTracker struct { 50 content.Storage 51 fetch int64 52 push int64 53 exists int64 54 } 55 56 func (t *storageTracker) Fetch(ctx context.Context, target ocispec.Descriptor) (io.ReadCloser, error) { 57 atomic.AddInt64(&t.fetch, 1) 58 return t.Storage.Fetch(ctx, target) 59 } 60 61 func (t *storageTracker) Push(ctx context.Context, expected ocispec.Descriptor, content io.Reader) error { 62 atomic.AddInt64(&t.push, 1) 63 return t.Storage.Push(ctx, expected, content) 64 } 65 66 func (t *storageTracker) Exists(ctx context.Context, target ocispec.Descriptor) (bool, error) { 67 atomic.AddInt64(&t.exists, 1) 68 return t.Storage.Exists(ctx, target) 69 } 70 71 func TestStoreInterface(t *testing.T) { 72 var store interface{} = &Store{} 73 if _, ok := store.(oras.GraphTarget); !ok { 74 t.Error("&Store{} does not conform oras.Target") 75 } 76 if _, ok := store.(registry.TagLister); !ok { 77 t.Error("&Store{} does not conform registry.TagLister") 78 } 79 } 80 81 func TestStore_Success(t *testing.T) { 82 blob := []byte("test") 83 blobDesc := content.NewDescriptorFromBytes("test", blob) 84 manifest := []byte(`{"layers":[]}`) 85 manifestDesc := content.NewDescriptorFromBytes(ocispec.MediaTypeImageManifest, manifest) 86 ref := "foobar" 87 88 tempDir := t.TempDir() 89 s, err := New(tempDir) 90 if err != nil { 91 t.Fatal("New() error =", err) 92 } 93 ctx := context.Background() 94 95 // validate layout 96 layoutFilePath := filepath.Join(tempDir, ocispec.ImageLayoutFile) 97 layoutFile, err := os.Open(layoutFilePath) 98 if err != nil { 99 t.Errorf("error opening layout file, error = %v", err) 100 } 101 defer layoutFile.Close() 102 103 var layout *ocispec.ImageLayout 104 err = json.NewDecoder(layoutFile).Decode(&layout) 105 if err != nil { 106 t.Fatal("error decoding layout, error =", err) 107 } 108 if want := ocispec.ImageLayoutVersion; layout.Version != want { 109 t.Errorf("layout.Version = %s, want %s", layout.Version, want) 110 } 111 112 // validate index.json 113 indexFilePath := filepath.Join(tempDir, "index.json") 114 indexFile, err := os.Open(indexFilePath) 115 if err != nil { 116 t.Errorf("error opening layout file, error = %v", err) 117 } 118 defer indexFile.Close() 119 120 var index *ocispec.Index 121 err = json.NewDecoder(indexFile).Decode(&index) 122 if err != nil { 123 t.Fatal("error decoding index.json, error =", err) 124 } 125 if want := 2; index.SchemaVersion != want { 126 t.Errorf("index.SchemaVersion = %v, want %v", index.SchemaVersion, want) 127 } 128 129 // test push blob 130 err = s.Push(ctx, blobDesc, bytes.NewReader(blob)) 131 if err != nil { 132 t.Fatal("Store.Push() error =", err) 133 } 134 internalResolver := s.tagResolver 135 if got, want := len(internalResolver.Map()), 0; got != want { 136 t.Errorf("resolver.Map() = %v, want %v", got, want) 137 } 138 139 // test push manifest 140 err = s.Push(ctx, manifestDesc, bytes.NewReader(manifest)) 141 if err != nil { 142 t.Fatal("Store.Push() error =", err) 143 } 144 if got, want := len(internalResolver.Map()), 1; got != want { 145 t.Errorf("resolver.Map() = %v, want %v", got, want) 146 } 147 148 // test resolving blob by digest 149 gotDesc, err := s.Resolve(ctx, blobDesc.Digest.String()) 150 if err != nil { 151 t.Fatal("Store.Resolve() error =", err) 152 } 153 if want := blobDesc; gotDesc.Size != want.Size || gotDesc.Digest != want.Digest { 154 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, blobDesc) 155 } 156 157 // test resolving manifest by digest 158 gotDesc, err = s.Resolve(ctx, manifestDesc.Digest.String()) 159 if err != nil { 160 t.Fatal("Store.Resolve() error =", err) 161 } 162 if !reflect.DeepEqual(gotDesc, manifestDesc) { 163 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, manifestDesc) 164 } 165 166 // test tag 167 err = s.Tag(ctx, manifestDesc, ref) 168 if err != nil { 169 t.Fatal("Store.Tag() error =", err) 170 } 171 if got, want := len(internalResolver.Map()), 2; got != want { 172 t.Errorf("resolver.Map() = %v, want %v", got, want) 173 } 174 175 // test resolving manifest by tag 176 gotDesc, err = s.Resolve(ctx, ref) 177 if err != nil { 178 t.Fatal("Store.Resolve() error =", err) 179 } 180 if !reflect.DeepEqual(gotDesc, manifestDesc) { 181 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, manifestDesc) 182 } 183 184 // test fetch 185 exists, err := s.Exists(ctx, manifestDesc) 186 if err != nil { 187 t.Fatal("Store.Exists() error =", err) 188 } 189 if !exists { 190 t.Errorf("Store.Exists() = %v, want %v", exists, true) 191 } 192 193 rc, err := s.Fetch(ctx, manifestDesc) 194 if err != nil { 195 t.Fatal("Store.Fetch() error =", err) 196 } 197 got, err := io.ReadAll(rc) 198 if err != nil { 199 t.Fatal("Store.Fetch().Read() error =", err) 200 } 201 err = rc.Close() 202 if err != nil { 203 t.Error("Store.Fetch().Close() error =", err) 204 } 205 if !bytes.Equal(got, manifest) { 206 t.Errorf("Store.Fetch() = %v, want %v", got, manifest) 207 } 208 } 209 210 func TestStore_RelativeRoot_Success(t *testing.T) { 211 blob := []byte("test") 212 blobDesc := content.NewDescriptorFromBytes("test", blob) 213 manifest := []byte(`{"layers":[]}`) 214 manifestDesc := content.NewDescriptorFromBytes(ocispec.MediaTypeImageManifest, manifest) 215 ref := "foobar" 216 217 tempDir, err := filepath.EvalSymlinks(t.TempDir()) 218 if err != nil { 219 t.Fatal("error calling filepath.EvalSymlinks(), error =", err) 220 } 221 currDir, err := os.Getwd() 222 if err != nil { 223 t.Fatal("error calling Getwd(), error=", err) 224 } 225 if err := os.Chdir(tempDir); err != nil { 226 t.Fatal("error calling Chdir(), error=", err) 227 } 228 s, err := New(".") 229 if err != nil { 230 t.Fatal("New() error =", err) 231 } 232 if want := tempDir; s.root != want { 233 t.Errorf("Store.root = %s, want %s", s.root, want) 234 } 235 // cd back to allow the temp directory to be removed 236 if err := os.Chdir(currDir); err != nil { 237 t.Fatal("error calling Chdir(), error=", err) 238 } 239 ctx := context.Background() 240 241 // validate layout 242 layoutFilePath := filepath.Join(tempDir, ocispec.ImageLayoutFile) 243 layoutFile, err := os.Open(layoutFilePath) 244 if err != nil { 245 t.Errorf("error opening layout file, error = %v", err) 246 } 247 defer layoutFile.Close() 248 249 var layout *ocispec.ImageLayout 250 err = json.NewDecoder(layoutFile).Decode(&layout) 251 if err != nil { 252 t.Fatal("error decoding layout, error =", err) 253 } 254 if want := ocispec.ImageLayoutVersion; layout.Version != want { 255 t.Errorf("layout.Version = %s, want %s", layout.Version, want) 256 } 257 258 // test push blob 259 err = s.Push(ctx, blobDesc, bytes.NewReader(blob)) 260 if err != nil { 261 t.Fatal("Store.Push() error =", err) 262 } 263 internalResolver := s.tagResolver 264 if got, want := len(internalResolver.Map()), 0; got != want { 265 t.Errorf("resolver.Map() = %v, want %v", got, want) 266 } 267 268 // test push manifest 269 err = s.Push(ctx, manifestDesc, bytes.NewReader(manifest)) 270 if err != nil { 271 t.Fatal("Store.Push() error =", err) 272 } 273 if got, want := len(internalResolver.Map()), 1; got != want { 274 t.Errorf("resolver.Map() = %v, want %v", got, want) 275 } 276 277 // test resolving blob by digest 278 gotDesc, err := s.Resolve(ctx, blobDesc.Digest.String()) 279 if err != nil { 280 t.Fatal("Store.Resolve() error =", err) 281 } 282 if want := blobDesc; gotDesc.Size != want.Size || gotDesc.Digest != want.Digest { 283 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, blobDesc) 284 } 285 286 // test resolving manifest by digest 287 gotDesc, err = s.Resolve(ctx, manifestDesc.Digest.String()) 288 if err != nil { 289 t.Fatal("Store.Resolve() error =", err) 290 } 291 if !reflect.DeepEqual(gotDesc, manifestDesc) { 292 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, manifestDesc) 293 } 294 295 // test tag 296 err = s.Tag(ctx, manifestDesc, ref) 297 if err != nil { 298 t.Fatal("Store.Tag() error =", err) 299 } 300 if got, want := len(internalResolver.Map()), 2; got != want { 301 t.Errorf("resolver.Map() = %v, want %v", got, want) 302 } 303 304 // test resolving manifest by tag 305 gotDesc, err = s.Resolve(ctx, ref) 306 if err != nil { 307 t.Fatal("Store.Resolve() error =", err) 308 } 309 if !reflect.DeepEqual(gotDesc, manifestDesc) { 310 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, manifestDesc) 311 } 312 313 // test fetch 314 exists, err := s.Exists(ctx, manifestDesc) 315 if err != nil { 316 t.Fatal("Store.Exists() error =", err) 317 } 318 if !exists { 319 t.Errorf("Store.Exists() = %v, want %v", exists, true) 320 } 321 322 rc, err := s.Fetch(ctx, manifestDesc) 323 if err != nil { 324 t.Fatal("Store.Fetch() error =", err) 325 } 326 got, err := io.ReadAll(rc) 327 if err != nil { 328 t.Fatal("Store.Fetch().Read() error =", err) 329 } 330 err = rc.Close() 331 if err != nil { 332 t.Error("Store.Fetch().Close() error =", err) 333 } 334 if !bytes.Equal(got, manifest) { 335 t.Errorf("Store.Fetch() = %v, want %v", got, manifest) 336 } 337 } 338 339 func TestStore_NotExistingRoot(t *testing.T) { 340 tempDir := t.TempDir() 341 root := filepath.Join(tempDir, "rootDir") 342 _, err := New(root) 343 if err != nil { 344 t.Fatal("New() error =", err) 345 } 346 347 // validate layout 348 layoutFilePath := filepath.Join(root, ocispec.ImageLayoutFile) 349 layoutFile, err := os.Open(layoutFilePath) 350 if err != nil { 351 t.Errorf("error opening layout file, error = %v", err) 352 } 353 defer layoutFile.Close() 354 355 var layout *ocispec.ImageLayout 356 err = json.NewDecoder(layoutFile).Decode(&layout) 357 if err != nil { 358 t.Fatal("error decoding layout, error =", err) 359 } 360 if want := ocispec.ImageLayoutVersion; layout.Version != want { 361 t.Errorf("layout.Version = %s, want %s", layout.Version, want) 362 } 363 364 // validate index.json 365 indexFilePath := filepath.Join(root, "index.json") 366 indexFile, err := os.Open(indexFilePath) 367 if err != nil { 368 t.Errorf("error opening layout file, error = %v", err) 369 } 370 defer indexFile.Close() 371 372 var index *ocispec.Index 373 err = json.NewDecoder(indexFile).Decode(&index) 374 if err != nil { 375 t.Fatal("error decoding index.json, error =", err) 376 } 377 if want := 2; index.SchemaVersion != want { 378 t.Errorf("index.SchemaVersion = %v, want %v", index.SchemaVersion, want) 379 } 380 } 381 382 func TestStore_ContentNotFound(t *testing.T) { 383 content := []byte("hello world") 384 desc := ocispec.Descriptor{ 385 MediaType: "test", 386 Digest: digest.FromBytes(content), 387 Size: int64(len(content)), 388 } 389 390 tempDir := t.TempDir() 391 s, err := New(tempDir) 392 if err != nil { 393 t.Fatal("New() error =", err) 394 } 395 ctx := context.Background() 396 397 exists, err := s.Exists(ctx, desc) 398 if err != nil { 399 t.Error("Store.Exists() error =", err) 400 } 401 if exists { 402 t.Errorf("Store.Exists() = %v, want %v", exists, false) 403 } 404 405 _, err = s.Fetch(ctx, desc) 406 if !errors.Is(err, errdef.ErrNotFound) { 407 t.Errorf("Store.Fetch() error = %v, want %v", err, errdef.ErrNotFound) 408 } 409 } 410 411 func TestStore_ContentAlreadyExists(t *testing.T) { 412 content := []byte("hello world") 413 desc := ocispec.Descriptor{ 414 MediaType: "test", 415 Digest: digest.FromBytes(content), 416 Size: int64(len(content)), 417 } 418 419 tempDir := t.TempDir() 420 s, err := New(tempDir) 421 if err != nil { 422 t.Fatal("New() error =", err) 423 } 424 ctx := context.Background() 425 426 err = s.Push(ctx, desc, bytes.NewReader(content)) 427 if err != nil { 428 t.Fatal("Store.Push() error =", err) 429 } 430 431 err = s.Push(ctx, desc, bytes.NewReader(content)) 432 if !errors.Is(err, errdef.ErrAlreadyExists) { 433 t.Errorf("Store.Push() error = %v, want %v", err, errdef.ErrAlreadyExists) 434 } 435 } 436 437 func TestStore_ContentBadPush(t *testing.T) { 438 content := []byte("hello world") 439 desc := ocispec.Descriptor{ 440 MediaType: "test", 441 Digest: digest.FromBytes(content), 442 Size: int64(len(content)), 443 } 444 445 tempDir := t.TempDir() 446 s, err := New(tempDir) 447 if err != nil { 448 t.Fatal("New() error =", err) 449 } 450 ctx := context.Background() 451 452 err = s.Push(ctx, desc, strings.NewReader("foobar")) 453 if err == nil { 454 t.Errorf("Store.Push() error = %v, wantErr %v", err, true) 455 } 456 } 457 458 func TestStore_ResolveByTagReturnsFullDescriptor(t *testing.T) { 459 content := []byte("hello world") 460 ref := "hello-world:0.0.1" 461 annotations := map[string]string{"name": "Hello"} 462 desc := ocispec.Descriptor{ 463 MediaType: "test", 464 Digest: digest.FromBytes(content), 465 Size: int64(len(content)), 466 Annotations: annotations, 467 } 468 469 tempDir := t.TempDir() 470 s, err := New(tempDir) 471 if err != nil { 472 t.Fatal("New() error =", err) 473 } 474 ctx := context.Background() 475 476 err = s.Push(ctx, desc, bytes.NewReader(content)) 477 if err != nil { 478 t.Errorf("Store.Push() error = %v, wantErr %v", err, false) 479 } 480 481 err = s.Tag(ctx, desc, ref) 482 if err != nil { 483 t.Errorf("error tagging descriptor error = %v, wantErr %v", err, false) 484 } 485 486 resolvedDescr, err := s.Resolve(ctx, ref) 487 if err != nil { 488 t.Errorf("error resolving descriptor error = %v, wantErr %v", err, false) 489 } 490 491 if !reflect.DeepEqual(resolvedDescr, desc) { 492 t.Errorf("Store.Resolve() = %v, want %v", resolvedDescr, desc) 493 } 494 } 495 496 func TestStore_ResolveByDigestReturnsPlainDescriptor(t *testing.T) { 497 content := []byte("hello world") 498 ref := "hello-world:0.0.1" 499 desc := ocispec.Descriptor{ 500 MediaType: "test", 501 Digest: digest.FromBytes(content), 502 Size: int64(len(content)), 503 Annotations: map[string]string{"name": "Hello"}, 504 } 505 plainDescriptor := descriptor.Plain(desc) 506 507 tempDir := t.TempDir() 508 s, err := New(tempDir) 509 if err != nil { 510 t.Fatal("New() error =", err) 511 } 512 ctx := context.Background() 513 514 err = s.Push(ctx, desc, bytes.NewReader(content)) 515 if err != nil { 516 t.Errorf("Store.Push() error = %v, wantErr %v", err, false) 517 } 518 519 err = s.Tag(ctx, desc, ref) 520 if err != nil { 521 t.Errorf("error tagging descriptor error = %v, wantErr %v", err, false) 522 } 523 524 resolvedDescr, err := s.Resolve(ctx, string(desc.Digest)) 525 if err != nil { 526 t.Errorf("error resolving descriptor error = %v, wantErr %v", err, false) 527 } 528 529 if !reflect.DeepEqual(resolvedDescr, plainDescriptor) { 530 t.Errorf("Store.Resolve() = %v, want %v", resolvedDescr, plainDescriptor) 531 } 532 } 533 534 func TestStore_TagNotFound(t *testing.T) { 535 ref := "foobar" 536 537 tempDir := t.TempDir() 538 s, err := New(tempDir) 539 if err != nil { 540 t.Fatal("New() error =", err) 541 } 542 ctx := context.Background() 543 544 _, err = s.Resolve(ctx, ref) 545 if !errors.Is(err, errdef.ErrNotFound) { 546 t.Errorf("Store.Resolve() error = %v, want %v", err, errdef.ErrNotFound) 547 } 548 } 549 550 func TestStore_TagUnknownContent(t *testing.T) { 551 content := []byte("hello world") 552 desc := ocispec.Descriptor{ 553 MediaType: "test", 554 Digest: digest.FromBytes(content), 555 Size: int64(len(content)), 556 } 557 ref := "foobar" 558 559 tempDir := t.TempDir() 560 s, err := New(tempDir) 561 if err != nil { 562 t.Fatal("New() error =", err) 563 } 564 ctx := context.Background() 565 566 err = s.Tag(ctx, desc, ref) 567 if !errors.Is(err, errdef.ErrNotFound) { 568 t.Errorf("Store.Resolve() error = %v, want %v", err, errdef.ErrNotFound) 569 } 570 } 571 572 func TestStore_DisableAutoSaveIndex(t *testing.T) { 573 content := []byte(`{"layers":[]}`) 574 desc := ocispec.Descriptor{ 575 MediaType: ocispec.MediaTypeImageManifest, 576 Digest: digest.FromBytes(content), 577 Size: int64(len(content)), 578 } 579 ref0 := "foobar" 580 ref1 := "barfoo" 581 582 tempDir := t.TempDir() 583 s, err := New(tempDir) 584 if err != nil { 585 t.Fatal("New() error =", err) 586 } 587 // disable auto save 588 s.AutoSaveIndex = false 589 ctx := context.Background() 590 591 // validate layout 592 layoutFilePath := filepath.Join(tempDir, ocispec.ImageLayoutFile) 593 layoutFile, err := os.Open(layoutFilePath) 594 if err != nil { 595 t.Errorf("error opening layout file, error = %v", err) 596 } 597 defer layoutFile.Close() 598 599 var layout *ocispec.ImageLayout 600 err = json.NewDecoder(layoutFile).Decode(&layout) 601 if err != nil { 602 t.Fatal("error decoding layout, error =", err) 603 } 604 if want := ocispec.ImageLayoutVersion; layout.Version != want { 605 t.Errorf("layout.Version = %s, want %s", layout.Version, want) 606 } 607 608 // test push 609 err = s.Push(ctx, desc, bytes.NewReader(content)) 610 if err != nil { 611 t.Fatal("Store.Push() error =", err) 612 } 613 internalResolver := s.tagResolver 614 if got, want := len(internalResolver.Map()), 1; got != want { 615 t.Errorf("resolver.Map() = %v, want %v", got, want) 616 } 617 618 // test resolving by digest 619 gotDesc, err := s.Resolve(ctx, desc.Digest.String()) 620 if err != nil { 621 t.Fatal("Store.Resolve() error =", err) 622 } 623 if !reflect.DeepEqual(gotDesc, desc) { 624 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, desc) 625 } 626 627 // test tag 628 err = s.Tag(ctx, desc, ref0) 629 if err != nil { 630 t.Fatal("Store.Tag() error =", err) 631 } 632 err = s.Tag(ctx, desc, ref1) 633 if err != nil { 634 t.Fatal("Store.Tag() error =", err) 635 } 636 if got, want := len(internalResolver.Map()), 3; got != want { 637 t.Errorf("resolver.Map() = %v, want %v", got, want) 638 } 639 640 // test resolving by digest 641 gotDesc, err = s.Resolve(ctx, ref0) 642 if err != nil { 643 t.Fatal("Store.Resolve() error =", err) 644 } 645 if !reflect.DeepEqual(gotDesc, desc) { 646 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, desc) 647 } 648 649 // test index file 650 if got, want := len(s.index.Manifests), 0; got != want { 651 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 652 } 653 if err := s.saveIndex(); err != nil { 654 t.Fatal("Store.SaveIndex() error =", err) 655 } 656 // test index file again 657 if got, want := len(s.index.Manifests), 2; got != want { 658 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 659 } 660 if _, err := os.Stat(s.indexPath); err != nil { 661 t.Errorf("error: %s does not exist", s.indexPath) 662 } 663 664 // test untag 665 err = s.Untag(ctx, ref0) 666 if err != nil { 667 t.Fatal("Store.Untag() error =", err) 668 } 669 if got, want := len(internalResolver.Map()), 2; got != want { 670 t.Errorf("resolver.Map() = %v, want %v", got, want) 671 } 672 if got, want := len(s.index.Manifests), 2; got != want { 673 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 674 } 675 if err := s.saveIndex(); err != nil { 676 t.Fatal("Store.SaveIndex() error =", err) 677 } 678 // test index file again 679 if got, want := len(s.index.Manifests), 1; got != want { 680 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 681 } 682 } 683 684 func TestStore_RepeatTag(t *testing.T) { 685 tempDir := t.TempDir() 686 s, err := New(tempDir) 687 if err != nil { 688 t.Fatal("New() error =", err) 689 } 690 ctx := context.Background() 691 ref := "foobar" 692 693 // get internal resolver 694 internalResolver := s.tagResolver 695 696 // first tag a manifest 697 manifest := []byte(`{"layers":[]}`) 698 desc := content.NewDescriptorFromBytes(ocispec.MediaTypeImageManifest, manifest) 699 err = s.Push(ctx, desc, bytes.NewReader(manifest)) 700 if err != nil { 701 t.Fatal("Store.Push() error =", err) 702 } 703 if got, want := len(internalResolver.Map()), 1; got != want { 704 t.Errorf("len(resolver.Map()) = %v, want %v", got, want) 705 } 706 if got, want := len(s.index.Manifests), 1; got != want { 707 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 708 } 709 710 err = s.Tag(ctx, desc, ref) 711 if err != nil { 712 t.Fatal("Store.Tag() error =", err) 713 } 714 if got, want := len(internalResolver.Map()), 2; got != want { 715 t.Errorf("resolver.Map() = %v, want %v", got, want) 716 } 717 if got, want := len(s.index.Manifests), 1; got != want { 718 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 719 } 720 721 gotDesc, err := s.Resolve(ctx, desc.Digest.String()) 722 if err != nil { 723 t.Fatal("Store.Resolve() error =", err) 724 } 725 if !reflect.DeepEqual(gotDesc, desc) { 726 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, desc) 727 } 728 729 gotDesc, err = s.Resolve(ctx, ref) 730 if err != nil { 731 t.Fatal("Store.Resolve() error =", err) 732 } 733 if !reflect.DeepEqual(gotDesc, desc) { 734 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, desc) 735 } 736 737 // tag another manifest 738 manifest = []byte(`{"layers":[], "annotations":{}}`) 739 desc = content.NewDescriptorFromBytes(ocispec.MediaTypeImageManifest, manifest) 740 err = s.Push(ctx, desc, bytes.NewReader(manifest)) 741 if err != nil { 742 t.Fatal("Store.Push() error =", err) 743 } 744 if got, want := len(internalResolver.Map()), 3; got != want { 745 t.Errorf("resolver.Map() = %v, want %v", got, want) 746 } 747 if got, want := len(s.index.Manifests), 2; got != want { 748 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 749 } 750 751 err = s.Tag(ctx, desc, ref) 752 if err != nil { 753 t.Fatal("Store.Tag() error =", err) 754 } 755 if got, want := len(internalResolver.Map()), 3; got != want { 756 t.Errorf("resolver.Map() = %v, want %v", got, want) 757 } 758 if got, want := len(s.index.Manifests), 2; got != want { 759 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 760 } 761 762 gotDesc, err = s.Resolve(ctx, desc.Digest.String()) 763 if err != nil { 764 t.Fatal("Store.Resolve() error =", err) 765 } 766 if !reflect.DeepEqual(gotDesc, desc) { 767 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, desc) 768 } 769 770 gotDesc, err = s.Resolve(ctx, ref) 771 if err != nil { 772 t.Fatal("Store.Resolve() error =", err) 773 } 774 if !reflect.DeepEqual(gotDesc, desc) { 775 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, desc) 776 } 777 778 // tag a blob 779 blob := []byte("foobar") 780 desc = content.NewDescriptorFromBytes("test", blob) 781 err = s.Push(ctx, desc, bytes.NewReader(blob)) 782 if err != nil { 783 t.Fatal("Store.Push() error =", err) 784 } 785 if got, want := len(internalResolver.Map()), 3; got != want { 786 t.Errorf("resolver.Map() = %v, want %v", got, want) 787 } 788 if got, want := len(s.index.Manifests), 2; got != want { 789 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 790 } 791 792 err = s.Tag(ctx, desc, ref) 793 if err != nil { 794 t.Fatal("Store.Tag() error =", err) 795 } 796 if got, want := len(internalResolver.Map()), 4; got != want { 797 t.Errorf("resolver.Map() = %v, want %v", got, want) 798 } 799 if got, want := len(s.index.Manifests), 3; got != want { 800 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 801 } 802 803 gotDesc, err = s.Resolve(ctx, desc.Digest.String()) 804 if err != nil { 805 t.Fatal("Store.Resolve() error =", err) 806 } 807 if !reflect.DeepEqual(gotDesc, desc) { 808 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, desc) 809 } 810 811 gotDesc, err = s.Resolve(ctx, ref) 812 if err != nil { 813 t.Fatal("Store.Resolve() error =", err) 814 } 815 if !reflect.DeepEqual(gotDesc, desc) { 816 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, desc) 817 } 818 819 // tag another blob 820 blob = []byte("barfoo") 821 desc = content.NewDescriptorFromBytes("test", blob) 822 err = s.Push(ctx, desc, bytes.NewReader(blob)) 823 if err != nil { 824 t.Fatal("Store.Push() error =", err) 825 } 826 if got, want := len(internalResolver.Map()), 4; got != want { 827 t.Errorf("resolver.Map() = %v, want %v", got, want) 828 } 829 if got, want := len(s.index.Manifests), 3; got != want { 830 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 831 } 832 833 err = s.Tag(ctx, desc, ref) 834 if err != nil { 835 t.Fatal("Store.Tag() error =", err) 836 } 837 if got, want := len(internalResolver.Map()), 5; got != want { 838 t.Errorf("resolver.Map() = %v, want %v", got, want) 839 } 840 if got, want := len(s.index.Manifests), 4; got != want { 841 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 842 } 843 844 gotDesc, err = s.Resolve(ctx, desc.Digest.String()) 845 if err != nil { 846 t.Fatal("Store.Resolve() error =", err) 847 } 848 if !reflect.DeepEqual(gotDesc, desc) { 849 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, desc) 850 } 851 852 gotDesc, err = s.Resolve(ctx, ref) 853 if err != nil { 854 t.Fatal("Store.Resolve() error =", err) 855 } 856 if !reflect.DeepEqual(gotDesc, desc) { 857 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, desc) 858 } 859 } 860 861 // Related bug: https://github.com/oras-project/oras-go/issues/461 862 func TestStore_TagByDigest(t *testing.T) { 863 tempDir := t.TempDir() 864 s, err := New(tempDir) 865 if err != nil { 866 t.Fatal("New() error =", err) 867 } 868 ctx := context.Background() 869 870 // get internal resolver 871 internalResolver := s.tagResolver 872 873 manifest := []byte(`{"layers":[]}`) 874 manifestDesc := content.NewDescriptorFromBytes(ocispec.MediaTypeImageManifest, manifest) 875 876 // push a manifest 877 err = s.Push(ctx, manifestDesc, bytes.NewReader(manifest)) 878 if err != nil { 879 t.Fatal("Store.Push() error =", err) 880 } 881 if got, want := len(internalResolver.Map()), 1; got != want { 882 t.Errorf("len(resolver.Map()) = %v, want %v", got, want) 883 } 884 if got, want := len(s.index.Manifests), 1; got != want { 885 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 886 } 887 gotDesc, err := s.Resolve(ctx, manifestDesc.Digest.String()) 888 if err != nil { 889 t.Fatal("Store.Resolve() error =", err) 890 } 891 if !reflect.DeepEqual(gotDesc, manifestDesc) { 892 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, manifestDesc) 893 } 894 895 // tag manifest by digest 896 err = s.Tag(ctx, manifestDesc, manifestDesc.Digest.String()) 897 if err != nil { 898 t.Fatal("Store.Tag() error =", err) 899 } 900 if got, want := len(internalResolver.Map()), 1; got != want { 901 t.Errorf("len(resolver.Map()) = %v, want %v", got, want) 902 } 903 if got, want := len(s.index.Manifests), 1; got != want { 904 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 905 } 906 gotDesc, err = s.Resolve(ctx, manifestDesc.Digest.String()) 907 if err != nil { 908 t.Fatal("Store.Resolve() error =", err) 909 } 910 if !reflect.DeepEqual(gotDesc, manifestDesc) { 911 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, manifestDesc) 912 } 913 914 // push a blob 915 blob := []byte("foobar") 916 blobDesc := content.NewDescriptorFromBytes("test", blob) 917 err = s.Push(ctx, blobDesc, bytes.NewReader(blob)) 918 if err != nil { 919 t.Fatal("Store.Push() error =", err) 920 } 921 if got, want := len(internalResolver.Map()), 1; got != want { 922 t.Errorf("resolver.Map() = %v, want %v", got, want) 923 } 924 if got, want := len(s.index.Manifests), 1; got != want { 925 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 926 } 927 gotDesc, err = s.Resolve(ctx, blobDesc.Digest.String()) 928 if err != nil { 929 t.Fatal("Store.Resolve() error =", err) 930 } 931 if gotDesc.Digest != blobDesc.Digest || gotDesc.Size != blobDesc.Size { 932 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, blobDesc) 933 } 934 935 // tag blob by digest 936 err = s.Tag(ctx, blobDesc, blobDesc.Digest.String()) 937 if err != nil { 938 t.Fatal("Store.Tag() error =", err) 939 } 940 if got, want := len(internalResolver.Map()), 2; got != want { 941 t.Errorf("resolver.Map() = %v, want %v", got, want) 942 } 943 if got, want := len(s.index.Manifests), 2; got != want { 944 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 945 } 946 gotDesc, err = s.Resolve(ctx, blobDesc.Digest.String()) 947 if err != nil { 948 t.Fatal("Store.Resolve() error =", err) 949 } 950 if !reflect.DeepEqual(gotDesc, blobDesc) { 951 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, blobDesc) 952 } 953 } 954 955 func TestStore_BadIndex(t *testing.T) { 956 tempDir := t.TempDir() 957 content := []byte("whatever") 958 path := filepath.Join(tempDir, "index.json") 959 os.WriteFile(path, content, 0666) 960 961 _, err := New(tempDir) 962 if err == nil { 963 t.Errorf("New() error = nil, want: error") 964 } 965 } 966 967 func TestStore_BadLayout(t *testing.T) { 968 tempDir := t.TempDir() 969 content := []byte("whatever") 970 path := filepath.Join(tempDir, ocispec.ImageLayoutFile) 971 os.WriteFile(path, content, 0666) 972 973 _, err := New(tempDir) 974 if err == nil { 975 t.Errorf("New() error = nil, want: error") 976 } 977 } 978 979 func TestStore_Predecessors(t *testing.T) { 980 tempDir := t.TempDir() 981 s, err := New(tempDir) 982 if err != nil { 983 t.Fatal("New() error =", err) 984 } 985 ctx := context.Background() 986 987 // generate test content 988 var blobs [][]byte 989 var descs []ocispec.Descriptor 990 appendBlob := func(mediaType string, blob []byte) { 991 blobs = append(blobs, blob) 992 descs = append(descs, ocispec.Descriptor{ 993 MediaType: mediaType, 994 Digest: digest.FromBytes(blob), 995 Size: int64(len(blob)), 996 }) 997 } 998 generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) { 999 manifest := ocispec.Manifest{ 1000 Config: config, 1001 Layers: layers, 1002 } 1003 manifestJSON, err := json.Marshal(manifest) 1004 if err != nil { 1005 t.Fatal(err) 1006 } 1007 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 1008 } 1009 generateIndex := func(manifests ...ocispec.Descriptor) { 1010 index := ocispec.Index{ 1011 Manifests: manifests, 1012 } 1013 indexJSON, err := json.Marshal(index) 1014 if err != nil { 1015 t.Fatal(err) 1016 } 1017 appendBlob(ocispec.MediaTypeImageIndex, indexJSON) 1018 } 1019 generateArtifactManifest := func(subject ocispec.Descriptor, blobs ...ocispec.Descriptor) { 1020 var manifest spec.Artifact 1021 manifest.Subject = &subject 1022 manifest.Blobs = append(manifest.Blobs, blobs...) 1023 manifestJSON, err := json.Marshal(manifest) 1024 if err != nil { 1025 t.Fatal(err) 1026 } 1027 appendBlob(spec.MediaTypeArtifactManifest, manifestJSON) 1028 } 1029 1030 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 1031 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1 1032 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2 1033 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello")) // Blob 3 1034 generateManifest(descs[0], descs[1:3]...) // Blob 4 1035 generateManifest(descs[0], descs[3]) // Blob 5 1036 generateManifest(descs[0], descs[1:4]...) // Blob 6 1037 generateIndex(descs[4:6]...) // Blob 7 1038 generateIndex(descs[6]) // Blob 8 1039 generateIndex() // Blob 9 1040 generateIndex(descs[7:10]...) // Blob 10 1041 appendBlob(ocispec.MediaTypeImageLayer, []byte("sig_1")) // Blob 11 1042 generateArtifactManifest(descs[6], descs[11]) // Blob 12 1043 appendBlob(ocispec.MediaTypeImageLayer, []byte("sig_2")) // Blob 13 1044 generateArtifactManifest(descs[10], descs[13]) // Blob 14 1045 1046 eg, egCtx := errgroup.WithContext(ctx) 1047 for i := range blobs { 1048 eg.Go(func(i int) func() error { 1049 return func() error { 1050 err := s.Push(egCtx, descs[i], bytes.NewReader(blobs[i])) 1051 if err != nil { 1052 return fmt.Errorf("failed to push test content to src: %d: %v", i, err) 1053 } 1054 return nil 1055 } 1056 }(i)) 1057 } 1058 if err := eg.Wait(); err != nil { 1059 t.Fatal(err) 1060 } 1061 1062 // verify predecessors 1063 wants := [][]ocispec.Descriptor{ 1064 descs[4:7], // Blob 0 1065 {descs[4], descs[6]}, // Blob 1 1066 {descs[4], descs[6]}, // Blob 2 1067 {descs[5], descs[6]}, // Blob 3 1068 {descs[7]}, // Blob 4 1069 {descs[7]}, // Blob 5 1070 {descs[8], descs[12]}, // Blob 6 1071 {descs[10]}, // Blob 7 1072 {descs[10]}, // Blob 8 1073 {descs[10]}, // Blob 9 1074 {descs[14]}, // Blob 10 1075 {descs[12]}, // Blob 11 1076 nil, // Blob 12 1077 {descs[14]}, // Blob 13 1078 nil, // Blob 14 1079 } 1080 for i, want := range wants { 1081 predecessors, err := s.Predecessors(ctx, descs[i]) 1082 if err != nil { 1083 t.Errorf("Store.Predecessors(%d) error = %v", i, err) 1084 } 1085 if !equalDescriptorSet(predecessors, want) { 1086 t.Errorf("Store.Predecessors(%d) = %v, want %v", i, predecessors, want) 1087 } 1088 } 1089 } 1090 1091 func TestStore_ExistingStore(t *testing.T) { 1092 tempDir := t.TempDir() 1093 s, err := New(tempDir) 1094 if err != nil { 1095 t.Fatal("New() error =", err) 1096 } 1097 1098 // generate test content 1099 var blobs [][]byte 1100 var descs []ocispec.Descriptor 1101 appendBlob := func(mediaType string, blob []byte) { 1102 blobs = append(blobs, blob) 1103 descs = append(descs, ocispec.Descriptor{ 1104 MediaType: mediaType, 1105 Digest: digest.FromBytes(blob), 1106 Size: int64(len(blob)), 1107 }) 1108 } 1109 generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) { 1110 manifest := ocispec.Manifest{ 1111 Config: config, 1112 Layers: layers, 1113 } 1114 manifestJSON, err := json.Marshal(manifest) 1115 if err != nil { 1116 t.Fatal(err) 1117 } 1118 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 1119 } 1120 generateIndex := func(manifests ...ocispec.Descriptor) { 1121 index := ocispec.Index{ 1122 Manifests: manifests, 1123 } 1124 indexJSON, err := json.Marshal(index) 1125 if err != nil { 1126 t.Fatal(err) 1127 } 1128 appendBlob(ocispec.MediaTypeImageIndex, indexJSON) 1129 } 1130 1131 generateArtifactManifest := func(subject ocispec.Descriptor, blobs ...ocispec.Descriptor) { 1132 var manifest spec.Artifact 1133 manifest.Subject = &subject 1134 manifest.Blobs = append(manifest.Blobs, blobs...) 1135 manifestJSON, err := json.Marshal(manifest) 1136 if err != nil { 1137 t.Fatal(err) 1138 } 1139 appendBlob(spec.MediaTypeArtifactManifest, manifestJSON) 1140 } 1141 1142 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 1143 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1 1144 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2 1145 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello")) // Blob 3 1146 generateManifest(descs[0], descs[1:3]...) // Blob 4 1147 generateManifest(descs[0], descs[3]) // Blob 5 1148 generateManifest(descs[0], descs[1:4]...) // Blob 6 1149 generateIndex(descs[4:6]...) // Blob 7 1150 generateIndex(descs[6]) // Blob 8 1151 generateIndex() // Blob 9 1152 generateIndex(descs[7:10]...) // Blob 10 1153 appendBlob(ocispec.MediaTypeImageLayer, []byte("sig_1")) // Blob 11 1154 generateArtifactManifest(descs[6], descs[11]) // Blob 12 1155 appendBlob(ocispec.MediaTypeImageLayer, []byte("sig_2")) // Blob 13 1156 generateArtifactManifest(descs[10], descs[13]) // Blob 14 1157 1158 ctx := context.Background() 1159 eg, egCtx := errgroup.WithContext(ctx) 1160 for i := range blobs { 1161 eg.Go(func(i int) func() error { 1162 return func() error { 1163 err := s.Push(egCtx, descs[i], bytes.NewReader(blobs[i])) 1164 if err != nil { 1165 return fmt.Errorf("failed to push test content to src: %d: %v", i, err) 1166 } 1167 return nil 1168 } 1169 }(i)) 1170 } 1171 if err := eg.Wait(); err != nil { 1172 t.Fatal(err) 1173 } 1174 1175 // tag index root 1176 indexRoot := descs[10] 1177 tag := "latest" 1178 if err := s.Tag(ctx, indexRoot, tag); err != nil { 1179 t.Fatal("Tag() error =", err) 1180 } 1181 // tag index root by digest 1182 // related bug: https://github.com/oras-project/oras-go/issues/461 1183 if err := s.Tag(ctx, indexRoot, indexRoot.Digest.String()); err != nil { 1184 t.Fatal("Tag() error =", err) 1185 } 1186 1187 // test with another OCI store instance to mock loading from an existing store 1188 anotherS, err := New(tempDir) 1189 if err != nil { 1190 t.Fatal("New() error =", err) 1191 } 1192 1193 // test resolving index root by tag 1194 gotDesc, err := anotherS.Resolve(ctx, tag) 1195 if err != nil { 1196 t.Fatal("Store: Resolve() error =", err) 1197 } 1198 if !content.Equal(gotDesc, indexRoot) { 1199 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, indexRoot) 1200 } 1201 1202 // test resolving index root by digest 1203 gotDesc, err = anotherS.Resolve(ctx, indexRoot.Digest.String()) 1204 if err != nil { 1205 t.Fatal("Store: Resolve() error =", err) 1206 } 1207 if !reflect.DeepEqual(gotDesc, indexRoot) { 1208 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, indexRoot) 1209 } 1210 1211 // test resolving artifact manifest by digest 1212 artifactRootDesc := descs[12] 1213 gotDesc, err = anotherS.Resolve(ctx, artifactRootDesc.Digest.String()) 1214 if err != nil { 1215 t.Fatal("Store: Resolve() error =", err) 1216 } 1217 if !reflect.DeepEqual(gotDesc, artifactRootDesc) { 1218 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, artifactRootDesc) 1219 } 1220 1221 // test resolving blob by digest 1222 gotDesc, err = anotherS.Resolve(ctx, descs[0].Digest.String()) 1223 if err != nil { 1224 t.Fatal("Store.Resolve() error =", err) 1225 } 1226 if want := descs[0]; gotDesc.Size != want.Size || gotDesc.Digest != want.Digest { 1227 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, want) 1228 } 1229 1230 // test fetching OCI root index 1231 exists, err := anotherS.Exists(ctx, indexRoot) 1232 if err != nil { 1233 t.Fatal("Store.Exists() error =", err) 1234 } 1235 if !exists { 1236 t.Errorf("Store.Exists() = %v, want %v", exists, true) 1237 } 1238 1239 // test fetching blobs 1240 for i := range blobs { 1241 eg.Go(func(i int) func() error { 1242 return func() error { 1243 rc, err := s.Fetch(egCtx, descs[i]) 1244 if err != nil { 1245 return fmt.Errorf("Store.Fetch(%d) error = %v", i, err) 1246 } 1247 got, err := io.ReadAll(rc) 1248 if err != nil { 1249 return fmt.Errorf("Store.Fetch(%d).Read() error = %v", i, err) 1250 } 1251 err = rc.Close() 1252 if err != nil { 1253 return fmt.Errorf("Store.Fetch(%d).Close() error = %v", i, err) 1254 } 1255 if !bytes.Equal(got, blobs[i]) { 1256 return fmt.Errorf("Store.Fetch(%d) = %v, want %v", i, got, blobs[i]) 1257 } 1258 return nil 1259 } 1260 }(i)) 1261 } 1262 if err := eg.Wait(); err != nil { 1263 t.Fatal(err) 1264 } 1265 1266 // verify predecessors 1267 wants := [][]ocispec.Descriptor{ 1268 descs[4:7], // Blob 0 1269 {descs[4], descs[6]}, // Blob 1 1270 {descs[4], descs[6]}, // Blob 2 1271 {descs[5], descs[6]}, // Blob 3 1272 {descs[7]}, // Blob 4 1273 {descs[7]}, // Blob 5 1274 {descs[8], descs[12]}, // Blob 6 1275 {descs[10]}, // Blob 7 1276 {descs[10]}, // Blob 8 1277 {descs[10]}, // Blob 9 1278 {descs[14]}, // Blob 10 1279 {descs[12]}, // Blob 11 1280 nil, // Blob 12, no predecessors 1281 {descs[14]}, // Blob 13 1282 nil, // Blob 14, no predecessors 1283 } 1284 for i, want := range wants { 1285 predecessors, err := anotherS.Predecessors(ctx, descs[i]) 1286 if err != nil { 1287 t.Errorf("Store.Predecessors(%d) error = %v", i, err) 1288 } 1289 if !equalDescriptorSet(predecessors, want) { 1290 t.Errorf("Store.Predecessors(%d) = %v, want %v", i, predecessors, want) 1291 } 1292 } 1293 } 1294 1295 func Test_ExistingStore_Retag(t *testing.T) { 1296 tempDir := t.TempDir() 1297 s, err := New(tempDir) 1298 if err != nil { 1299 t.Fatal("New() error =", err) 1300 } 1301 ctx := context.Background() 1302 1303 manifest_1 := []byte(`{"layers":[]}`) 1304 manifestDesc_1 := content.NewDescriptorFromBytes(ocispec.MediaTypeImageManifest, manifest_1) 1305 manifestDesc_1.Annotations = map[string]string{"key1": "val1"} 1306 1307 // push a manifest 1308 err = s.Push(ctx, manifestDesc_1, bytes.NewReader(manifest_1)) 1309 if err != nil { 1310 t.Fatal("Store.Push() error =", err) 1311 } 1312 // tag manifest by digest 1313 err = s.Tag(ctx, manifestDesc_1, manifestDesc_1.Digest.String()) 1314 if err != nil { 1315 t.Fatal("Store.Tag() error =", err) 1316 } 1317 // tag manifest by tag 1318 ref := "foobar" 1319 err = s.Tag(ctx, manifestDesc_1, ref) 1320 if err != nil { 1321 t.Fatal("Store.Tag() error =", err) 1322 } 1323 1324 // verify index 1325 want := []ocispec.Descriptor{ 1326 { 1327 MediaType: manifestDesc_1.MediaType, 1328 Digest: manifestDesc_1.Digest, 1329 Size: manifestDesc_1.Size, 1330 Annotations: map[string]string{ 1331 "key1": "val1", 1332 ocispec.AnnotationRefName: ref, 1333 }, 1334 }, 1335 } 1336 if got := s.index.Manifests; !equalDescriptorSet(got, want) { 1337 t.Errorf("index.Manifests = %v, want %v", got, want) 1338 } 1339 1340 // test with another OCI store instance to mock loading from an existing store 1341 anotherS, err := New(tempDir) 1342 if err != nil { 1343 t.Fatal("New() error =", err) 1344 } 1345 manifest_2 := []byte(`{"layers":[], "annotations":{}}`) 1346 manifestDesc_2 := content.NewDescriptorFromBytes(ocispec.MediaTypeImageManifest, manifest_2) 1347 manifestDesc_2.Annotations = map[string]string{"key2": "val2"} 1348 1349 err = anotherS.Push(ctx, manifestDesc_2, bytes.NewReader(manifest_2)) 1350 if err != nil { 1351 t.Fatal("Store.Push() error =", err) 1352 } 1353 err = anotherS.Tag(ctx, manifestDesc_2, ref) 1354 if err != nil { 1355 t.Fatal("Store.Tag() error =", err) 1356 } 1357 1358 // verify index 1359 want = []ocispec.Descriptor{ 1360 { 1361 MediaType: manifestDesc_1.MediaType, 1362 Digest: manifestDesc_1.Digest, 1363 Size: manifestDesc_1.Size, 1364 Annotations: map[string]string{ 1365 "key1": "val1", 1366 }, 1367 }, 1368 { 1369 MediaType: manifestDesc_2.MediaType, 1370 Digest: manifestDesc_2.Digest, 1371 Size: manifestDesc_2.Size, 1372 Annotations: map[string]string{ 1373 "key2": "val2", 1374 ocispec.AnnotationRefName: ref, 1375 }, 1376 }, 1377 } 1378 if got := anotherS.index.Manifests; !equalDescriptorSet(got, want) { 1379 t.Errorf("index.Manifests = %v, want %v", got, want) 1380 } 1381 } 1382 1383 func TestCopy_MemoryToOCI_FullCopy(t *testing.T) { 1384 src := memory.New() 1385 1386 tempDir := t.TempDir() 1387 dst, err := New(tempDir) 1388 if err != nil { 1389 t.Fatal("OCI.New() error =", err) 1390 } 1391 1392 // generate test content 1393 var blobs [][]byte 1394 var descs []ocispec.Descriptor 1395 appendBlob := func(mediaType string, blob []byte) { 1396 blobs = append(blobs, blob) 1397 descs = append(descs, ocispec.Descriptor{ 1398 MediaType: mediaType, 1399 Digest: digest.FromBytes(blob), 1400 Size: int64(len(blob)), 1401 }) 1402 } 1403 generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) { 1404 manifest := ocispec.Manifest{ 1405 Config: config, 1406 Layers: layers, 1407 } 1408 manifestJSON, err := json.Marshal(manifest) 1409 if err != nil { 1410 t.Fatal(err) 1411 } 1412 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 1413 } 1414 1415 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 1416 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1 1417 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2 1418 generateManifest(descs[0], descs[1:3]...) // Blob 3 1419 1420 ctx := context.Background() 1421 for i := range blobs { 1422 err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 1423 if err != nil { 1424 t.Fatalf("failed to push test content to src: %d: %v", i, err) 1425 } 1426 } 1427 1428 root := descs[3] 1429 ref := "foobar" 1430 err = src.Tag(ctx, root, ref) 1431 if err != nil { 1432 t.Fatal("fail to tag root node", err) 1433 } 1434 1435 // test copy 1436 gotDesc, err := oras.Copy(ctx, src, ref, dst, "", oras.CopyOptions{}) 1437 if err != nil { 1438 t.Fatalf("Copy() error = %v, wantErr %v", err, false) 1439 } 1440 if !reflect.DeepEqual(gotDesc, root) { 1441 t.Errorf("Copy() = %v, want %v", gotDesc, root) 1442 } 1443 1444 // verify contents 1445 for i, desc := range descs { 1446 exists, err := dst.Exists(ctx, desc) 1447 if err != nil { 1448 t.Fatalf("dst.Exists(%d) error = %v", i, err) 1449 } 1450 if !exists { 1451 t.Errorf("dst.Exists(%d) = %v, want %v", i, exists, true) 1452 } 1453 } 1454 1455 // verify tag 1456 gotDesc, err = dst.Resolve(ctx, ref) 1457 if err != nil { 1458 t.Fatal("dst.Resolve() error =", err) 1459 } 1460 if !reflect.DeepEqual(gotDesc, root) { 1461 t.Errorf("dst.Resolve() = %v, want %v", gotDesc, root) 1462 } 1463 } 1464 1465 func TestCopyGraph_MemoryToOCI_FullCopy(t *testing.T) { 1466 src := cas.NewMemory() 1467 1468 tempDir := t.TempDir() 1469 dst, err := New(tempDir) 1470 if err != nil { 1471 t.Fatal("OCI.New() error =", err) 1472 } 1473 1474 // generate test content 1475 var blobs [][]byte 1476 var descs []ocispec.Descriptor 1477 appendBlob := func(mediaType string, blob []byte) { 1478 blobs = append(blobs, blob) 1479 descs = append(descs, ocispec.Descriptor{ 1480 MediaType: mediaType, 1481 Digest: digest.FromBytes(blob), 1482 Size: int64(len(blob)), 1483 }) 1484 } 1485 generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) { 1486 manifest := ocispec.Manifest{ 1487 Config: config, 1488 Layers: layers, 1489 } 1490 manifestJSON, err := json.Marshal(manifest) 1491 if err != nil { 1492 t.Fatal(err) 1493 } 1494 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 1495 } 1496 generateIndex := func(manifests ...ocispec.Descriptor) { 1497 index := ocispec.Index{ 1498 Manifests: manifests, 1499 } 1500 indexJSON, err := json.Marshal(index) 1501 if err != nil { 1502 t.Fatal(err) 1503 } 1504 appendBlob(ocispec.MediaTypeImageIndex, indexJSON) 1505 } 1506 1507 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 1508 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1 1509 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2 1510 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello")) // Blob 3 1511 generateManifest(descs[0], descs[1:3]...) // Blob 4 1512 generateManifest(descs[0], descs[3]) // Blob 5 1513 generateManifest(descs[0], descs[1:4]...) // Blob 6 1514 generateIndex(descs[4:6]...) // Blob 7 1515 generateIndex(descs[6]) // Blob 8 1516 generateIndex() // Blob 9 1517 generateIndex(descs[7:10]...) // Blob 10 1518 1519 ctx := context.Background() 1520 for i := range blobs { 1521 err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 1522 if err != nil { 1523 t.Fatalf("failed to push test content to src: %d: %v", i, err) 1524 } 1525 } 1526 1527 // test copy 1528 srcTracker := &storageTracker{Storage: src} 1529 dstTracker := &storageTracker{Storage: dst} 1530 root := descs[len(descs)-1] 1531 if err := oras.CopyGraph(ctx, srcTracker, dstTracker, root, oras.CopyGraphOptions{}); err != nil { 1532 t.Fatalf("CopyGraph() error = %v, wantErr %v", err, false) 1533 } 1534 1535 // verify contents 1536 for i := range blobs { 1537 got, err := content.FetchAll(ctx, dst, descs[i]) 1538 if err != nil { 1539 t.Errorf("content[%d] error = %v, wantErr %v", i, err, false) 1540 continue 1541 } 1542 if want := blobs[i]; !bytes.Equal(got, want) { 1543 t.Errorf("content[%d] = %v, want %v", i, got, want) 1544 } 1545 } 1546 1547 // verify API counts 1548 if got, want := srcTracker.fetch, int64(len(blobs)); got != want { 1549 t.Errorf("count(src.Fetch()) = %v, want %v", got, want) 1550 } 1551 if got, want := srcTracker.push, int64(0); got != want { 1552 t.Errorf("count(src.Push()) = %v, want %v", got, want) 1553 } 1554 if got, want := srcTracker.exists, int64(0); got != want { 1555 t.Errorf("count(src.Exists()) = %v, want %v", got, want) 1556 } 1557 if got, want := dstTracker.fetch, int64(0); got != want { 1558 t.Errorf("count(dst.Fetch()) = %v, want %v", got, want) 1559 } 1560 if got, want := dstTracker.push, int64(len(blobs)); got != want { 1561 t.Errorf("count(dst.Push()) = %v, want %v", got, want) 1562 } 1563 if got, want := dstTracker.exists, int64(len(blobs)); got != want { 1564 t.Errorf("count(dst.Exists()) = %v, want %v", got, want) 1565 } 1566 } 1567 1568 func TestCopyGraph_MemoryToOCI_PartialCopy(t *testing.T) { 1569 src := cas.NewMemory() 1570 1571 tempDir := t.TempDir() 1572 dst, err := New(tempDir) 1573 if err != nil { 1574 t.Fatal("OCI.New() error =", err) 1575 } 1576 1577 // generate test content 1578 var blobs [][]byte 1579 var descs []ocispec.Descriptor 1580 appendBlob := func(mediaType string, blob []byte) { 1581 blobs = append(blobs, blob) 1582 descs = append(descs, ocispec.Descriptor{ 1583 MediaType: mediaType, 1584 Digest: digest.FromBytes(blob), 1585 Size: int64(len(blob)), 1586 }) 1587 } 1588 generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) { 1589 manifest := ocispec.Manifest{ 1590 Config: config, 1591 Layers: layers, 1592 } 1593 manifestJSON, err := json.Marshal(manifest) 1594 if err != nil { 1595 t.Fatal(err) 1596 } 1597 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 1598 } 1599 generateIndex := func(manifests ...ocispec.Descriptor) { 1600 index := ocispec.Index{ 1601 Manifests: manifests, 1602 } 1603 indexJSON, err := json.Marshal(index) 1604 if err != nil { 1605 t.Fatal(err) 1606 } 1607 appendBlob(ocispec.MediaTypeImageIndex, indexJSON) 1608 } 1609 1610 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 1611 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1 1612 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2 1613 generateManifest(descs[0], descs[1:3]...) // Blob 3 1614 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello")) // Blob 4 1615 generateManifest(descs[0], descs[4]) // Blob 5 1616 generateIndex(descs[3], descs[5]) // Blob 6 1617 1618 ctx := context.Background() 1619 for i := range blobs { 1620 err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 1621 if err != nil { 1622 t.Fatalf("failed to push test content to src: %d: %v", i, err) 1623 } 1624 } 1625 1626 // initial copy 1627 root := descs[3] 1628 if err := oras.CopyGraph(ctx, src, dst, root, oras.CopyGraphOptions{}); err != nil { 1629 t.Fatalf("CopyGraph() error = %v, wantErr %v", err, false) 1630 } 1631 // verify contents 1632 for i := range blobs[:4] { 1633 got, err := content.FetchAll(ctx, dst, descs[i]) 1634 if err != nil { 1635 t.Fatalf("content[%d] error = %v, wantErr %v", i, err, false) 1636 } 1637 if want := blobs[i]; !bytes.Equal(got, want) { 1638 t.Fatalf("content[%d] = %v, want %v", i, got, want) 1639 } 1640 } 1641 1642 // test copy 1643 srcTracker := &storageTracker{Storage: src} 1644 dstTracker := &storageTracker{Storage: dst} 1645 root = descs[len(descs)-1] 1646 if err := oras.CopyGraph(ctx, srcTracker, dstTracker, root, oras.CopyGraphOptions{}); err != nil { 1647 t.Fatalf("CopyGraph() error = %v, wantErr %v", err, false) 1648 } 1649 1650 // verify contents 1651 for i := range blobs { 1652 got, err := content.FetchAll(ctx, dst, descs[i]) 1653 if err != nil { 1654 t.Errorf("content[%d] error = %v, wantErr %v", i, err, false) 1655 continue 1656 } 1657 if want := blobs[i]; !bytes.Equal(got, want) { 1658 t.Errorf("content[%d] = %v, want %v", i, got, want) 1659 } 1660 } 1661 1662 // verify API counts 1663 if got, want := srcTracker.fetch, int64(3); got != want { 1664 t.Errorf("count(src.Fetch()) = %v, want %v", got, want) 1665 } 1666 if got, want := srcTracker.push, int64(0); got != want { 1667 t.Errorf("count(src.Push()) = %v, want %v", got, want) 1668 } 1669 if got, want := srcTracker.exists, int64(0); got != want { 1670 t.Errorf("count(src.Exists()) = %v, want %v", got, want) 1671 } 1672 if got, want := dstTracker.fetch, int64(0); got != want { 1673 t.Errorf("count(dst.Fetch()) = %v, want %v", got, want) 1674 } 1675 if got, want := dstTracker.push, int64(3); got != want { 1676 t.Errorf("count(dst.Push()) = %v, want %v", got, want) 1677 } 1678 if got, want := dstTracker.exists, int64(5); got != want { 1679 t.Errorf("count(dst.Exists()) = %v, want %v", got, want) 1680 } 1681 } 1682 1683 func TestCopyGraph_OCIToMemory_FullCopy(t *testing.T) { 1684 tempDir := t.TempDir() 1685 src, err := New(tempDir) 1686 if err != nil { 1687 t.Fatal("OCI.New() error =", err) 1688 } 1689 1690 dst := cas.NewMemory() 1691 1692 // generate test content 1693 var blobs [][]byte 1694 var descs []ocispec.Descriptor 1695 appendBlob := func(mediaType string, blob []byte) { 1696 blobs = append(blobs, blob) 1697 descs = append(descs, ocispec.Descriptor{ 1698 MediaType: mediaType, 1699 Digest: digest.FromBytes(blob), 1700 Size: int64(len(blob)), 1701 }) 1702 } 1703 generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) { 1704 manifest := ocispec.Manifest{ 1705 Config: config, 1706 Layers: layers, 1707 } 1708 manifestJSON, err := json.Marshal(manifest) 1709 if err != nil { 1710 t.Fatal(err) 1711 } 1712 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 1713 } 1714 generateIndex := func(manifests ...ocispec.Descriptor) { 1715 index := ocispec.Index{ 1716 Manifests: manifests, 1717 } 1718 indexJSON, err := json.Marshal(index) 1719 if err != nil { 1720 t.Fatal(err) 1721 } 1722 appendBlob(ocispec.MediaTypeImageIndex, indexJSON) 1723 } 1724 1725 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 1726 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1 1727 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2 1728 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello")) // Blob 3 1729 generateManifest(descs[0], descs[1:3]...) // Blob 4 1730 generateManifest(descs[0], descs[3]) // Blob 5 1731 generateManifest(descs[0], descs[1:4]...) // Blob 6 1732 generateIndex(descs[4:6]...) // Blob 7 1733 generateIndex(descs[6]) // Blob 8 1734 generateIndex() // Blob 9 1735 generateIndex(descs[7:10]...) // Blob 10 1736 1737 ctx := context.Background() 1738 for i := range blobs { 1739 err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 1740 if err != nil { 1741 t.Fatalf("failed to push test content to src: %d: %v", i, err) 1742 } 1743 } 1744 1745 // test copy 1746 srcTracker := &storageTracker{Storage: src} 1747 dstTracker := &storageTracker{Storage: dst} 1748 root := descs[len(descs)-1] 1749 if err := oras.CopyGraph(ctx, srcTracker, dstTracker, root, oras.CopyGraphOptions{}); err != nil { 1750 t.Fatalf("CopyGraph() error = %v, wantErr %v", err, false) 1751 } 1752 1753 // verify contents 1754 for i := range blobs { 1755 got, err := content.FetchAll(ctx, dst, descs[i]) 1756 if err != nil { 1757 t.Errorf("content[%d] error = %v, wantErr %v", i, err, false) 1758 continue 1759 } 1760 if want := blobs[i]; !bytes.Equal(got, want) { 1761 t.Errorf("content[%d] = %v, want %v", i, got, want) 1762 } 1763 } 1764 1765 // verify API counts 1766 if got, want := srcTracker.fetch, int64(len(blobs)); got != want { 1767 t.Errorf("count(src.Fetch()) = %v, want %v", got, want) 1768 } 1769 if got, want := srcTracker.push, int64(0); got != want { 1770 t.Errorf("count(src.Push()) = %v, want %v", got, want) 1771 } 1772 if got, want := srcTracker.exists, int64(0); got != want { 1773 t.Errorf("count(src.Exists()) = %v, want %v", got, want) 1774 } 1775 if got, want := dstTracker.fetch, int64(0); got != want { 1776 t.Errorf("count(dst.Fetch()) = %v, want %v", got, want) 1777 } 1778 if got, want := dstTracker.push, int64(len(blobs)); got != want { 1779 t.Errorf("count(dst.Push()) = %v, want %v", got, want) 1780 } 1781 if got, want := dstTracker.exists, int64(len(blobs)); got != want { 1782 t.Errorf("count(dst.Exists()) = %v, want %v", got, want) 1783 } 1784 } 1785 1786 func TestCopyGraph_OCIToMemory_PartialCopy(t *testing.T) { 1787 tempDir := t.TempDir() 1788 src, err := New(tempDir) 1789 if err != nil { 1790 t.Fatal("OCI.New() error =", err) 1791 } 1792 1793 dst := cas.NewMemory() 1794 1795 // generate test content 1796 var blobs [][]byte 1797 var descs []ocispec.Descriptor 1798 appendBlob := func(mediaType string, blob []byte) { 1799 blobs = append(blobs, blob) 1800 descs = append(descs, ocispec.Descriptor{ 1801 MediaType: mediaType, 1802 Digest: digest.FromBytes(blob), 1803 Size: int64(len(blob)), 1804 }) 1805 } 1806 generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) { 1807 manifest := ocispec.Manifest{ 1808 Config: config, 1809 Layers: layers, 1810 } 1811 manifestJSON, err := json.Marshal(manifest) 1812 if err != nil { 1813 t.Fatal(err) 1814 } 1815 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 1816 } 1817 generateIndex := func(manifests ...ocispec.Descriptor) { 1818 index := ocispec.Index{ 1819 Manifests: manifests, 1820 } 1821 indexJSON, err := json.Marshal(index) 1822 if err != nil { 1823 t.Fatal(err) 1824 } 1825 appendBlob(ocispec.MediaTypeImageIndex, indexJSON) 1826 } 1827 1828 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 1829 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1 1830 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2 1831 generateManifest(descs[0], descs[1:3]...) // Blob 3 1832 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello")) // Blob 4 1833 generateManifest(descs[0], descs[4]) // Blob 5 1834 generateIndex(descs[3], descs[5]) // Blob 6 1835 1836 ctx := context.Background() 1837 for i := range blobs { 1838 err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 1839 if err != nil { 1840 t.Fatalf("failed to push test content to src: %d: %v", i, err) 1841 } 1842 } 1843 1844 // initial copy 1845 root := descs[3] 1846 if err := oras.CopyGraph(ctx, src, dst, root, oras.CopyGraphOptions{}); err != nil { 1847 t.Fatalf("CopyGraph() error = %v, wantErr %v", err, false) 1848 } 1849 // verify contents 1850 for i := range blobs[:4] { 1851 got, err := content.FetchAll(ctx, dst, descs[i]) 1852 if err != nil { 1853 t.Fatalf("content[%d] error = %v, wantErr %v", i, err, false) 1854 } 1855 if want := blobs[i]; !bytes.Equal(got, want) { 1856 t.Fatalf("content[%d] = %v, want %v", i, got, want) 1857 } 1858 } 1859 1860 // test copy 1861 srcTracker := &storageTracker{Storage: src} 1862 dstTracker := &storageTracker{Storage: dst} 1863 root = descs[len(descs)-1] 1864 if err := oras.CopyGraph(ctx, srcTracker, dstTracker, root, oras.CopyGraphOptions{}); err != nil { 1865 t.Fatalf("CopyGraph() error = %v, wantErr %v", err, false) 1866 } 1867 1868 // verify contents 1869 for i := range blobs { 1870 got, err := content.FetchAll(ctx, dst, descs[i]) 1871 if err != nil { 1872 t.Errorf("content[%d] error = %v, wantErr %v", i, err, false) 1873 continue 1874 } 1875 if want := blobs[i]; !bytes.Equal(got, want) { 1876 t.Errorf("content[%d] = %v, want %v", i, got, want) 1877 } 1878 } 1879 1880 // verify API counts 1881 if got, want := srcTracker.fetch, int64(3); got != want { 1882 t.Errorf("count(src.Fetch()) = %v, want %v", got, want) 1883 } 1884 if got, want := srcTracker.push, int64(0); got != want { 1885 t.Errorf("count(src.Push()) = %v, want %v", got, want) 1886 } 1887 if got, want := srcTracker.exists, int64(0); got != want { 1888 t.Errorf("count(src.Exists()) = %v, want %v", got, want) 1889 } 1890 if got, want := dstTracker.fetch, int64(0); got != want { 1891 t.Errorf("count(dst.Fetch()) = %v, want %v", got, want) 1892 } 1893 if got, want := dstTracker.push, int64(3); got != want { 1894 t.Errorf("count(dst.Push()) = %v, want %v", got, want) 1895 } 1896 if got, want := dstTracker.exists, int64(5); got != want { 1897 t.Errorf("count(dst.Exists()) = %v, want %v", got, want) 1898 } 1899 } 1900 1901 func TestStore_Tags(t *testing.T) { 1902 tempDir := t.TempDir() 1903 s, err := New(tempDir) 1904 if err != nil { 1905 t.Fatal("New() error =", err) 1906 } 1907 ctx := context.Background() 1908 1909 // generate test content 1910 var blobs [][]byte 1911 var descs []ocispec.Descriptor 1912 appendBlob := func(mediaType string, blob []byte) { 1913 blobs = append(blobs, blob) 1914 descs = append(descs, ocispec.Descriptor{ 1915 MediaType: mediaType, 1916 Digest: digest.FromBytes(blob), 1917 Size: int64(len(blob)), 1918 }) 1919 } 1920 generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) { 1921 manifest := ocispec.Manifest{ 1922 Config: config, 1923 Layers: layers, 1924 } 1925 // add annotation to make each manifest unique 1926 manifest.Annotations = map[string]string{ 1927 "blob_index": strconv.Itoa(len(blobs)), 1928 } 1929 manifestJSON, err := json.Marshal(manifest) 1930 if err != nil { 1931 t.Fatal(err) 1932 } 1933 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 1934 } 1935 tagManifest := func(desc ocispec.Descriptor, ref string) { 1936 if err := s.Tag(ctx, desc, ref); err != nil { 1937 t.Fatal("Store.Tag() error =", err) 1938 } 1939 } 1940 1941 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 1942 appendBlob(ocispec.MediaTypeImageLayer, []byte("foobar")) // Blob 1 1943 generateManifest(descs[0], descs[1]) // Blob 2 1944 generateManifest(descs[0], descs[1]) // Blob 3 1945 generateManifest(descs[0], descs[1]) // Blob 4 1946 generateManifest(descs[0], descs[1]) // Blob 5 1947 generateManifest(descs[0], descs[1]) // Blob 6 1948 1949 for i := range blobs { 1950 err := s.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 1951 if err != nil { 1952 t.Fatalf("failed to push test content: %d: %v", i, err) 1953 } 1954 } 1955 1956 tagManifest(descs[3], "v2") 1957 tagManifest(descs[4], "v3") 1958 tagManifest(descs[5], "v1") 1959 tagManifest(descs[6], "v4") 1960 1961 // test tags 1962 tests := []struct { 1963 name string 1964 last string 1965 want []string 1966 }{ 1967 { 1968 name: "list all tags", 1969 want: []string{"v1", "v2", "v3", "v4"}, 1970 }, 1971 { 1972 name: "list from middle", 1973 last: "v2", 1974 want: []string{"v3", "v4"}, 1975 }, 1976 { 1977 name: "list from end", 1978 last: "v4", 1979 want: nil, 1980 }, 1981 } 1982 for _, tt := range tests { 1983 t.Run(tt.name, func(t *testing.T) { 1984 if err := s.Tags(ctx, tt.last, func(got []string) error { 1985 if !reflect.DeepEqual(got, tt.want) { 1986 t.Errorf("Store.Tags() = %v, want %v", got, tt.want) 1987 } 1988 return nil 1989 }); err != nil { 1990 t.Errorf("Store.Tags() error = %v", err) 1991 } 1992 }) 1993 } 1994 1995 wantErr := errors.New("expected error") 1996 if err := s.Tags(ctx, "", func(got []string) error { 1997 return wantErr 1998 }); err != wantErr { 1999 t.Errorf("Store.Tags() error = %v, wantErr %v", err, wantErr) 2000 } 2001 } 2002 2003 func TestStore_BasicDelete(t *testing.T) { 2004 content := []byte("test delete") 2005 desc := ocispec.Descriptor{ 2006 MediaType: "test-delete", 2007 Digest: digest.FromBytes(content), 2008 Size: int64(len(content)), 2009 } 2010 ref := "latest" 2011 2012 tempDir := t.TempDir() 2013 s, err := New(tempDir) 2014 if err != nil { 2015 t.Fatal("NewDeletableStore() error =", err) 2016 } 2017 ctx := context.Background() 2018 2019 err = s.Push(ctx, desc, bytes.NewReader(content)) 2020 if err != nil { 2021 t.Errorf("Store.Push() error = %v, wantErr %v", err, false) 2022 } 2023 2024 err = s.Tag(ctx, desc, ref) 2025 if err != nil { 2026 t.Errorf("error tagging descriptor error = %v, wantErr %v", err, false) 2027 } 2028 2029 exists, err := s.Exists(ctx, desc) 2030 if err != nil { 2031 t.Fatal("Store.Exists() error =", err) 2032 } 2033 if !exists { 2034 t.Errorf("Store.Exists() = %v, want %v", exists, true) 2035 } 2036 2037 resolvedDescr, err := s.Resolve(ctx, ref) 2038 if err != nil { 2039 t.Errorf("error resolving descriptor error = %v, wantErr %v", err, false) 2040 } 2041 2042 if !reflect.DeepEqual(resolvedDescr, desc) { 2043 t.Errorf("Store.Resolve() = %v, want %v", resolvedDescr, desc) 2044 } 2045 2046 err = s.Delete(ctx, desc) 2047 if err != nil { 2048 t.Errorf("Store.Delete() = %v, wantErr %v", err, nil) 2049 } 2050 2051 exists, err = s.Exists(ctx, desc) 2052 if err != nil { 2053 t.Fatal("Store.Exists() error =", err) 2054 } 2055 if exists { 2056 t.Errorf("Store.Exists() = %v, want %v", exists, false) 2057 } 2058 } 2059 2060 func TestStore_FetchAndDelete(t *testing.T) { 2061 // create a store 2062 tempDir := t.TempDir() 2063 s, err := New(tempDir) 2064 if err != nil { 2065 t.Fatal("error =", err) 2066 } 2067 2068 // push a content 2069 content := []byte("test delete") 2070 desc := ocispec.Descriptor{ 2071 MediaType: "test-delete", 2072 Digest: digest.FromBytes(content), 2073 Size: int64(len(content)), 2074 } 2075 err = s.Push(context.Background(), desc, bytes.NewReader(content)) 2076 if err != nil { 2077 t.Fatal("error =", err) 2078 } 2079 2080 // fetch a content 2081 rc, err := s.Fetch(context.Background(), desc) 2082 if err != nil { 2083 t.Fatal("error =", err) 2084 } 2085 2086 // read and verify the content 2087 got, err := io.ReadAll(rc) 2088 if err != nil { 2089 t.Fatal("error =", err) 2090 } 2091 if !bytes.Equal(got, content) { 2092 t.Errorf("Store.Fetch() = %v, want %v", string(got), string(content)) 2093 } 2094 rc.Close() 2095 2096 // delete. If rc is not closed, Delete would fail on some systems. 2097 err = s.Delete(context.Background(), desc) 2098 if err != nil { 2099 t.Fatal("error =", err) 2100 } 2101 } 2102 2103 func TestStore_PredecessorsAndDelete(t *testing.T) { 2104 tempDir := t.TempDir() 2105 s, err := New(tempDir) 2106 if err != nil { 2107 t.Fatal("New() error =", err) 2108 } 2109 s.AutoGC = false 2110 ctx := context.Background() 2111 2112 // generate test content 2113 var blobs [][]byte 2114 var descs []ocispec.Descriptor 2115 appendBlob := func(mediaType string, blob []byte) { 2116 blobs = append(blobs, blob) 2117 descs = append(descs, ocispec.Descriptor{ 2118 MediaType: mediaType, 2119 Digest: digest.FromBytes(blob), 2120 Size: int64(len(blob)), 2121 }) 2122 } 2123 generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) { 2124 manifest := ocispec.Manifest{ 2125 Config: config, 2126 Layers: layers, 2127 } 2128 manifestJSON, err := json.Marshal(manifest) 2129 if err != nil { 2130 t.Fatal(err) 2131 } 2132 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 2133 } 2134 generateIndex := func(manifests ...ocispec.Descriptor) { 2135 index := ocispec.Index{ 2136 Manifests: manifests, 2137 } 2138 indexJSON, err := json.Marshal(index) 2139 if err != nil { 2140 t.Fatal(err) 2141 } 2142 appendBlob(ocispec.MediaTypeImageIndex, indexJSON) 2143 } 2144 2145 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 2146 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1 2147 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2 2148 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello")) // Blob 3 2149 generateManifest(descs[0], descs[1:3]...) // Blob 4 2150 generateManifest(descs[0], descs[3]) // Blob 5 2151 generateManifest(descs[0], descs[1:4]...) // Blob 6 2152 generateIndex(descs[4:6]...) // Blob 7 2153 generateIndex(descs[6]) // Blob 8 2154 2155 eg, egCtx := errgroup.WithContext(ctx) 2156 for i := range blobs { 2157 eg.Go(func(i int) func() error { 2158 return func() error { 2159 err := s.Push(egCtx, descs[i], bytes.NewReader(blobs[i])) 2160 if err != nil { 2161 return fmt.Errorf("failed to push test content to src: %d: %v", i, err) 2162 } 2163 return nil 2164 } 2165 }(i)) 2166 } 2167 if err := eg.Wait(); err != nil { 2168 t.Fatal(err) 2169 } 2170 2171 // verify predecessors 2172 wants := [][]ocispec.Descriptor{ 2173 descs[4:7], // Blob 0 2174 {descs[4], descs[6]}, // Blob 1 2175 {descs[4], descs[6]}, // Blob 2 2176 {descs[5], descs[6]}, // Blob 3 2177 {descs[7]}, // Blob 4 2178 {descs[7]}, // Blob 5 2179 {descs[8]}, // Blob 6 2180 nil, // Blob 7 2181 nil, // Blob 8 2182 } 2183 for i, want := range wants { 2184 predecessors, err := s.Predecessors(ctx, descs[i]) 2185 if err != nil { 2186 t.Errorf("Store.Predecessors(%d) error = %v", i, err) 2187 } 2188 if !equalDescriptorSet(predecessors, want) { 2189 t.Errorf("Store.Predecessors(%d) = %v, want %v", i, predecessors, want) 2190 } 2191 } 2192 2193 // delete a node and verify the result 2194 s.Delete(egCtx, descs[6]) 2195 // verify predecessors 2196 wants = [][]ocispec.Descriptor{ 2197 descs[4:6], // Blob 0 2198 {descs[4]}, // Blob 1 2199 {descs[4]}, // Blob 2 2200 {descs[5]}, // Blob 3 2201 {descs[7]}, // Blob 4 2202 {descs[7]}, // Blob 5 2203 {descs[8]}, // Blob 6 2204 nil, // Blob 7 2205 nil, // Blob 8 2206 } 2207 for i, want := range wants { 2208 predecessors, err := s.Predecessors(ctx, descs[i]) 2209 if err != nil { 2210 t.Errorf("Store.Predecessors(%d) error = %v", i, err) 2211 } 2212 if !equalDescriptorSet(predecessors, want) { 2213 t.Errorf("Store.Predecessors(%d) = %v, want %v", i, predecessors, want) 2214 } 2215 } 2216 2217 // delete a node and verify the result 2218 s.Delete(egCtx, descs[8]) 2219 // verify predecessors 2220 wants = [][]ocispec.Descriptor{ 2221 descs[4:6], // Blob 0 2222 {descs[4]}, // Blob 1 2223 {descs[4]}, // Blob 2 2224 {descs[5]}, // Blob 3 2225 {descs[7]}, // Blob 4 2226 {descs[7]}, // Blob 5 2227 nil, // Blob 6 2228 nil, // Blob 7 2229 nil, // Blob 8 2230 } 2231 for i, want := range wants { 2232 predecessors, err := s.Predecessors(ctx, descs[i]) 2233 if err != nil { 2234 t.Errorf("Store.Predecessors(%d) error = %v", i, err) 2235 } 2236 if !equalDescriptorSet(predecessors, want) { 2237 t.Errorf("Store.Predecessors(%d) = %v, want %v", i, predecessors, want) 2238 } 2239 } 2240 2241 // delete a node and verify the result 2242 s.Delete(egCtx, descs[5]) 2243 // verify predecessors 2244 wants = [][]ocispec.Descriptor{ 2245 {descs[4]}, // Blob 0 2246 {descs[4]}, // Blob 1 2247 {descs[4]}, // Blob 2 2248 nil, // Blob 3 2249 {descs[7]}, // Blob 4 2250 {descs[7]}, // Blob 5 2251 nil, // Blob 6 2252 nil, // Blob 7 2253 nil, // Blob 8 2254 } 2255 for i, want := range wants { 2256 predecessors, err := s.Predecessors(ctx, descs[i]) 2257 if err != nil { 2258 t.Errorf("Store.Predecessors(%d) error = %v", i, err) 2259 } 2260 if !equalDescriptorSet(predecessors, want) { 2261 t.Errorf("Store.Predecessors(%d) = %v, want %v", i, predecessors, want) 2262 } 2263 } 2264 } 2265 2266 func TestStore_DeleteWithAutoGC(t *testing.T) { 2267 tempDir := t.TempDir() 2268 s, err := New(tempDir) 2269 if err != nil { 2270 t.Fatal("New() error =", err) 2271 } 2272 ctx := context.Background() 2273 2274 // generate test content 2275 var blobs [][]byte 2276 var descs []ocispec.Descriptor 2277 appendBlob := func(mediaType string, blob []byte) { 2278 blobs = append(blobs, blob) 2279 descs = append(descs, ocispec.Descriptor{ 2280 MediaType: mediaType, 2281 Digest: digest.FromBytes(blob), 2282 Size: int64(len(blob)), 2283 }) 2284 } 2285 generateManifest := func(config ocispec.Descriptor, subject *ocispec.Descriptor, layers ...ocispec.Descriptor) { 2286 manifest := ocispec.Manifest{ 2287 Config: config, 2288 Subject: subject, 2289 Layers: layers, 2290 } 2291 manifestJSON, err := json.Marshal(manifest) 2292 if err != nil { 2293 t.Fatal(err) 2294 } 2295 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 2296 } 2297 generateIndex := func(manifests ...ocispec.Descriptor) { 2298 index := ocispec.Index{ 2299 Manifests: manifests, 2300 } 2301 indexJSON, err := json.Marshal(index) 2302 if err != nil { 2303 t.Fatal(err) 2304 } 2305 appendBlob(ocispec.MediaTypeImageIndex, indexJSON) 2306 } 2307 2308 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 2309 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1 2310 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2 2311 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello")) // Blob 3 2312 generateManifest(descs[0], nil, descs[1]) // Blob 4 2313 generateManifest(descs[0], nil, descs[2]) // Blob 5 2314 generateManifest(descs[0], nil, descs[3]) // Blob 6 2315 generateIndex(descs[4:6]...) // Blob 7 2316 generateIndex(descs[6]) // Blob 8 2317 appendBlob(ocispec.MediaTypeImageLayer, []byte("world")) // Blob 9 2318 generateManifest(descs[0], &descs[6], descs[9]) // Blob 10 2319 generateManifest(descs[0], &descs[10], descs[2]) // Blob 11 2320 2321 eg, egCtx := errgroup.WithContext(ctx) 2322 for i := range blobs { 2323 eg.Go(func(i int) func() error { 2324 return func() error { 2325 err := s.Push(egCtx, descs[i], bytes.NewReader(blobs[i])) 2326 if err != nil { 2327 return fmt.Errorf("failed to push test content to src: %d: %v", i, err) 2328 } 2329 return nil 2330 } 2331 }(i)) 2332 } 2333 if err := eg.Wait(); err != nil { 2334 t.Fatal(err) 2335 } 2336 2337 // delete blob 4 and verify the result 2338 if err := s.Delete(egCtx, descs[4]); err != nil { 2339 t.Fatal(err) 2340 } 2341 2342 // blob 1 and 4 are now deleted, and other blobs are still present 2343 notPresent := []ocispec.Descriptor{descs[1], descs[4]} 2344 for _, node := range notPresent { 2345 if exists, _ := s.Exists(egCtx, node); exists { 2346 t.Errorf("%v should not exist in store", node) 2347 } 2348 } 2349 stillPresent := []ocispec.Descriptor{descs[0], descs[2], descs[3], descs[5], descs[6], descs[7], descs[8], descs[9], descs[10], descs[11]} 2350 for _, node := range stillPresent { 2351 if exists, _ := s.Exists(egCtx, node); !exists { 2352 t.Errorf("%v should exist in store", node) 2353 } 2354 } 2355 2356 // delete blob 8 and verify the result 2357 if err := s.Delete(egCtx, descs[8]); err != nil { 2358 t.Fatal(err) 2359 } 2360 2361 // blob 1, 4 and 8 are now deleted, and other blobs are still present 2362 notPresent = []ocispec.Descriptor{descs[1], descs[4], descs[8]} 2363 for _, node := range notPresent { 2364 if exists, _ := s.Exists(egCtx, node); exists { 2365 t.Errorf("%v should not exist in store", node) 2366 } 2367 } 2368 stillPresent = []ocispec.Descriptor{descs[0], descs[2], descs[3], descs[5], descs[6], descs[7], descs[9], descs[10], descs[11]} 2369 for _, node := range stillPresent { 2370 if exists, _ := s.Exists(egCtx, node); !exists { 2371 t.Errorf("%v should exist in store", node) 2372 } 2373 } 2374 2375 // delete blob 6 and verify the result 2376 if err := s.Delete(egCtx, descs[6]); err != nil { 2377 t.Fatal(err) 2378 } 2379 2380 // blob 1, 3, 4, 6, 8, 9, 10, 11 are now deleted, and other blobs are still present 2381 notPresent = []ocispec.Descriptor{descs[1], descs[3], descs[4], descs[6], descs[8], descs[9], descs[10], descs[11]} 2382 for _, node := range notPresent { 2383 if exists, _ := s.Exists(egCtx, node); exists { 2384 t.Errorf("%v should not exist in store", node) 2385 } 2386 } 2387 stillPresent = []ocispec.Descriptor{descs[0], descs[2], descs[5], descs[7]} 2388 for _, node := range stillPresent { 2389 if exists, _ := s.Exists(egCtx, node); !exists { 2390 t.Errorf("%v should exist in store", node) 2391 } 2392 } 2393 2394 // verify predecessors information 2395 wants := [][]ocispec.Descriptor{ 2396 {descs[5]}, // Blob 0 2397 nil, // Blob 1 2398 {descs[5]}, // Blob 2 2399 nil, // Blob 3 2400 {descs[7]}, // Blob 4's predecessor is descs[7], even though blob 4 no longer exist 2401 {descs[7]}, // Blob 5 2402 nil, // Blob 6 2403 nil, // Blob 7 2404 nil, // Blob 8 2405 nil, // Blob 9 2406 nil, // Blob 10 2407 nil, // Blob 11 2408 } 2409 for i, want := range wants { 2410 predecessors, err := s.Predecessors(ctx, descs[i]) 2411 if err != nil { 2412 t.Errorf("Store.Predecessors(%d) error = %v", i, err) 2413 } 2414 if !equalDescriptorSet(predecessors, want) { 2415 t.Errorf("Store.Predecessors(%d) = %v, want %v", i, predecessors, want) 2416 } 2417 } 2418 } 2419 2420 func TestStore_Untag(t *testing.T) { 2421 content := []byte("test delete") 2422 desc := ocispec.Descriptor{ 2423 MediaType: "test-delete", 2424 Digest: digest.FromBytes(content), 2425 Size: int64(len(content)), 2426 } 2427 ref := "latest" 2428 2429 tempDir := t.TempDir() 2430 s, err := New(tempDir) 2431 if err != nil { 2432 t.Fatal("NewDeletableStore() error =", err) 2433 } 2434 ctx := context.Background() 2435 2436 err = s.Push(ctx, desc, bytes.NewReader(content)) 2437 if err != nil { 2438 t.Errorf("Store.Push() error = %v, wantErr %v", err, false) 2439 } 2440 2441 err = s.Tag(ctx, desc, ref) 2442 if err != nil { 2443 t.Errorf("error tagging descriptor error = %v, wantErr %v", err, false) 2444 } 2445 2446 exists, err := s.Exists(ctx, desc) 2447 if err != nil { 2448 t.Fatal("Store.Exists() error =", err) 2449 } 2450 if !exists { 2451 t.Errorf("Store.Exists() = %v, want %v", exists, true) 2452 } 2453 2454 resolvedDescr, err := s.Resolve(ctx, ref) 2455 if err != nil { 2456 t.Errorf("error resolving descriptor error = %v, wantErr %v", err, false) 2457 } 2458 2459 if !reflect.DeepEqual(resolvedDescr, desc) { 2460 t.Errorf("Store.Resolve() = %v, want %v", resolvedDescr, desc) 2461 } 2462 2463 err = s.Untag(ctx, ref) 2464 if err != nil { 2465 t.Errorf("Store.Untag() = %v, wantErr %v", err, nil) 2466 } 2467 2468 _, err = s.Resolve(ctx, ref) 2469 if !errors.Is(err, errdef.ErrNotFound) { 2470 t.Errorf("error resolving descriptor error = %v, wantErr %v", err, errdef.ErrNotFound) 2471 } 2472 2473 exists, err = s.Exists(ctx, desc) 2474 if err != nil { 2475 t.Fatal("Store.Exists() error =", err) 2476 } 2477 if !exists { 2478 t.Errorf("Store.Exists() = %v, want %v", exists, true) 2479 } 2480 } 2481 2482 func TestStore_UntagErrorPath(t *testing.T) { 2483 content := []byte("test delete") 2484 desc := ocispec.Descriptor{ 2485 MediaType: "test-delete", 2486 Digest: digest.FromBytes(content), 2487 Size: int64(len(content)), 2488 } 2489 ref := "latest" 2490 2491 tempDir := t.TempDir() 2492 s, err := New(tempDir) 2493 if err != nil { 2494 t.Fatal("NewDeletableStore() error =", err) 2495 } 2496 ctx := context.Background() 2497 2498 err = s.Untag(ctx, "") 2499 if !errors.Is(err, errdef.ErrMissingReference) { 2500 t.Errorf("Store.Untag() error = %v, wantErr %v", err, errdef.ErrMissingReference) 2501 } 2502 2503 err = s.Untag(ctx, "foobar") 2504 if !errors.Is(err, errdef.ErrNotFound) { 2505 t.Errorf("Store.Untag() error = %v, wantErr %v", err, errdef.ErrNotFound) 2506 } 2507 2508 err = s.Push(ctx, desc, bytes.NewReader(content)) 2509 if err != nil { 2510 t.Errorf("Store.Push() error = %v, wantErr %v", err, false) 2511 } 2512 2513 err = s.Tag(ctx, desc, ref) 2514 if err != nil { 2515 t.Errorf("error tagging descriptor error = %v, wantErr %v", err, false) 2516 } 2517 2518 exists, err := s.Exists(ctx, desc) 2519 if err != nil { 2520 t.Fatal("Store.Exists() error =", err) 2521 } 2522 if !exists { 2523 t.Errorf("Store.Exists() = %v, want %v", exists, true) 2524 } 2525 2526 resolvedDescr, err := s.Resolve(ctx, ref) 2527 if err != nil { 2528 t.Errorf("error resolving descriptor error = %v, wantErr %v", err, false) 2529 } 2530 2531 err = s.Untag(ctx, resolvedDescr.Digest.String()) 2532 if !errors.Is(err, errdef.ErrInvalidReference) { 2533 t.Errorf("Store.Untag() error = %v, wantErr %v", err, errdef.ErrInvalidReference) 2534 } 2535 } 2536 2537 func TestStore_GC(t *testing.T) { 2538 tempDir := t.TempDir() 2539 s, err := New(tempDir) 2540 if err != nil { 2541 t.Fatal("New() error =", err) 2542 } 2543 ctx := context.Background() 2544 2545 // generate test content 2546 var blobs [][]byte 2547 var descs []ocispec.Descriptor 2548 appendBlob := func(mediaType string, blob []byte) { 2549 blobs = append(blobs, blob) 2550 descs = append(descs, ocispec.Descriptor{ 2551 MediaType: mediaType, 2552 Digest: digest.FromBytes(blob), 2553 Size: int64(len(blob)), 2554 }) 2555 } 2556 generateManifest := func(config ocispec.Descriptor, subject *ocispec.Descriptor, layers ...ocispec.Descriptor) { 2557 manifest := ocispec.Manifest{ 2558 Config: config, 2559 Subject: subject, 2560 Layers: layers, 2561 } 2562 manifestJSON, err := json.Marshal(manifest) 2563 if err != nil { 2564 t.Fatal(err) 2565 } 2566 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 2567 } 2568 generateImageIndex := func(manifests ...ocispec.Descriptor) { 2569 index := ocispec.Index{ 2570 Manifests: manifests, 2571 } 2572 indexJSON, err := json.Marshal(index) 2573 if err != nil { 2574 t.Fatal(err) 2575 } 2576 appendBlob(ocispec.MediaTypeImageIndex, indexJSON) 2577 } 2578 generateArtifactManifest := func(blobs ...ocispec.Descriptor) { 2579 var manifest spec.Artifact 2580 manifest.Blobs = append(manifest.Blobs, blobs...) 2581 manifestJSON, err := json.Marshal(manifest) 2582 if err != nil { 2583 t.Fatal(err) 2584 } 2585 appendBlob(spec.MediaTypeArtifactManifest, manifestJSON) 2586 } 2587 2588 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 2589 appendBlob(ocispec.MediaTypeImageLayer, []byte("blob")) // Blob 1 2590 appendBlob(ocispec.MediaTypeImageLayer, []byte("dangling layer")) // Blob 2, dangling layer 2591 generateManifest(descs[0], nil, descs[1]) // Blob 3, valid manifest 2592 generateManifest(descs[0], &descs[3], descs[1]) // Blob 4, referrer of a valid manifest 2593 appendBlob(ocispec.MediaTypeImageLayer, []byte("dangling layer 2")) // Blob 5, dangling layer 2594 generateArtifactManifest(descs[4]) // blob 6, dangling artifact 2595 generateManifest(descs[0], &descs[5], descs[1]) // Blob 7, referrer of a dangling manifest 2596 appendBlob(ocispec.MediaTypeImageLayer, []byte("dangling layer 3")) // Blob 8, dangling layer 2597 generateArtifactManifest(descs[6]) // blob 9, dangling artifact 2598 generateImageIndex(descs[7], descs[5]) // blob 10, dangling image index 2599 appendBlob(ocispec.MediaTypeImageLayer, []byte("garbage layer 1")) // Blob 11, garbage layer 1 2600 generateManifest(descs[0], nil, descs[4]) // Blob 12, garbage manifest 1 2601 appendBlob(ocispec.MediaTypeImageConfig, []byte("garbage config")) // Blob 13, garbage config 2602 appendBlob(ocispec.MediaTypeImageLayer, []byte("garbage layer 2")) // Blob 14, garbage layer 2 2603 generateManifest(descs[6], nil, descs[7]) // Blob 15, garbage manifest 2 2604 generateManifest(descs[0], &descs[13], descs[1]) // Blob 16, referrer of a garbage manifest 2605 appendBlob(ocispec.MediaTypeImageLayer, []byte("another layer")) // Blob 17, untagged manifest 2606 generateManifest(descs[0], nil, descs[17]) // Blob 18, valid untagged manifest 2607 2608 // push blobs 0 - blobs 10 into s 2609 for i := 0; i <= 10; i++ { 2610 err := s.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 2611 if err != nil { 2612 t.Errorf("failed to push test content to src: %d: %v", i, err) 2613 } 2614 } 2615 2616 // push blobs 17 - blobs 18 into s 2617 for i := 17; i <= 18; i++ { 2618 err := s.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 2619 if err != nil { 2620 t.Errorf("failed to push test content to src: %d: %v", i, err) 2621 } 2622 } 2623 2624 // remove blobs 5 - blobs 10 from index.json 2625 for i := 5; i <= 10; i++ { 2626 s.tagResolver.Untag(string(descs[i].Digest)) 2627 } 2628 s.SaveIndex() 2629 2630 // push blobs 11 - blobs 16 into s.storage, making them garbage as their metadata 2631 // doesn't exist in s 2632 for i := 11; i < 17; i++ { 2633 err := s.storage.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 2634 if err != nil { 2635 t.Errorf("failed to push test content to src: %d: %v", i, err) 2636 } 2637 } 2638 2639 // confirm that all the blobs are in the storage 2640 for i := 0; i < len(blobs); i++ { 2641 exists, err := s.Exists(ctx, descs[i]) 2642 if err != nil { 2643 t.Fatal(err) 2644 } 2645 if !exists { 2646 t.Fatalf("descs[%d] should exist", i) 2647 } 2648 } 2649 2650 // tag manifest blob 3 2651 s.Tag(ctx, descs[3], "latest") 2652 2653 // perform double GC 2654 if err = s.GC(ctx); err != nil { 2655 t.Fatal(err) 2656 } 2657 if err = s.GC(ctx); err != nil { 2658 t.Fatal(err) 2659 } 2660 2661 // verify existence 2662 wantExistence := []bool{true, true, false, true, true, false, false, false, 2663 false, false, false, false, false, false, false, false, false, false, false} 2664 for i, wantValue := range wantExistence { 2665 exists, err := s.Exists(ctx, descs[i]) 2666 if err != nil { 2667 t.Fatal(err) 2668 } 2669 if exists != wantValue { 2670 t.Fatalf("want existence %d to be %v, got %v", i, wantValue, exists) 2671 } 2672 } 2673 } 2674 2675 func TestStore_GCAndDeleteOnIndex(t *testing.T) { 2676 tempDir := t.TempDir() 2677 s, err := New(tempDir) 2678 if err != nil { 2679 t.Fatal("New() error =", err) 2680 } 2681 ctx := context.Background() 2682 2683 // generate test content 2684 var blobs [][]byte 2685 var descs []ocispec.Descriptor 2686 appendBlob := func(mediaType string, blob []byte) { 2687 blobs = append(blobs, blob) 2688 descs = append(descs, ocispec.Descriptor{ 2689 MediaType: mediaType, 2690 Digest: digest.FromBytes(blob), 2691 Size: int64(len(blob)), 2692 }) 2693 } 2694 generateManifest := func(config ocispec.Descriptor, subject *ocispec.Descriptor, layers ...ocispec.Descriptor) { 2695 manifest := ocispec.Manifest{ 2696 Config: config, 2697 Subject: subject, 2698 Layers: layers, 2699 } 2700 manifestJSON, err := json.Marshal(manifest) 2701 if err != nil { 2702 t.Fatal(err) 2703 } 2704 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 2705 } 2706 generateImageIndex := func(manifests ...ocispec.Descriptor) { 2707 index := ocispec.Index{ 2708 Manifests: manifests, 2709 } 2710 indexJSON, err := json.Marshal(index) 2711 if err != nil { 2712 t.Fatal(err) 2713 } 2714 appendBlob(ocispec.MediaTypeImageIndex, indexJSON) 2715 } 2716 2717 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 2718 appendBlob(ocispec.MediaTypeImageLayer, []byte("blob1")) // Blob 1 2719 generateManifest(descs[0], nil, descs[1]) // Blob 2, manifest 2720 appendBlob(ocispec.MediaTypeImageLayer, []byte("blob2")) // Blob 3 2721 generateManifest(descs[0], nil, descs[3]) // Blob 4, manifest 2722 appendBlob(ocispec.MediaTypeImageLayer, []byte("blob3")) // Blob 5 2723 generateManifest(descs[0], nil, descs[5]) // Blob 6, manifest 2724 appendBlob(ocispec.MediaTypeImageLayer, []byte("blob4")) // Blob 7 2725 generateManifest(descs[0], nil, descs[7]) // Blob 8, manifest 2726 generateImageIndex(descs[2], descs[4], descs[6], descs[8]) // blob 9, image index 2727 2728 // push all blobs into the store 2729 for i := 0; i < len(blobs); i++ { 2730 err := s.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 2731 if err != nil { 2732 t.Errorf("failed to push test content to src: %d: %v", i, err) 2733 } 2734 } 2735 2736 // confirm that all the blobs are in the storage 2737 for i := 0; i < len(blobs); i++ { 2738 exists, err := s.Exists(ctx, descs[i]) 2739 if err != nil { 2740 t.Fatal(err) 2741 } 2742 if !exists { 2743 t.Fatalf("descs[%d] should exist", i) 2744 } 2745 } 2746 2747 // tag manifest blob 8 2748 s.Tag(ctx, descs[8], "latest") 2749 2750 // delete the image index 2751 if err := s.Delete(ctx, descs[9]); err != nil { 2752 t.Fatal(err) 2753 } 2754 2755 // verify existence 2756 wantExistence := []bool{true, false, false, false, false, false, false, true, true, false} 2757 for i, wantValue := range wantExistence { 2758 exists, err := s.Exists(ctx, descs[i]) 2759 if err != nil { 2760 t.Fatal(err) 2761 } 2762 if exists != wantValue { 2763 t.Fatalf("want existence %d to be %v, got %v", i, wantValue, exists) 2764 } 2765 } 2766 } 2767 2768 func TestStore_GCErrorPath(t *testing.T) { 2769 tempDir := t.TempDir() 2770 s, err := New(tempDir) 2771 if err != nil { 2772 t.Fatal("New() error =", err) 2773 } 2774 ctx := context.Background() 2775 2776 // generate test content 2777 var blobs [][]byte 2778 var descs []ocispec.Descriptor 2779 appendBlob := func(mediaType string, blob []byte) { 2780 blobs = append(blobs, blob) 2781 descs = append(descs, ocispec.Descriptor{ 2782 MediaType: mediaType, 2783 Digest: digest.FromBytes(blob), 2784 Size: int64(len(blob)), 2785 }) 2786 } 2787 appendBlob(ocispec.MediaTypeImageLayer, []byte("valid blob")) // Blob 0 2788 2789 // push the valid blob 2790 err = s.Push(ctx, descs[0], bytes.NewReader(blobs[0])) 2791 if err != nil { 2792 t.Error("failed to push test content to src") 2793 } 2794 2795 // write random contents 2796 algPath := path.Join(tempDir, "blobs") 2797 dgstPath := path.Join(algPath, "sha256") 2798 if err := os.WriteFile(path.Join(algPath, "other"), []byte("random"), 0444); err != nil { 2799 t.Fatal("error calling WriteFile(), error =", err) 2800 } 2801 if err := os.WriteFile(path.Join(dgstPath, "other2"), []byte("random2"), 0444); err != nil { 2802 t.Fatal("error calling WriteFile(), error =", err) 2803 } 2804 2805 // perform GC 2806 if err = s.GC(ctx); err != nil { 2807 t.Fatal(err) 2808 } 2809 2810 appendBlob(ocispec.MediaTypeImageLayer, []byte("valid blob 2")) // Blob 1 2811 2812 // push the valid blob 2813 err = s.Push(ctx, descs[1], bytes.NewReader(blobs[1])) 2814 if err != nil { 2815 t.Error("failed to push test content to src") 2816 } 2817 2818 // unknown algorithm 2819 if err := os.Mkdir(path.Join(algPath, "sha666"), 0777); err != nil { 2820 t.Fatal(err) 2821 } 2822 if err = s.GC(ctx); err != nil { 2823 t.Fatal("this error should be silently ignored") 2824 } 2825 2826 // os.Remove() error 2827 badDigest := digest.FromBytes([]byte("bad digest")).Encoded() 2828 badPath := path.Join(algPath, "sha256", badDigest) 2829 if err := os.Mkdir(badPath, 0777); err != nil { 2830 t.Fatal(err) 2831 } 2832 if err := os.WriteFile(path.Join(badPath, "whatever"), []byte("extra content"), 0444); err != nil { 2833 t.Fatal("error calling WriteFile(), error =", err) 2834 } 2835 if err = s.GC(ctx); err == nil { 2836 t.Fatal("expect an error when os.Remove()") 2837 } 2838 } 2839 2840 func equalDescriptorSet(actual []ocispec.Descriptor, expected []ocispec.Descriptor) bool { 2841 if len(actual) != len(expected) { 2842 return false 2843 } 2844 contains := func(node ocispec.Descriptor) bool { 2845 for _, candidate := range actual { 2846 if reflect.DeepEqual(candidate, node) { 2847 return true 2848 } 2849 } 2850 return false 2851 } 2852 for _, node := range expected { 2853 if !contains(node) { 2854 return false 2855 } 2856 } 2857 return true 2858 } 2859 2860 func Test_isContextDone(t *testing.T) { 2861 ctx := context.Background() 2862 ctx, cancel := context.WithCancel(ctx) 2863 if err := isContextDone(ctx); err != nil { 2864 t.Errorf("expect error = %v, got %v", nil, err) 2865 } 2866 cancel() 2867 if err := isContextDone(ctx); err != context.Canceled { 2868 t.Errorf("expect error = %v, got %v", context.Canceled, err) 2869 } 2870 }