github.com/opcr-io/oras-go/v2@v2.0.0-20231122155130-eb4260d8a0ae/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/filepath" 28 "reflect" 29 "strconv" 30 "strings" 31 "sync/atomic" 32 "testing" 33 34 "github.com/opcr-io/oras-go/v2" 35 "github.com/opcr-io/oras-go/v2/content" 36 "github.com/opcr-io/oras-go/v2/content/memory" 37 "github.com/opcr-io/oras-go/v2/errdef" 38 "github.com/opcr-io/oras-go/v2/internal/cas" 39 "github.com/opcr-io/oras-go/v2/internal/descriptor" 40 "github.com/opcr-io/oras-go/v2/registry" 41 "github.com/opencontainers/go-digest" 42 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 43 "golang.org/x/sync/errgroup" 44 ) 45 46 // storageTracker tracks storage API counts. 47 type storageTracker struct { 48 content.Storage 49 fetch int64 50 push int64 51 exists int64 52 } 53 54 func (t *storageTracker) Fetch(ctx context.Context, target ocispec.Descriptor) (io.ReadCloser, error) { 55 atomic.AddInt64(&t.fetch, 1) 56 return t.Storage.Fetch(ctx, target) 57 } 58 59 func (t *storageTracker) Push(ctx context.Context, expected ocispec.Descriptor, content io.Reader) error { 60 atomic.AddInt64(&t.push, 1) 61 return t.Storage.Push(ctx, expected, content) 62 } 63 64 func (t *storageTracker) Exists(ctx context.Context, target ocispec.Descriptor) (bool, error) { 65 atomic.AddInt64(&t.exists, 1) 66 return t.Storage.Exists(ctx, target) 67 } 68 69 func TestStoreInterface(t *testing.T) { 70 var store interface{} = &Store{} 71 if _, ok := store.(oras.GraphTarget); !ok { 72 t.Error("&Store{} does not conform oras.Target") 73 } 74 if _, ok := store.(registry.TagLister); !ok { 75 t.Error("&Store{} does not conform registry.TagLister") 76 } 77 } 78 79 func TestStore_Success(t *testing.T) { 80 blob := []byte("test") 81 blobDesc := content.NewDescriptorFromBytes("test", blob) 82 manifest := []byte(`{"layers":[]}`) 83 manifestDesc := content.NewDescriptorFromBytes(ocispec.MediaTypeImageManifest, manifest) 84 ref := "foobar" 85 86 tempDir := t.TempDir() 87 s, err := New(tempDir) 88 if err != nil { 89 t.Fatal("New() error =", err) 90 } 91 ctx := context.Background() 92 93 // validate layout 94 layoutFilePath := filepath.Join(tempDir, ocispec.ImageLayoutFile) 95 layoutFile, err := os.Open(layoutFilePath) 96 if err != nil { 97 t.Errorf("error opening layout file, error = %v", err) 98 } 99 defer layoutFile.Close() 100 101 var layout *ocispec.ImageLayout 102 err = json.NewDecoder(layoutFile).Decode(&layout) 103 if err != nil { 104 t.Fatal("error decoding layout, error =", err) 105 } 106 if want := ocispec.ImageLayoutVersion; layout.Version != want { 107 t.Errorf("layout.Version = %s, want %s", layout.Version, want) 108 } 109 110 // validate index.json 111 indexFilePath := filepath.Join(tempDir, ociImageIndexFile) 112 indexFile, err := os.Open(indexFilePath) 113 if err != nil { 114 t.Errorf("error opening layout file, error = %v", err) 115 } 116 defer indexFile.Close() 117 118 var index *ocispec.Index 119 err = json.NewDecoder(indexFile).Decode(&index) 120 if err != nil { 121 t.Fatal("error decoding index.json, error =", err) 122 } 123 if want := 2; index.SchemaVersion != want { 124 t.Errorf("index.SchemaVersion = %v, want %v", index.SchemaVersion, want) 125 } 126 127 // test push blob 128 err = s.Push(ctx, blobDesc, bytes.NewReader(blob)) 129 if err != nil { 130 t.Fatal("Store.Push() error =", err) 131 } 132 internalResolver := s.tagResolver 133 if got, want := len(internalResolver.Map()), 0; got != want { 134 t.Errorf("resolver.Map() = %v, want %v", got, want) 135 } 136 137 // test push manifest 138 err = s.Push(ctx, manifestDesc, bytes.NewReader(manifest)) 139 if err != nil { 140 t.Fatal("Store.Push() error =", err) 141 } 142 if got, want := len(internalResolver.Map()), 1; got != want { 143 t.Errorf("resolver.Map() = %v, want %v", got, want) 144 } 145 146 // test resolving blob by digest 147 gotDesc, err := s.Resolve(ctx, blobDesc.Digest.String()) 148 if err != nil { 149 t.Fatal("Store.Resolve() error =", err) 150 } 151 if want := blobDesc; gotDesc.Size != want.Size || gotDesc.Digest != want.Digest { 152 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, blobDesc) 153 } 154 155 // test resolving manifest by digest 156 gotDesc, err = s.Resolve(ctx, manifestDesc.Digest.String()) 157 if err != nil { 158 t.Fatal("Store.Resolve() error =", err) 159 } 160 if !reflect.DeepEqual(gotDesc, manifestDesc) { 161 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, manifestDesc) 162 } 163 164 // test tag 165 err = s.Tag(ctx, manifestDesc, ref) 166 if err != nil { 167 t.Fatal("Store.Tag() error =", err) 168 } 169 if got, want := len(internalResolver.Map()), 2; got != want { 170 t.Errorf("resolver.Map() = %v, want %v", got, want) 171 } 172 173 // test resolving manifest by tag 174 gotDesc, err = s.Resolve(ctx, ref) 175 if err != nil { 176 t.Fatal("Store.Resolve() error =", err) 177 } 178 if !reflect.DeepEqual(gotDesc, manifestDesc) { 179 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, manifestDesc) 180 } 181 182 // test fetch 183 exists, err := s.Exists(ctx, manifestDesc) 184 if err != nil { 185 t.Fatal("Store.Exists() error =", err) 186 } 187 if !exists { 188 t.Errorf("Store.Exists() = %v, want %v", exists, true) 189 } 190 191 rc, err := s.Fetch(ctx, manifestDesc) 192 if err != nil { 193 t.Fatal("Store.Fetch() error =", err) 194 } 195 got, err := io.ReadAll(rc) 196 if err != nil { 197 t.Fatal("Store.Fetch().Read() error =", err) 198 } 199 err = rc.Close() 200 if err != nil { 201 t.Error("Store.Fetch().Close() error =", err) 202 } 203 if !bytes.Equal(got, manifest) { 204 t.Errorf("Store.Fetch() = %v, want %v", got, manifest) 205 } 206 } 207 208 func TestStore_RelativeRoot_Success(t *testing.T) { 209 blob := []byte("test") 210 blobDesc := content.NewDescriptorFromBytes("test", blob) 211 manifest := []byte(`{"layers":[]}`) 212 manifestDesc := content.NewDescriptorFromBytes(ocispec.MediaTypeImageManifest, manifest) 213 ref := "foobar" 214 215 tempDir, err := filepath.EvalSymlinks(t.TempDir()) 216 if err != nil { 217 t.Fatal("error calling filepath.EvalSymlinks(), error =", err) 218 } 219 currDir, err := os.Getwd() 220 if err != nil { 221 t.Fatal("error calling Getwd(), error=", err) 222 } 223 if err := os.Chdir(tempDir); err != nil { 224 t.Fatal("error calling Chdir(), error=", err) 225 } 226 s, err := New(".") 227 if err != nil { 228 t.Fatal("New() error =", err) 229 } 230 if want := tempDir; s.root != want { 231 t.Errorf("Store.root = %s, want %s", s.root, want) 232 } 233 // cd back to allow the temp directory to be removed 234 if err := os.Chdir(currDir); err != nil { 235 t.Fatal("error calling Chdir(), error=", err) 236 } 237 ctx := context.Background() 238 239 // validate layout 240 layoutFilePath := filepath.Join(tempDir, ocispec.ImageLayoutFile) 241 layoutFile, err := os.Open(layoutFilePath) 242 if err != nil { 243 t.Errorf("error opening layout file, error = %v", err) 244 } 245 defer layoutFile.Close() 246 247 var layout *ocispec.ImageLayout 248 err = json.NewDecoder(layoutFile).Decode(&layout) 249 if err != nil { 250 t.Fatal("error decoding layout, error =", err) 251 } 252 if want := ocispec.ImageLayoutVersion; layout.Version != want { 253 t.Errorf("layout.Version = %s, want %s", layout.Version, want) 254 } 255 256 // test push blob 257 err = s.Push(ctx, blobDesc, bytes.NewReader(blob)) 258 if err != nil { 259 t.Fatal("Store.Push() error =", err) 260 } 261 internalResolver := s.tagResolver 262 if got, want := len(internalResolver.Map()), 0; got != want { 263 t.Errorf("resolver.Map() = %v, want %v", got, want) 264 } 265 266 // test push manifest 267 err = s.Push(ctx, manifestDesc, bytes.NewReader(manifest)) 268 if err != nil { 269 t.Fatal("Store.Push() error =", err) 270 } 271 if got, want := len(internalResolver.Map()), 1; got != want { 272 t.Errorf("resolver.Map() = %v, want %v", got, want) 273 } 274 275 // test resolving blob by digest 276 gotDesc, err := s.Resolve(ctx, blobDesc.Digest.String()) 277 if err != nil { 278 t.Fatal("Store.Resolve() error =", err) 279 } 280 if want := blobDesc; gotDesc.Size != want.Size || gotDesc.Digest != want.Digest { 281 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, blobDesc) 282 } 283 284 // test resolving manifest by digest 285 gotDesc, err = s.Resolve(ctx, manifestDesc.Digest.String()) 286 if err != nil { 287 t.Fatal("Store.Resolve() error =", err) 288 } 289 if !reflect.DeepEqual(gotDesc, manifestDesc) { 290 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, manifestDesc) 291 } 292 293 // test tag 294 err = s.Tag(ctx, manifestDesc, ref) 295 if err != nil { 296 t.Fatal("Store.Tag() error =", err) 297 } 298 if got, want := len(internalResolver.Map()), 2; got != want { 299 t.Errorf("resolver.Map() = %v, want %v", got, want) 300 } 301 302 // test resolving manifest by tag 303 gotDesc, err = s.Resolve(ctx, ref) 304 if err != nil { 305 t.Fatal("Store.Resolve() error =", err) 306 } 307 if !reflect.DeepEqual(gotDesc, manifestDesc) { 308 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, manifestDesc) 309 } 310 311 // test fetch 312 exists, err := s.Exists(ctx, manifestDesc) 313 if err != nil { 314 t.Fatal("Store.Exists() error =", err) 315 } 316 if !exists { 317 t.Errorf("Store.Exists() = %v, want %v", exists, true) 318 } 319 320 rc, err := s.Fetch(ctx, manifestDesc) 321 if err != nil { 322 t.Fatal("Store.Fetch() error =", err) 323 } 324 got, err := io.ReadAll(rc) 325 if err != nil { 326 t.Fatal("Store.Fetch().Read() error =", err) 327 } 328 err = rc.Close() 329 if err != nil { 330 t.Error("Store.Fetch().Close() error =", err) 331 } 332 if !bytes.Equal(got, manifest) { 333 t.Errorf("Store.Fetch() = %v, want %v", got, manifest) 334 } 335 } 336 337 func TestStore_NotExistingRoot(t *testing.T) { 338 tempDir := t.TempDir() 339 root := filepath.Join(tempDir, "rootDir") 340 _, err := New(root) 341 if err != nil { 342 t.Fatal("New() error =", err) 343 } 344 345 // validate layout 346 layoutFilePath := filepath.Join(root, ocispec.ImageLayoutFile) 347 layoutFile, err := os.Open(layoutFilePath) 348 if err != nil { 349 t.Errorf("error opening layout file, error = %v", err) 350 } 351 defer layoutFile.Close() 352 353 var layout *ocispec.ImageLayout 354 err = json.NewDecoder(layoutFile).Decode(&layout) 355 if err != nil { 356 t.Fatal("error decoding layout, error =", err) 357 } 358 if want := ocispec.ImageLayoutVersion; layout.Version != want { 359 t.Errorf("layout.Version = %s, want %s", layout.Version, want) 360 } 361 362 // validate index.json 363 indexFilePath := filepath.Join(root, ociImageIndexFile) 364 indexFile, err := os.Open(indexFilePath) 365 if err != nil { 366 t.Errorf("error opening layout file, error = %v", err) 367 } 368 defer indexFile.Close() 369 370 var index *ocispec.Index 371 err = json.NewDecoder(indexFile).Decode(&index) 372 if err != nil { 373 t.Fatal("error decoding index.json, error =", err) 374 } 375 if want := 2; index.SchemaVersion != want { 376 t.Errorf("index.SchemaVersion = %v, want %v", index.SchemaVersion, want) 377 } 378 } 379 380 func TestStore_ContentNotFound(t *testing.T) { 381 content := []byte("hello world") 382 desc := ocispec.Descriptor{ 383 MediaType: "test", 384 Digest: digest.FromBytes(content), 385 Size: int64(len(content)), 386 } 387 388 tempDir := t.TempDir() 389 s, err := New(tempDir) 390 if err != nil { 391 t.Fatal("New() error =", err) 392 } 393 ctx := context.Background() 394 395 exists, err := s.Exists(ctx, desc) 396 if err != nil { 397 t.Error("Store.Exists() error =", err) 398 } 399 if exists { 400 t.Errorf("Store.Exists() = %v, want %v", exists, false) 401 } 402 403 _, err = s.Fetch(ctx, desc) 404 if !errors.Is(err, errdef.ErrNotFound) { 405 t.Errorf("Store.Fetch() error = %v, want %v", err, errdef.ErrNotFound) 406 } 407 } 408 409 func TestStore_ContentAlreadyExists(t *testing.T) { 410 content := []byte("hello world") 411 desc := ocispec.Descriptor{ 412 MediaType: "test", 413 Digest: digest.FromBytes(content), 414 Size: int64(len(content)), 415 } 416 417 tempDir := t.TempDir() 418 s, err := New(tempDir) 419 if err != nil { 420 t.Fatal("New() error =", err) 421 } 422 ctx := context.Background() 423 424 err = s.Push(ctx, desc, bytes.NewReader(content)) 425 if err != nil { 426 t.Fatal("Store.Push() error =", err) 427 } 428 429 err = s.Push(ctx, desc, bytes.NewReader(content)) 430 if !errors.Is(err, errdef.ErrAlreadyExists) { 431 t.Errorf("Store.Push() error = %v, want %v", err, errdef.ErrAlreadyExists) 432 } 433 } 434 435 func TestStore_ContentBadPush(t *testing.T) { 436 content := []byte("hello world") 437 desc := ocispec.Descriptor{ 438 MediaType: "test", 439 Digest: digest.FromBytes(content), 440 Size: int64(len(content)), 441 } 442 443 tempDir := t.TempDir() 444 s, err := New(tempDir) 445 if err != nil { 446 t.Fatal("New() error =", err) 447 } 448 ctx := context.Background() 449 450 err = s.Push(ctx, desc, strings.NewReader("foobar")) 451 if err == nil { 452 t.Errorf("Store.Push() error = %v, wantErr %v", err, true) 453 } 454 } 455 456 func TestStore_ResolveByTagReturnsFullDescriptor(t *testing.T) { 457 content := []byte("hello world") 458 ref := "hello-world:0.0.1" 459 annotations := map[string]string{"name": "Hello"} 460 desc := ocispec.Descriptor{ 461 MediaType: "test", 462 Digest: digest.FromBytes(content), 463 Size: int64(len(content)), 464 Annotations: annotations, 465 } 466 467 tempDir := t.TempDir() 468 s, err := New(tempDir) 469 if err != nil { 470 t.Fatal("New() error =", err) 471 } 472 ctx := context.Background() 473 474 err = s.Push(ctx, desc, bytes.NewReader(content)) 475 if err != nil { 476 t.Errorf("Store.Push() error = %v, wantErr %v", err, false) 477 } 478 479 err = s.Tag(ctx, desc, ref) 480 if err != nil { 481 t.Errorf("error tagging descriptor error = %v, wantErr %v", err, false) 482 } 483 484 resolvedDescr, err := s.Resolve(ctx, ref) 485 if err != nil { 486 t.Errorf("error resolving descriptor error = %v, wantErr %v", err, false) 487 } 488 489 if !reflect.DeepEqual(resolvedDescr, desc) { 490 t.Errorf("Store.Resolve() = %v, want %v", resolvedDescr, desc) 491 } 492 } 493 494 func TestStore_ResolveByDigestReturnsPlainDescriptor(t *testing.T) { 495 content := []byte("hello world") 496 ref := "hello-world:0.0.1" 497 desc := ocispec.Descriptor{ 498 MediaType: "test", 499 Digest: digest.FromBytes(content), 500 Size: int64(len(content)), 501 Annotations: map[string]string{"name": "Hello"}, 502 } 503 plainDescriptor := descriptor.Plain(desc) 504 505 tempDir := t.TempDir() 506 s, err := New(tempDir) 507 if err != nil { 508 t.Fatal("New() error =", err) 509 } 510 ctx := context.Background() 511 512 err = s.Push(ctx, desc, bytes.NewReader(content)) 513 if err != nil { 514 t.Errorf("Store.Push() error = %v, wantErr %v", err, false) 515 } 516 517 err = s.Tag(ctx, desc, ref) 518 if err != nil { 519 t.Errorf("error tagging descriptor error = %v, wantErr %v", err, false) 520 } 521 522 resolvedDescr, err := s.Resolve(ctx, string(desc.Digest)) 523 if err != nil { 524 t.Errorf("error resolving descriptor error = %v, wantErr %v", err, false) 525 } 526 527 if !reflect.DeepEqual(resolvedDescr, plainDescriptor) { 528 t.Errorf("Store.Resolve() = %v, want %v", resolvedDescr, plainDescriptor) 529 } 530 } 531 532 func TestStore_TagNotFound(t *testing.T) { 533 ref := "foobar" 534 535 tempDir := t.TempDir() 536 s, err := New(tempDir) 537 if err != nil { 538 t.Fatal("New() error =", err) 539 } 540 ctx := context.Background() 541 542 _, err = s.Resolve(ctx, ref) 543 if !errors.Is(err, errdef.ErrNotFound) { 544 t.Errorf("Store.Resolve() error = %v, want %v", err, errdef.ErrNotFound) 545 } 546 } 547 548 func TestStore_TagUnknownContent(t *testing.T) { 549 content := []byte("hello world") 550 desc := ocispec.Descriptor{ 551 MediaType: "test", 552 Digest: digest.FromBytes(content), 553 Size: int64(len(content)), 554 } 555 ref := "foobar" 556 557 tempDir := t.TempDir() 558 s, err := New(tempDir) 559 if err != nil { 560 t.Fatal("New() error =", err) 561 } 562 ctx := context.Background() 563 564 err = s.Tag(ctx, desc, ref) 565 if !errors.Is(err, errdef.ErrNotFound) { 566 t.Errorf("Store.Resolve() error = %v, want %v", err, errdef.ErrNotFound) 567 } 568 } 569 570 func TestStore_DisableAutoSaveIndex(t *testing.T) { 571 content := []byte(`{"layers":[]}`) 572 desc := ocispec.Descriptor{ 573 MediaType: ocispec.MediaTypeImageManifest, 574 Digest: digest.FromBytes(content), 575 Size: int64(len(content)), 576 } 577 ref := "foobar" 578 579 tempDir := t.TempDir() 580 s, err := New(tempDir) 581 if err != nil { 582 t.Fatal("New() error =", err) 583 } 584 // disable auto save 585 s.AutoSaveIndex = false 586 ctx := context.Background() 587 588 // validate layout 589 layoutFilePath := filepath.Join(tempDir, ocispec.ImageLayoutFile) 590 layoutFile, err := os.Open(layoutFilePath) 591 if err != nil { 592 t.Errorf("error opening layout file, error = %v", err) 593 } 594 defer layoutFile.Close() 595 596 var layout *ocispec.ImageLayout 597 err = json.NewDecoder(layoutFile).Decode(&layout) 598 if err != nil { 599 t.Fatal("error decoding layout, error =", err) 600 } 601 if want := ocispec.ImageLayoutVersion; layout.Version != want { 602 t.Errorf("layout.Version = %s, want %s", layout.Version, want) 603 } 604 605 // test push 606 err = s.Push(ctx, desc, bytes.NewReader(content)) 607 if err != nil { 608 t.Fatal("Store.Push() error =", err) 609 } 610 internalResolver := s.tagResolver 611 if got, want := len(internalResolver.Map()), 1; got != want { 612 t.Errorf("resolver.Map() = %v, want %v", got, want) 613 } 614 615 // test resolving by digest 616 gotDesc, err := s.Resolve(ctx, desc.Digest.String()) 617 if err != nil { 618 t.Fatal("Store.Resolve() error =", err) 619 } 620 if !reflect.DeepEqual(gotDesc, desc) { 621 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, desc) 622 } 623 624 // test tag 625 err = s.Tag(ctx, desc, ref) 626 if err != nil { 627 t.Fatal("Store.Tag() error =", err) 628 } 629 if got, want := len(internalResolver.Map()), 2; got != want { 630 t.Errorf("resolver.Map() = %v, want %v", got, want) 631 } 632 633 // test resolving by digest 634 gotDesc, err = s.Resolve(ctx, ref) 635 if err != nil { 636 t.Fatal("Store.Resolve() error =", err) 637 } 638 if !reflect.DeepEqual(gotDesc, desc) { 639 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, desc) 640 } 641 642 // test index file 643 if got, want := len(s.index.Manifests), 0; got != want { 644 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 645 } 646 if err := s.SaveIndex(); err != nil { 647 t.Fatal("Store.SaveIndex() error =", err) 648 } 649 // test index file again 650 if got, want := len(s.index.Manifests), 1; got != want { 651 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 652 } 653 if _, err := os.Stat(s.indexPath); err != nil { 654 t.Errorf("error: %s does not exist", s.indexPath) 655 } 656 } 657 658 func TestStore_RepeatTag(t *testing.T) { 659 tempDir := t.TempDir() 660 s, err := New(tempDir) 661 if err != nil { 662 t.Fatal("New() error =", err) 663 } 664 ctx := context.Background() 665 ref := "foobar" 666 667 // get internal resolver 668 internalResolver := s.tagResolver 669 670 // first tag a manifest 671 manifest := []byte(`{"layers":[]}`) 672 desc := content.NewDescriptorFromBytes(ocispec.MediaTypeImageManifest, manifest) 673 err = s.Push(ctx, desc, bytes.NewReader(manifest)) 674 if err != nil { 675 t.Fatal("Store.Push() error =", err) 676 } 677 if got, want := len(internalResolver.Map()), 1; got != want { 678 t.Errorf("len(resolver.Map()) = %v, want %v", got, want) 679 } 680 if got, want := len(s.index.Manifests), 1; got != want { 681 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 682 } 683 684 err = s.Tag(ctx, desc, ref) 685 if err != nil { 686 t.Fatal("Store.Tag() error =", err) 687 } 688 if got, want := len(internalResolver.Map()), 2; got != want { 689 t.Errorf("resolver.Map() = %v, want %v", got, want) 690 } 691 if got, want := len(s.index.Manifests), 1; got != want { 692 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 693 } 694 695 gotDesc, err := s.Resolve(ctx, desc.Digest.String()) 696 if err != nil { 697 t.Fatal("Store.Resolve() error =", err) 698 } 699 if !reflect.DeepEqual(gotDesc, desc) { 700 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, desc) 701 } 702 703 gotDesc, err = s.Resolve(ctx, ref) 704 if err != nil { 705 t.Fatal("Store.Resolve() error =", err) 706 } 707 if !reflect.DeepEqual(gotDesc, desc) { 708 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, desc) 709 } 710 711 // tag another manifest 712 manifest = []byte(`{"layers":[], "annotations":{}}`) 713 desc = content.NewDescriptorFromBytes(ocispec.MediaTypeImageManifest, manifest) 714 err = s.Push(ctx, desc, bytes.NewReader(manifest)) 715 if err != nil { 716 t.Fatal("Store.Push() error =", err) 717 } 718 if got, want := len(internalResolver.Map()), 3; got != want { 719 t.Errorf("resolver.Map() = %v, want %v", got, want) 720 } 721 if got, want := len(s.index.Manifests), 2; got != want { 722 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 723 } 724 725 err = s.Tag(ctx, desc, ref) 726 if err != nil { 727 t.Fatal("Store.Tag() error =", err) 728 } 729 if got, want := len(internalResolver.Map()), 3; got != want { 730 t.Errorf("resolver.Map() = %v, want %v", got, want) 731 } 732 if got, want := len(s.index.Manifests), 2; got != want { 733 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 734 } 735 736 gotDesc, err = s.Resolve(ctx, desc.Digest.String()) 737 if err != nil { 738 t.Fatal("Store.Resolve() error =", err) 739 } 740 if !reflect.DeepEqual(gotDesc, desc) { 741 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, desc) 742 } 743 744 gotDesc, err = s.Resolve(ctx, ref) 745 if err != nil { 746 t.Fatal("Store.Resolve() error =", err) 747 } 748 if !reflect.DeepEqual(gotDesc, desc) { 749 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, desc) 750 } 751 752 // tag a blob 753 blob := []byte("foobar") 754 desc = content.NewDescriptorFromBytes("test", blob) 755 err = s.Push(ctx, desc, bytes.NewReader(blob)) 756 if err != nil { 757 t.Fatal("Store.Push() error =", err) 758 } 759 if got, want := len(internalResolver.Map()), 3; got != want { 760 t.Errorf("resolver.Map() = %v, want %v", got, want) 761 } 762 if got, want := len(s.index.Manifests), 2; got != want { 763 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 764 } 765 766 err = s.Tag(ctx, desc, ref) 767 if err != nil { 768 t.Fatal("Store.Tag() error =", err) 769 } 770 if got, want := len(internalResolver.Map()), 4; got != want { 771 t.Errorf("resolver.Map() = %v, want %v", got, want) 772 } 773 if got, want := len(s.index.Manifests), 3; got != want { 774 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 775 } 776 777 gotDesc, err = s.Resolve(ctx, desc.Digest.String()) 778 if err != nil { 779 t.Fatal("Store.Resolve() error =", err) 780 } 781 if !reflect.DeepEqual(gotDesc, desc) { 782 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, desc) 783 } 784 785 gotDesc, err = s.Resolve(ctx, ref) 786 if err != nil { 787 t.Fatal("Store.Resolve() error =", err) 788 } 789 if !reflect.DeepEqual(gotDesc, desc) { 790 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, desc) 791 } 792 793 // tag another blob 794 blob = []byte("barfoo") 795 desc = content.NewDescriptorFromBytes("test", blob) 796 err = s.Push(ctx, desc, bytes.NewReader(blob)) 797 if err != nil { 798 t.Fatal("Store.Push() error =", err) 799 } 800 if got, want := len(internalResolver.Map()), 4; got != want { 801 t.Errorf("resolver.Map() = %v, want %v", got, want) 802 } 803 if got, want := len(s.index.Manifests), 3; got != want { 804 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 805 } 806 807 err = s.Tag(ctx, desc, ref) 808 if err != nil { 809 t.Fatal("Store.Tag() error =", err) 810 } 811 if got, want := len(internalResolver.Map()), 5; got != want { 812 t.Errorf("resolver.Map() = %v, want %v", got, want) 813 } 814 if got, want := len(s.index.Manifests), 4; got != want { 815 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 816 } 817 818 gotDesc, err = s.Resolve(ctx, desc.Digest.String()) 819 if err != nil { 820 t.Fatal("Store.Resolve() error =", err) 821 } 822 if !reflect.DeepEqual(gotDesc, desc) { 823 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, desc) 824 } 825 826 gotDesc, err = s.Resolve(ctx, ref) 827 if err != nil { 828 t.Fatal("Store.Resolve() error =", err) 829 } 830 if !reflect.DeepEqual(gotDesc, desc) { 831 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, desc) 832 } 833 } 834 835 // Related bug: https://github.com/oras-project/oras-go/issues/461 836 func TestStore_TagByDigest(t *testing.T) { 837 tempDir := t.TempDir() 838 s, err := New(tempDir) 839 if err != nil { 840 t.Fatal("New() error =", err) 841 } 842 ctx := context.Background() 843 844 // get internal resolver 845 internalResolver := s.tagResolver 846 847 manifest := []byte(`{"layers":[]}`) 848 manifestDesc := content.NewDescriptorFromBytes(ocispec.MediaTypeImageManifest, manifest) 849 850 // push a manifest 851 err = s.Push(ctx, manifestDesc, bytes.NewReader(manifest)) 852 if err != nil { 853 t.Fatal("Store.Push() error =", err) 854 } 855 if got, want := len(internalResolver.Map()), 1; got != want { 856 t.Errorf("len(resolver.Map()) = %v, want %v", got, want) 857 } 858 if got, want := len(s.index.Manifests), 1; got != want { 859 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 860 } 861 gotDesc, err := s.Resolve(ctx, manifestDesc.Digest.String()) 862 if err != nil { 863 t.Fatal("Store.Resolve() error =", err) 864 } 865 if !reflect.DeepEqual(gotDesc, manifestDesc) { 866 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, manifestDesc) 867 } 868 869 // tag manifest by digest 870 err = s.Tag(ctx, manifestDesc, manifestDesc.Digest.String()) 871 if err != nil { 872 t.Fatal("Store.Tag() error =", err) 873 } 874 if got, want := len(internalResolver.Map()), 1; got != want { 875 t.Errorf("len(resolver.Map()) = %v, want %v", got, want) 876 } 877 if got, want := len(s.index.Manifests), 1; got != want { 878 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 879 } 880 gotDesc, err = s.Resolve(ctx, manifestDesc.Digest.String()) 881 if err != nil { 882 t.Fatal("Store.Resolve() error =", err) 883 } 884 if !reflect.DeepEqual(gotDesc, manifestDesc) { 885 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, manifestDesc) 886 } 887 888 // push a blob 889 blob := []byte("foobar") 890 blobDesc := content.NewDescriptorFromBytes("test", blob) 891 err = s.Push(ctx, blobDesc, bytes.NewReader(blob)) 892 if err != nil { 893 t.Fatal("Store.Push() error =", err) 894 } 895 if got, want := len(internalResolver.Map()), 1; got != want { 896 t.Errorf("resolver.Map() = %v, want %v", got, want) 897 } 898 if got, want := len(s.index.Manifests), 1; got != want { 899 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 900 } 901 gotDesc, err = s.Resolve(ctx, blobDesc.Digest.String()) 902 if err != nil { 903 t.Fatal("Store.Resolve() error =", err) 904 } 905 if gotDesc.Digest != blobDesc.Digest || gotDesc.Size != blobDesc.Size { 906 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, blobDesc) 907 } 908 909 // tag blob by digest 910 err = s.Tag(ctx, blobDesc, blobDesc.Digest.String()) 911 if err != nil { 912 t.Fatal("Store.Tag() error =", err) 913 } 914 if got, want := len(internalResolver.Map()), 2; got != want { 915 t.Errorf("resolver.Map() = %v, want %v", got, want) 916 } 917 if got, want := len(s.index.Manifests), 2; got != want { 918 t.Errorf("len(index.Manifests) = %v, want %v", got, want) 919 } 920 gotDesc, err = s.Resolve(ctx, blobDesc.Digest.String()) 921 if err != nil { 922 t.Fatal("Store.Resolve() error =", err) 923 } 924 if !reflect.DeepEqual(gotDesc, blobDesc) { 925 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, blobDesc) 926 } 927 } 928 929 func TestStore_Untag(t *testing.T) { 930 content := []byte("hello world") 931 ref := "hello-world:0.0.1" 932 desc := ocispec.Descriptor{ 933 MediaType: "test", 934 Digest: digest.FromBytes(content), 935 Size: int64(len(content)), 936 } 937 938 tempDir := t.TempDir() 939 s, err := New(tempDir) 940 if err != nil { 941 t.Fatal("New() error =", err) 942 } 943 ctx := context.Background() 944 945 err = s.Push(ctx, desc, bytes.NewReader(content)) 946 if err != nil { 947 t.Errorf("Store.Push() error = %v, wantErr %v", err, false) 948 } 949 950 err = s.Tag(ctx, desc, ref) 951 if err != nil { 952 t.Errorf("error tagging descriptor error = %v, wantErr %v", err, false) 953 } 954 955 if len(s.tagResolver.Map()) == 0 { 956 t.Error("tagresolver map should not be empty") 957 } 958 959 resolvedDescr, err := s.Resolve(ctx, string(desc.Digest)) 960 if err != nil { 961 t.Errorf("error resolving descriptor error = %v, wantErr %v", err, false) 962 } 963 964 if !reflect.DeepEqual(resolvedDescr, desc) { 965 t.Errorf("Store.Resolve() = %v, want %v", resolvedDescr, desc) 966 } 967 968 err = s.Untag(ctx, resolvedDescr, ref) 969 if err != nil { 970 t.Errorf("error untagging descriptor error = %v, wantErr %v", err, false) 971 } 972 973 if len(s.tagResolver.Map()) > 0 { 974 t.Error("tagresolver map should be empty") 975 } 976 } 977 978 func TestStore_BadIndex(t *testing.T) { 979 tempDir := t.TempDir() 980 content := []byte("whatever") 981 path := filepath.Join(tempDir, ociImageIndexFile) 982 os.WriteFile(path, content, 0666) 983 984 _, err := New(tempDir) 985 if err == nil { 986 t.Errorf("New() error = nil, want: error") 987 } 988 } 989 990 func TestStore_BadLayout(t *testing.T) { 991 tempDir := t.TempDir() 992 content := []byte("whatever") 993 path := filepath.Join(tempDir, ocispec.ImageLayoutFile) 994 os.WriteFile(path, content, 0666) 995 996 _, err := New(tempDir) 997 if err == nil { 998 t.Errorf("New() error = nil, want: error") 999 } 1000 } 1001 1002 func TestStore_Predecessors(t *testing.T) { 1003 tempDir := t.TempDir() 1004 s, err := New(tempDir) 1005 if err != nil { 1006 t.Fatal("New() error =", err) 1007 } 1008 ctx := context.Background() 1009 1010 // generate test content 1011 var blobs [][]byte 1012 var descs []ocispec.Descriptor 1013 appendBlob := func(mediaType string, blob []byte) { 1014 blobs = append(blobs, blob) 1015 descs = append(descs, ocispec.Descriptor{ 1016 MediaType: mediaType, 1017 Digest: digest.FromBytes(blob), 1018 Size: int64(len(blob)), 1019 }) 1020 } 1021 generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) { 1022 manifest := ocispec.Manifest{ 1023 Config: config, 1024 Layers: layers, 1025 } 1026 manifestJSON, err := json.Marshal(manifest) 1027 if err != nil { 1028 t.Fatal(err) 1029 } 1030 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 1031 } 1032 generateIndex := func(manifests ...ocispec.Descriptor) { 1033 index := ocispec.Index{ 1034 Manifests: manifests, 1035 } 1036 indexJSON, err := json.Marshal(index) 1037 if err != nil { 1038 t.Fatal(err) 1039 } 1040 appendBlob(ocispec.MediaTypeImageIndex, indexJSON) 1041 } 1042 generateArtifactManifest := func(subject ocispec.Descriptor, blobs ...ocispec.Descriptor) { 1043 var manifest ocispec.Artifact 1044 manifest.Subject = &subject 1045 manifest.Blobs = append(manifest.Blobs, blobs...) 1046 manifestJSON, err := json.Marshal(manifest) 1047 if err != nil { 1048 t.Fatal(err) 1049 } 1050 appendBlob(ocispec.MediaTypeArtifactManifest, manifestJSON) 1051 } 1052 1053 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 1054 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1 1055 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2 1056 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello")) // Blob 3 1057 generateManifest(descs[0], descs[1:3]...) // Blob 4 1058 generateManifest(descs[0], descs[3]) // Blob 5 1059 generateManifest(descs[0], descs[1:4]...) // Blob 6 1060 generateIndex(descs[4:6]...) // Blob 7 1061 generateIndex(descs[6]) // Blob 8 1062 generateIndex() // Blob 9 1063 generateIndex(descs[7:10]...) // Blob 10 1064 appendBlob(ocispec.MediaTypeImageLayer, []byte("sig_1")) // Blob 11 1065 generateArtifactManifest(descs[6], descs[11]) // Blob 12 1066 appendBlob(ocispec.MediaTypeImageLayer, []byte("sig_2")) // Blob 13 1067 generateArtifactManifest(descs[10], descs[13]) // Blob 14 1068 1069 eg, egCtx := errgroup.WithContext(ctx) 1070 for i := range blobs { 1071 eg.Go(func(i int) func() error { 1072 return func() error { 1073 err := s.Push(egCtx, descs[i], bytes.NewReader(blobs[i])) 1074 if err != nil { 1075 return fmt.Errorf("failed to push test content to src: %d: %v", i, err) 1076 } 1077 return nil 1078 } 1079 }(i)) 1080 } 1081 if err := eg.Wait(); err != nil { 1082 t.Fatal(err) 1083 } 1084 1085 // verify predecessors 1086 wants := [][]ocispec.Descriptor{ 1087 descs[4:7], // Blob 0 1088 {descs[4], descs[6]}, // Blob 1 1089 {descs[4], descs[6]}, // Blob 2 1090 {descs[5], descs[6]}, // Blob 3 1091 {descs[7]}, // Blob 4 1092 {descs[7]}, // Blob 5 1093 {descs[8], descs[12]}, // Blob 6 1094 {descs[10]}, // Blob 7 1095 {descs[10]}, // Blob 8 1096 {descs[10]}, // Blob 9 1097 {descs[14]}, // Blob 10 1098 {descs[12]}, // Blob 11 1099 nil, // Blob 12 1100 {descs[14]}, // Blob 13 1101 nil, // Blob 14 1102 } 1103 for i, want := range wants { 1104 predecessors, err := s.Predecessors(ctx, descs[i]) 1105 if err != nil { 1106 t.Errorf("Store.Predecessors(%d) error = %v", i, err) 1107 } 1108 if !equalDescriptorSet(predecessors, want) { 1109 t.Errorf("Store.Predecessors(%d) = %v, want %v", i, predecessors, want) 1110 } 1111 } 1112 } 1113 1114 func TestStore_ExistingStore(t *testing.T) { 1115 tempDir := t.TempDir() 1116 s, err := New(tempDir) 1117 if err != nil { 1118 t.Fatal("New() error =", err) 1119 } 1120 1121 // generate test content 1122 var blobs [][]byte 1123 var descs []ocispec.Descriptor 1124 appendBlob := func(mediaType string, blob []byte) { 1125 blobs = append(blobs, blob) 1126 descs = append(descs, ocispec.Descriptor{ 1127 MediaType: mediaType, 1128 Digest: digest.FromBytes(blob), 1129 Size: int64(len(blob)), 1130 }) 1131 } 1132 generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) { 1133 manifest := ocispec.Manifest{ 1134 Config: config, 1135 Layers: layers, 1136 } 1137 manifestJSON, err := json.Marshal(manifest) 1138 if err != nil { 1139 t.Fatal(err) 1140 } 1141 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 1142 } 1143 generateIndex := func(manifests ...ocispec.Descriptor) { 1144 index := ocispec.Index{ 1145 Manifests: manifests, 1146 } 1147 indexJSON, err := json.Marshal(index) 1148 if err != nil { 1149 t.Fatal(err) 1150 } 1151 appendBlob(ocispec.MediaTypeImageIndex, indexJSON) 1152 } 1153 1154 generateArtifactManifest := func(subject ocispec.Descriptor, blobs ...ocispec.Descriptor) { 1155 var manifest ocispec.Artifact 1156 manifest.Subject = &subject 1157 manifest.Blobs = append(manifest.Blobs, blobs...) 1158 manifestJSON, err := json.Marshal(manifest) 1159 if err != nil { 1160 t.Fatal(err) 1161 } 1162 appendBlob(ocispec.MediaTypeArtifactManifest, manifestJSON) 1163 } 1164 1165 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 1166 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1 1167 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2 1168 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello")) // Blob 3 1169 generateManifest(descs[0], descs[1:3]...) // Blob 4 1170 generateManifest(descs[0], descs[3]) // Blob 5 1171 generateManifest(descs[0], descs[1:4]...) // Blob 6 1172 generateIndex(descs[4:6]...) // Blob 7 1173 generateIndex(descs[6]) // Blob 8 1174 generateIndex() // Blob 9 1175 generateIndex(descs[7:10]...) // Blob 10 1176 appendBlob(ocispec.MediaTypeImageLayer, []byte("sig_1")) // Blob 11 1177 generateArtifactManifest(descs[6], descs[11]) // Blob 12 1178 appendBlob(ocispec.MediaTypeImageLayer, []byte("sig_2")) // Blob 13 1179 generateArtifactManifest(descs[10], descs[13]) // Blob 14 1180 1181 ctx := context.Background() 1182 eg, egCtx := errgroup.WithContext(ctx) 1183 for i := range blobs { 1184 eg.Go(func(i int) func() error { 1185 return func() error { 1186 err := s.Push(egCtx, descs[i], bytes.NewReader(blobs[i])) 1187 if err != nil { 1188 return fmt.Errorf("failed to push test content to src: %d: %v", i, err) 1189 } 1190 return nil 1191 } 1192 }(i)) 1193 } 1194 if err := eg.Wait(); err != nil { 1195 t.Fatal(err) 1196 } 1197 1198 // tag index root 1199 indexRoot := descs[10] 1200 tag := "latest" 1201 if err := s.Tag(ctx, indexRoot, tag); err != nil { 1202 t.Fatal("Tag() error =", err) 1203 } 1204 // tag index root by digest 1205 // related bug: https://github.com/oras-project/oras-go/issues/461 1206 if err := s.Tag(ctx, indexRoot, indexRoot.Digest.String()); err != nil { 1207 t.Fatal("Tag() error =", err) 1208 } 1209 1210 // test with another OCI store instance to mock loading from an existing store 1211 anotherS, err := New(tempDir) 1212 if err != nil { 1213 t.Fatal("New() error =", err) 1214 } 1215 1216 // test resolving index root by tag 1217 gotDesc, err := anotherS.Resolve(ctx, tag) 1218 if err != nil { 1219 t.Fatal("Store: Resolve() error =", err) 1220 } 1221 if !content.Equal(gotDesc, indexRoot) { 1222 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, indexRoot) 1223 } 1224 1225 // test resolving index root by digest 1226 gotDesc, err = anotherS.Resolve(ctx, indexRoot.Digest.String()) 1227 if err != nil { 1228 t.Fatal("Store: Resolve() error =", err) 1229 } 1230 if !reflect.DeepEqual(gotDesc, indexRoot) { 1231 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, indexRoot) 1232 } 1233 1234 // test resolving artifact manifest by digest 1235 artifactRootDesc := descs[12] 1236 gotDesc, err = anotherS.Resolve(ctx, artifactRootDesc.Digest.String()) 1237 if err != nil { 1238 t.Fatal("Store: Resolve() error =", err) 1239 } 1240 if !reflect.DeepEqual(gotDesc, artifactRootDesc) { 1241 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, artifactRootDesc) 1242 } 1243 1244 // test resolving blob by digest 1245 gotDesc, err = anotherS.Resolve(ctx, descs[0].Digest.String()) 1246 if err != nil { 1247 t.Fatal("Store.Resolve() error =", err) 1248 } 1249 if want := descs[0]; gotDesc.Size != want.Size || gotDesc.Digest != want.Digest { 1250 t.Errorf("Store.Resolve() = %v, want %v", gotDesc, want) 1251 } 1252 1253 // test fetching OCI root index 1254 exists, err := anotherS.Exists(ctx, indexRoot) 1255 if err != nil { 1256 t.Fatal("Store.Exists() error =", err) 1257 } 1258 if !exists { 1259 t.Errorf("Store.Exists() = %v, want %v", exists, true) 1260 } 1261 1262 // test fetching blobs 1263 for i := range blobs { 1264 eg.Go(func(i int) func() error { 1265 return func() error { 1266 rc, err := s.Fetch(egCtx, descs[i]) 1267 if err != nil { 1268 return fmt.Errorf("Store.Fetch(%d) error = %v", i, err) 1269 } 1270 got, err := io.ReadAll(rc) 1271 if err != nil { 1272 return fmt.Errorf("Store.Fetch(%d).Read() error = %v", i, err) 1273 } 1274 err = rc.Close() 1275 if err != nil { 1276 return fmt.Errorf("Store.Fetch(%d).Close() error = %v", i, err) 1277 } 1278 if !bytes.Equal(got, blobs[i]) { 1279 return fmt.Errorf("Store.Fetch(%d) = %v, want %v", i, got, blobs[i]) 1280 } 1281 return nil 1282 } 1283 }(i)) 1284 } 1285 if err := eg.Wait(); err != nil { 1286 t.Fatal(err) 1287 } 1288 1289 // verify predecessors 1290 wants := [][]ocispec.Descriptor{ 1291 descs[4:7], // Blob 0 1292 {descs[4], descs[6]}, // Blob 1 1293 {descs[4], descs[6]}, // Blob 2 1294 {descs[5], descs[6]}, // Blob 3 1295 {descs[7]}, // Blob 4 1296 {descs[7]}, // Blob 5 1297 {descs[8], descs[12]}, // Blob 6 1298 {descs[10]}, // Blob 7 1299 {descs[10]}, // Blob 8 1300 {descs[10]}, // Blob 9 1301 {descs[14]}, // Blob 10 1302 {descs[12]}, // Blob 11 1303 nil, // Blob 12, no predecessors 1304 {descs[14]}, // Blob 13 1305 nil, // Blob 14, no predecessors 1306 } 1307 for i, want := range wants { 1308 predecessors, err := anotherS.Predecessors(ctx, descs[i]) 1309 if err != nil { 1310 t.Errorf("Store.Predecessors(%d) error = %v", i, err) 1311 } 1312 if !equalDescriptorSet(predecessors, want) { 1313 t.Errorf("Store.Predecessors(%d) = %v, want %v", i, predecessors, want) 1314 } 1315 } 1316 } 1317 1318 func Test_ExistingStore_Retag(t *testing.T) { 1319 tempDir := t.TempDir() 1320 s, err := New(tempDir) 1321 if err != nil { 1322 t.Fatal("New() error =", err) 1323 } 1324 ctx := context.Background() 1325 1326 manifest_1 := []byte(`{"layers":[]}`) 1327 manifestDesc_1 := content.NewDescriptorFromBytes(ocispec.MediaTypeImageManifest, manifest_1) 1328 manifestDesc_1.Annotations = map[string]string{"key1": "val1"} 1329 1330 // push a manifest 1331 err = s.Push(ctx, manifestDesc_1, bytes.NewReader(manifest_1)) 1332 if err != nil { 1333 t.Fatal("Store.Push() error =", err) 1334 } 1335 // tag manifest by digest 1336 err = s.Tag(ctx, manifestDesc_1, manifestDesc_1.Digest.String()) 1337 if err != nil { 1338 t.Fatal("Store.Tag() error =", err) 1339 } 1340 // tag manifest by tag 1341 ref := "foobar" 1342 err = s.Tag(ctx, manifestDesc_1, ref) 1343 if err != nil { 1344 t.Fatal("Store.Tag() error =", err) 1345 } 1346 1347 // verify index 1348 want := []ocispec.Descriptor{ 1349 { 1350 MediaType: manifestDesc_1.MediaType, 1351 Digest: manifestDesc_1.Digest, 1352 Size: manifestDesc_1.Size, 1353 Annotations: map[string]string{ 1354 "key1": "val1", 1355 ocispec.AnnotationRefName: ref, 1356 }, 1357 }, 1358 } 1359 if got := s.index.Manifests; !equalDescriptorSet(got, want) { 1360 t.Errorf("index.Manifests = %v, want %v", got, want) 1361 } 1362 1363 // test with another OCI store instance to mock loading from an existing store 1364 anotherS, err := New(tempDir) 1365 if err != nil { 1366 t.Fatal("New() error =", err) 1367 } 1368 manifest_2 := []byte(`{"layers":[], "annotations":{}}`) 1369 manifestDesc_2 := content.NewDescriptorFromBytes(ocispec.MediaTypeImageManifest, manifest_2) 1370 manifestDesc_2.Annotations = map[string]string{"key2": "val2"} 1371 1372 err = anotherS.Push(ctx, manifestDesc_2, bytes.NewReader(manifest_2)) 1373 if err != nil { 1374 t.Fatal("Store.Push() error =", err) 1375 } 1376 err = anotherS.Tag(ctx, manifestDesc_2, ref) 1377 if err != nil { 1378 t.Fatal("Store.Tag() error =", err) 1379 } 1380 1381 // verify index 1382 want = []ocispec.Descriptor{ 1383 { 1384 MediaType: manifestDesc_1.MediaType, 1385 Digest: manifestDesc_1.Digest, 1386 Size: manifestDesc_1.Size, 1387 Annotations: map[string]string{ 1388 "key1": "val1", 1389 }, 1390 }, 1391 { 1392 MediaType: manifestDesc_2.MediaType, 1393 Digest: manifestDesc_2.Digest, 1394 Size: manifestDesc_2.Size, 1395 Annotations: map[string]string{ 1396 "key2": "val2", 1397 ocispec.AnnotationRefName: ref, 1398 }, 1399 }, 1400 } 1401 if got := anotherS.index.Manifests; !equalDescriptorSet(got, want) { 1402 t.Errorf("index.Manifests = %v, want %v", got, want) 1403 } 1404 } 1405 1406 func TestCopy_MemoryToOCI_FullCopy(t *testing.T) { 1407 src := memory.New() 1408 1409 tempDir := t.TempDir() 1410 dst, err := New(tempDir) 1411 if err != nil { 1412 t.Fatal("OCI.New() error =", err) 1413 } 1414 1415 // generate test content 1416 var blobs [][]byte 1417 var descs []ocispec.Descriptor 1418 appendBlob := func(mediaType string, blob []byte) { 1419 blobs = append(blobs, blob) 1420 descs = append(descs, ocispec.Descriptor{ 1421 MediaType: mediaType, 1422 Digest: digest.FromBytes(blob), 1423 Size: int64(len(blob)), 1424 }) 1425 } 1426 generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) { 1427 manifest := ocispec.Manifest{ 1428 Config: config, 1429 Layers: layers, 1430 } 1431 manifestJSON, err := json.Marshal(manifest) 1432 if err != nil { 1433 t.Fatal(err) 1434 } 1435 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 1436 } 1437 1438 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 1439 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1 1440 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2 1441 generateManifest(descs[0], descs[1:3]...) // Blob 3 1442 1443 ctx := context.Background() 1444 for i := range blobs { 1445 err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 1446 if err != nil { 1447 t.Fatalf("failed to push test content to src: %d: %v", i, err) 1448 } 1449 } 1450 1451 root := descs[3] 1452 ref := "foobar" 1453 err = src.Tag(ctx, root, ref) 1454 if err != nil { 1455 t.Fatal("fail to tag root node", err) 1456 } 1457 1458 // test copy 1459 gotDesc, err := oras.Copy(ctx, src, ref, dst, "", oras.CopyOptions{}) 1460 if err != nil { 1461 t.Fatalf("Copy() error = %v, wantErr %v", err, false) 1462 } 1463 if !reflect.DeepEqual(gotDesc, root) { 1464 t.Errorf("Copy() = %v, want %v", gotDesc, root) 1465 } 1466 1467 // verify contents 1468 for i, desc := range descs { 1469 exists, err := dst.Exists(ctx, desc) 1470 if err != nil { 1471 t.Fatalf("dst.Exists(%d) error = %v", i, err) 1472 } 1473 if !exists { 1474 t.Errorf("dst.Exists(%d) = %v, want %v", i, exists, true) 1475 } 1476 } 1477 1478 // verify tag 1479 gotDesc, err = dst.Resolve(ctx, ref) 1480 if err != nil { 1481 t.Fatal("dst.Resolve() error =", err) 1482 } 1483 if !reflect.DeepEqual(gotDesc, root) { 1484 t.Errorf("dst.Resolve() = %v, want %v", gotDesc, root) 1485 } 1486 } 1487 1488 func TestCopyGraph_MemoryToOCI_FullCopy(t *testing.T) { 1489 src := cas.NewMemory() 1490 1491 tempDir := t.TempDir() 1492 dst, err := New(tempDir) 1493 if err != nil { 1494 t.Fatal("OCI.New() error =", err) 1495 } 1496 1497 // generate test content 1498 var blobs [][]byte 1499 var descs []ocispec.Descriptor 1500 appendBlob := func(mediaType string, blob []byte) { 1501 blobs = append(blobs, blob) 1502 descs = append(descs, ocispec.Descriptor{ 1503 MediaType: mediaType, 1504 Digest: digest.FromBytes(blob), 1505 Size: int64(len(blob)), 1506 }) 1507 } 1508 generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) { 1509 manifest := ocispec.Manifest{ 1510 Config: config, 1511 Layers: layers, 1512 } 1513 manifestJSON, err := json.Marshal(manifest) 1514 if err != nil { 1515 t.Fatal(err) 1516 } 1517 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 1518 } 1519 generateIndex := func(manifests ...ocispec.Descriptor) { 1520 index := ocispec.Index{ 1521 Manifests: manifests, 1522 } 1523 indexJSON, err := json.Marshal(index) 1524 if err != nil { 1525 t.Fatal(err) 1526 } 1527 appendBlob(ocispec.MediaTypeImageIndex, indexJSON) 1528 } 1529 1530 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 1531 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1 1532 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2 1533 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello")) // Blob 3 1534 generateManifest(descs[0], descs[1:3]...) // Blob 4 1535 generateManifest(descs[0], descs[3]) // Blob 5 1536 generateManifest(descs[0], descs[1:4]...) // Blob 6 1537 generateIndex(descs[4:6]...) // Blob 7 1538 generateIndex(descs[6]) // Blob 8 1539 generateIndex() // Blob 9 1540 generateIndex(descs[7:10]...) // Blob 10 1541 1542 ctx := context.Background() 1543 for i := range blobs { 1544 err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 1545 if err != nil { 1546 t.Fatalf("failed to push test content to src: %d: %v", i, err) 1547 } 1548 } 1549 1550 // test copy 1551 srcTracker := &storageTracker{Storage: src} 1552 dstTracker := &storageTracker{Storage: dst} 1553 root := descs[len(descs)-1] 1554 if err := oras.CopyGraph(ctx, srcTracker, dstTracker, root, oras.CopyGraphOptions{}); err != nil { 1555 t.Fatalf("CopyGraph() error = %v, wantErr %v", err, false) 1556 } 1557 1558 // verify contents 1559 for i := range blobs { 1560 got, err := content.FetchAll(ctx, dst, descs[i]) 1561 if err != nil { 1562 t.Errorf("content[%d] error = %v, wantErr %v", i, err, false) 1563 continue 1564 } 1565 if want := blobs[i]; !bytes.Equal(got, want) { 1566 t.Errorf("content[%d] = %v, want %v", i, got, want) 1567 } 1568 } 1569 1570 // verify API counts 1571 if got, want := srcTracker.fetch, int64(len(blobs)); got != want { 1572 t.Errorf("count(src.Fetch()) = %v, want %v", got, want) 1573 } 1574 if got, want := srcTracker.push, int64(0); got != want { 1575 t.Errorf("count(src.Push()) = %v, want %v", got, want) 1576 } 1577 if got, want := srcTracker.exists, int64(0); got != want { 1578 t.Errorf("count(src.Exists()) = %v, want %v", got, want) 1579 } 1580 if got, want := dstTracker.fetch, int64(0); got != want { 1581 t.Errorf("count(dst.Fetch()) = %v, want %v", got, want) 1582 } 1583 if got, want := dstTracker.push, int64(len(blobs)); got != want { 1584 t.Errorf("count(dst.Push()) = %v, want %v", got, want) 1585 } 1586 if got, want := dstTracker.exists, int64(len(blobs)); got != want { 1587 t.Errorf("count(dst.Exists()) = %v, want %v", got, want) 1588 } 1589 } 1590 1591 func TestCopyGraph_MemoryToOCI_PartialCopy(t *testing.T) { 1592 src := cas.NewMemory() 1593 1594 tempDir := t.TempDir() 1595 dst, err := New(tempDir) 1596 if err != nil { 1597 t.Fatal("OCI.New() error =", err) 1598 } 1599 1600 // generate test content 1601 var blobs [][]byte 1602 var descs []ocispec.Descriptor 1603 appendBlob := func(mediaType string, blob []byte) { 1604 blobs = append(blobs, blob) 1605 descs = append(descs, ocispec.Descriptor{ 1606 MediaType: mediaType, 1607 Digest: digest.FromBytes(blob), 1608 Size: int64(len(blob)), 1609 }) 1610 } 1611 generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) { 1612 manifest := ocispec.Manifest{ 1613 Config: config, 1614 Layers: layers, 1615 } 1616 manifestJSON, err := json.Marshal(manifest) 1617 if err != nil { 1618 t.Fatal(err) 1619 } 1620 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 1621 } 1622 generateIndex := func(manifests ...ocispec.Descriptor) { 1623 index := ocispec.Index{ 1624 Manifests: manifests, 1625 } 1626 indexJSON, err := json.Marshal(index) 1627 if err != nil { 1628 t.Fatal(err) 1629 } 1630 appendBlob(ocispec.MediaTypeImageIndex, indexJSON) 1631 } 1632 1633 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 1634 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1 1635 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2 1636 generateManifest(descs[0], descs[1:3]...) // Blob 3 1637 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello")) // Blob 4 1638 generateManifest(descs[0], descs[4]) // Blob 5 1639 generateIndex(descs[3], descs[5]) // Blob 6 1640 1641 ctx := context.Background() 1642 for i := range blobs { 1643 err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 1644 if err != nil { 1645 t.Fatalf("failed to push test content to src: %d: %v", i, err) 1646 } 1647 } 1648 1649 // initial copy 1650 root := descs[3] 1651 if err := oras.CopyGraph(ctx, src, dst, root, oras.CopyGraphOptions{}); err != nil { 1652 t.Fatalf("CopyGraph() error = %v, wantErr %v", err, false) 1653 } 1654 // verify contents 1655 for i := range blobs[:4] { 1656 got, err := content.FetchAll(ctx, dst, descs[i]) 1657 if err != nil { 1658 t.Fatalf("content[%d] error = %v, wantErr %v", i, err, false) 1659 } 1660 if want := blobs[i]; !bytes.Equal(got, want) { 1661 t.Fatalf("content[%d] = %v, want %v", i, got, want) 1662 } 1663 } 1664 1665 // test copy 1666 srcTracker := &storageTracker{Storage: src} 1667 dstTracker := &storageTracker{Storage: dst} 1668 root = descs[len(descs)-1] 1669 if err := oras.CopyGraph(ctx, srcTracker, dstTracker, root, oras.CopyGraphOptions{}); err != nil { 1670 t.Fatalf("CopyGraph() error = %v, wantErr %v", err, false) 1671 } 1672 1673 // verify contents 1674 for i := range blobs { 1675 got, err := content.FetchAll(ctx, dst, descs[i]) 1676 if err != nil { 1677 t.Errorf("content[%d] error = %v, wantErr %v", i, err, false) 1678 continue 1679 } 1680 if want := blobs[i]; !bytes.Equal(got, want) { 1681 t.Errorf("content[%d] = %v, want %v", i, got, want) 1682 } 1683 } 1684 1685 // verify API counts 1686 if got, want := srcTracker.fetch, int64(3); got != want { 1687 t.Errorf("count(src.Fetch()) = %v, want %v", got, want) 1688 } 1689 if got, want := srcTracker.push, int64(0); got != want { 1690 t.Errorf("count(src.Push()) = %v, want %v", got, want) 1691 } 1692 if got, want := srcTracker.exists, int64(0); got != want { 1693 t.Errorf("count(src.Exists()) = %v, want %v", got, want) 1694 } 1695 if got, want := dstTracker.fetch, int64(0); got != want { 1696 t.Errorf("count(dst.Fetch()) = %v, want %v", got, want) 1697 } 1698 if got, want := dstTracker.push, int64(3); got != want { 1699 t.Errorf("count(dst.Push()) = %v, want %v", got, want) 1700 } 1701 if got, want := dstTracker.exists, int64(5); got != want { 1702 t.Errorf("count(dst.Exists()) = %v, want %v", got, want) 1703 } 1704 } 1705 1706 func TestCopyGraph_OCIToMemory_FullCopy(t *testing.T) { 1707 tempDir := t.TempDir() 1708 src, err := New(tempDir) 1709 if err != nil { 1710 t.Fatal("OCI.New() error =", err) 1711 } 1712 1713 dst := cas.NewMemory() 1714 1715 // generate test content 1716 var blobs [][]byte 1717 var descs []ocispec.Descriptor 1718 appendBlob := func(mediaType string, blob []byte) { 1719 blobs = append(blobs, blob) 1720 descs = append(descs, ocispec.Descriptor{ 1721 MediaType: mediaType, 1722 Digest: digest.FromBytes(blob), 1723 Size: int64(len(blob)), 1724 }) 1725 } 1726 generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) { 1727 manifest := ocispec.Manifest{ 1728 Config: config, 1729 Layers: layers, 1730 } 1731 manifestJSON, err := json.Marshal(manifest) 1732 if err != nil { 1733 t.Fatal(err) 1734 } 1735 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 1736 } 1737 generateIndex := func(manifests ...ocispec.Descriptor) { 1738 index := ocispec.Index{ 1739 Manifests: manifests, 1740 } 1741 indexJSON, err := json.Marshal(index) 1742 if err != nil { 1743 t.Fatal(err) 1744 } 1745 appendBlob(ocispec.MediaTypeImageIndex, indexJSON) 1746 } 1747 1748 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 1749 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1 1750 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2 1751 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello")) // Blob 3 1752 generateManifest(descs[0], descs[1:3]...) // Blob 4 1753 generateManifest(descs[0], descs[3]) // Blob 5 1754 generateManifest(descs[0], descs[1:4]...) // Blob 6 1755 generateIndex(descs[4:6]...) // Blob 7 1756 generateIndex(descs[6]) // Blob 8 1757 generateIndex() // Blob 9 1758 generateIndex(descs[7:10]...) // Blob 10 1759 1760 ctx := context.Background() 1761 for i := range blobs { 1762 err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 1763 if err != nil { 1764 t.Fatalf("failed to push test content to src: %d: %v", i, err) 1765 } 1766 } 1767 1768 // test copy 1769 srcTracker := &storageTracker{Storage: src} 1770 dstTracker := &storageTracker{Storage: dst} 1771 root := descs[len(descs)-1] 1772 if err := oras.CopyGraph(ctx, srcTracker, dstTracker, root, oras.CopyGraphOptions{}); err != nil { 1773 t.Fatalf("CopyGraph() error = %v, wantErr %v", err, false) 1774 } 1775 1776 // verify contents 1777 for i := range blobs { 1778 got, err := content.FetchAll(ctx, dst, descs[i]) 1779 if err != nil { 1780 t.Errorf("content[%d] error = %v, wantErr %v", i, err, false) 1781 continue 1782 } 1783 if want := blobs[i]; !bytes.Equal(got, want) { 1784 t.Errorf("content[%d] = %v, want %v", i, got, want) 1785 } 1786 } 1787 1788 // verify API counts 1789 if got, want := srcTracker.fetch, int64(len(blobs)); got != want { 1790 t.Errorf("count(src.Fetch()) = %v, want %v", got, want) 1791 } 1792 if got, want := srcTracker.push, int64(0); got != want { 1793 t.Errorf("count(src.Push()) = %v, want %v", got, want) 1794 } 1795 if got, want := srcTracker.exists, int64(0); got != want { 1796 t.Errorf("count(src.Exists()) = %v, want %v", got, want) 1797 } 1798 if got, want := dstTracker.fetch, int64(0); got != want { 1799 t.Errorf("count(dst.Fetch()) = %v, want %v", got, want) 1800 } 1801 if got, want := dstTracker.push, int64(len(blobs)); got != want { 1802 t.Errorf("count(dst.Push()) = %v, want %v", got, want) 1803 } 1804 if got, want := dstTracker.exists, int64(len(blobs)); got != want { 1805 t.Errorf("count(dst.Exists()) = %v, want %v", got, want) 1806 } 1807 } 1808 1809 func TestCopyGraph_OCIToMemory_PartialCopy(t *testing.T) { 1810 tempDir := t.TempDir() 1811 src, err := New(tempDir) 1812 if err != nil { 1813 t.Fatal("OCI.New() error =", err) 1814 } 1815 1816 dst := cas.NewMemory() 1817 1818 // generate test content 1819 var blobs [][]byte 1820 var descs []ocispec.Descriptor 1821 appendBlob := func(mediaType string, blob []byte) { 1822 blobs = append(blobs, blob) 1823 descs = append(descs, ocispec.Descriptor{ 1824 MediaType: mediaType, 1825 Digest: digest.FromBytes(blob), 1826 Size: int64(len(blob)), 1827 }) 1828 } 1829 generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) { 1830 manifest := ocispec.Manifest{ 1831 Config: config, 1832 Layers: layers, 1833 } 1834 manifestJSON, err := json.Marshal(manifest) 1835 if err != nil { 1836 t.Fatal(err) 1837 } 1838 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 1839 } 1840 generateIndex := func(manifests ...ocispec.Descriptor) { 1841 index := ocispec.Index{ 1842 Manifests: manifests, 1843 } 1844 indexJSON, err := json.Marshal(index) 1845 if err != nil { 1846 t.Fatal(err) 1847 } 1848 appendBlob(ocispec.MediaTypeImageIndex, indexJSON) 1849 } 1850 1851 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 1852 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1 1853 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2 1854 generateManifest(descs[0], descs[1:3]...) // Blob 3 1855 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello")) // Blob 4 1856 generateManifest(descs[0], descs[4]) // Blob 5 1857 generateIndex(descs[3], descs[5]) // Blob 6 1858 1859 ctx := context.Background() 1860 for i := range blobs { 1861 err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 1862 if err != nil { 1863 t.Fatalf("failed to push test content to src: %d: %v", i, err) 1864 } 1865 } 1866 1867 // initial copy 1868 root := descs[3] 1869 if err := oras.CopyGraph(ctx, src, dst, root, oras.CopyGraphOptions{}); err != nil { 1870 t.Fatalf("CopyGraph() error = %v, wantErr %v", err, false) 1871 } 1872 // verify contents 1873 for i := range blobs[:4] { 1874 got, err := content.FetchAll(ctx, dst, descs[i]) 1875 if err != nil { 1876 t.Fatalf("content[%d] error = %v, wantErr %v", i, err, false) 1877 } 1878 if want := blobs[i]; !bytes.Equal(got, want) { 1879 t.Fatalf("content[%d] = %v, want %v", i, got, want) 1880 } 1881 } 1882 1883 // test copy 1884 srcTracker := &storageTracker{Storage: src} 1885 dstTracker := &storageTracker{Storage: dst} 1886 root = descs[len(descs)-1] 1887 if err := oras.CopyGraph(ctx, srcTracker, dstTracker, root, oras.CopyGraphOptions{}); err != nil { 1888 t.Fatalf("CopyGraph() error = %v, wantErr %v", err, false) 1889 } 1890 1891 // verify contents 1892 for i := range blobs { 1893 got, err := content.FetchAll(ctx, dst, descs[i]) 1894 if err != nil { 1895 t.Errorf("content[%d] error = %v, wantErr %v", i, err, false) 1896 continue 1897 } 1898 if want := blobs[i]; !bytes.Equal(got, want) { 1899 t.Errorf("content[%d] = %v, want %v", i, got, want) 1900 } 1901 } 1902 1903 // verify API counts 1904 if got, want := srcTracker.fetch, int64(3); got != want { 1905 t.Errorf("count(src.Fetch()) = %v, want %v", got, want) 1906 } 1907 if got, want := srcTracker.push, int64(0); got != want { 1908 t.Errorf("count(src.Push()) = %v, want %v", got, want) 1909 } 1910 if got, want := srcTracker.exists, int64(0); got != want { 1911 t.Errorf("count(src.Exists()) = %v, want %v", got, want) 1912 } 1913 if got, want := dstTracker.fetch, int64(0); got != want { 1914 t.Errorf("count(dst.Fetch()) = %v, want %v", got, want) 1915 } 1916 if got, want := dstTracker.push, int64(3); got != want { 1917 t.Errorf("count(dst.Push()) = %v, want %v", got, want) 1918 } 1919 if got, want := dstTracker.exists, int64(5); got != want { 1920 t.Errorf("count(dst.Exists()) = %v, want %v", got, want) 1921 } 1922 } 1923 1924 func TestStore_Tags(t *testing.T) { 1925 tempDir := t.TempDir() 1926 s, err := New(tempDir) 1927 if err != nil { 1928 t.Fatal("New() error =", err) 1929 } 1930 ctx := context.Background() 1931 1932 // generate test content 1933 var blobs [][]byte 1934 var descs []ocispec.Descriptor 1935 appendBlob := func(mediaType string, blob []byte) { 1936 blobs = append(blobs, blob) 1937 descs = append(descs, ocispec.Descriptor{ 1938 MediaType: mediaType, 1939 Digest: digest.FromBytes(blob), 1940 Size: int64(len(blob)), 1941 }) 1942 } 1943 generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) { 1944 manifest := ocispec.Manifest{ 1945 Config: config, 1946 Layers: layers, 1947 } 1948 // add annotation to make each manifest unique 1949 manifest.Annotations = map[string]string{ 1950 "blob_index": strconv.Itoa(len(blobs)), 1951 } 1952 manifestJSON, err := json.Marshal(manifest) 1953 if err != nil { 1954 t.Fatal(err) 1955 } 1956 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 1957 } 1958 tagManifest := func(desc ocispec.Descriptor, ref string) { 1959 if err := s.Tag(ctx, desc, ref); err != nil { 1960 t.Fatal("Store.Tag() error =", err) 1961 } 1962 } 1963 1964 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 1965 appendBlob(ocispec.MediaTypeImageLayer, []byte("foobar")) // Blob 1 1966 generateManifest(descs[0], descs[1]) // Blob 2 1967 generateManifest(descs[0], descs[1]) // Blob 3 1968 generateManifest(descs[0], descs[1]) // Blob 4 1969 generateManifest(descs[0], descs[1]) // Blob 5 1970 generateManifest(descs[0], descs[1]) // Blob 6 1971 1972 for i := range blobs { 1973 err := s.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 1974 if err != nil { 1975 t.Fatalf("failed to push test content: %d: %v", i, err) 1976 } 1977 } 1978 1979 tagManifest(descs[3], "v2") 1980 tagManifest(descs[4], "v3") 1981 tagManifest(descs[5], "v1") 1982 tagManifest(descs[6], "v4") 1983 1984 // test tags 1985 tests := []struct { 1986 name string 1987 last string 1988 want []string 1989 }{ 1990 { 1991 name: "list all tags", 1992 want: []string{"v1", "v2", "v3", "v4"}, 1993 }, 1994 { 1995 name: "list from middle", 1996 last: "v2", 1997 want: []string{"v3", "v4"}, 1998 }, 1999 { 2000 name: "list from end", 2001 last: "v4", 2002 want: nil, 2003 }, 2004 } 2005 for _, tt := range tests { 2006 t.Run(tt.name, func(t *testing.T) { 2007 if err := s.Tags(ctx, tt.last, func(got []string) error { 2008 if !reflect.DeepEqual(got, tt.want) { 2009 t.Errorf("Store.Tags() = %v, want %v", got, tt.want) 2010 } 2011 return nil 2012 }); err != nil { 2013 t.Errorf("Store.Tags() error = %v", err) 2014 } 2015 }) 2016 } 2017 2018 wantErr := errors.New("expected error") 2019 if err := s.Tags(ctx, "", func(got []string) error { 2020 return wantErr 2021 }); err != wantErr { 2022 t.Errorf("Store.Tags() error = %v, wantErr %v", err, wantErr) 2023 } 2024 } 2025 2026 func equalDescriptorSet(actual []ocispec.Descriptor, expected []ocispec.Descriptor) bool { 2027 if len(actual) != len(expected) { 2028 return false 2029 } 2030 contains := func(node ocispec.Descriptor) bool { 2031 for _, candidate := range actual { 2032 if reflect.DeepEqual(candidate, node) { 2033 return true 2034 } 2035 } 2036 return false 2037 } 2038 for _, node := range expected { 2039 if !contains(node) { 2040 return false 2041 } 2042 } 2043 return true 2044 } 2045 2046 func TestStore_Delete(t *testing.T) { 2047 content := []byte("hello world") 2048 ref := "hello-world:0.0.1" 2049 desc := ocispec.Descriptor{ 2050 MediaType: "test", 2051 Digest: digest.FromBytes(content), 2052 Size: int64(len(content)), 2053 } 2054 2055 tempDir := t.TempDir() 2056 s, err := New(tempDir) 2057 if err != nil { 2058 t.Fatal("New() error =", err) 2059 } 2060 ctx := context.Background() 2061 2062 err = s.Push(ctx, desc, bytes.NewReader(content)) 2063 if err != nil { 2064 t.Errorf("Store.Push() error = %v, wantErr %v", err, false) 2065 } 2066 2067 err = s.Tag(ctx, desc, ref) 2068 if err != nil { 2069 t.Errorf("error tagging descriptor error = %v, wantErr %v", err, false) 2070 } 2071 2072 resolvedDescr, err := s.Resolve(ctx, ref) 2073 if err != nil { 2074 t.Errorf("error resolving descriptor error = %v, wantErr %v", err, false) 2075 } 2076 2077 if !reflect.DeepEqual(resolvedDescr, desc) { 2078 t.Errorf("Store.Resolve() = %v, want %v", resolvedDescr, desc) 2079 } 2080 2081 err = s.Delete(ctx, resolvedDescr) 2082 if err != nil { 2083 t.Errorf("Store.Delete() = %v, wantErr %v", err, true) 2084 } 2085 2086 _, err = s.Resolve(ctx, ref) 2087 if !errors.Is(err, errdef.ErrNotFound) { 2088 t.Errorf("descriptor should no longer exist in store = %v, wantErr %v", err, errdef.ErrNotFound) 2089 } 2090 } 2091 2092 func TestStore_DeleteDescriptoMultipleRefs(t *testing.T) { 2093 content := []byte("hello world") 2094 ref1 := "hello-world:0.0.1" 2095 ref2 := "hello-world:0.0.2" 2096 desc := ocispec.Descriptor{ 2097 MediaType: "test", 2098 Digest: digest.FromBytes(content), 2099 Size: int64(len(content)), 2100 } 2101 2102 tempDir := t.TempDir() 2103 s, err := New(tempDir) 2104 s.AutoSaveIndex = true 2105 if err != nil { 2106 t.Fatal("New() error =", err) 2107 } 2108 ctx := context.Background() 2109 2110 err = s.Push(ctx, desc, bytes.NewReader(content)) 2111 if err != nil { 2112 t.Errorf("Store.Push() error = %v, wantErr %v", err, false) 2113 } 2114 2115 if len(s.index.Manifests) != 0 { 2116 t.Errorf("manifest should be empty but has %d elements", len(s.index.Manifests)) 2117 } 2118 2119 err = s.Tag(ctx, desc, ref1) 2120 if err != nil { 2121 t.Errorf("error tagging descriptor error = %v, wantErr %v", err, false) 2122 } 2123 2124 err = s.Tag(ctx, desc, ref2) 2125 if err != nil { 2126 t.Errorf("error tagging descriptor error = %v, wantErr %v", err, false) 2127 } 2128 2129 if len(s.index.Manifests) != 2 { 2130 t.Errorf("manifest should have %d, but has %d", len(s.index.Manifests), 0) 2131 } 2132 2133 resolvedDescr, err := s.Resolve(ctx, ref1) 2134 if err != nil { 2135 t.Errorf("error resolving descriptor error = %v, wantErr %v", err, false) 2136 } 2137 2138 if !reflect.DeepEqual(resolvedDescr, desc) { 2139 t.Errorf("Store.Resolve() = %v, want %v", resolvedDescr, desc) 2140 } 2141 2142 err = s.Delete(ctx, resolvedDescr) 2143 if err != nil { 2144 t.Errorf("Store.Delete() = %v, wantErr %v", err, true) 2145 } 2146 2147 if len(s.index.Manifests) != 0 { 2148 t.Errorf("manifest should be empty after delete but has %d", len(s.index.Manifests)) 2149 } 2150 2151 _, err = s.Resolve(ctx, ref2) 2152 if !errors.Is(err, errdef.ErrNotFound) { 2153 t.Errorf("descriptor should no longer exist in store = %v, wantErr %v", err, errdef.ErrNotFound) 2154 } 2155 }