github.com/opcr-io/oras-go/v2@v2.0.0-20231122155130-eb4260d8a0ae/copy_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 oras_test 17 18 import ( 19 "bytes" 20 "context" 21 _ "crypto/sha256" 22 "encoding/json" 23 "errors" 24 "fmt" 25 "io" 26 "reflect" 27 "sync/atomic" 28 "testing" 29 30 "github.com/opcr-io/oras-go/v2" 31 "github.com/opcr-io/oras-go/v2/content" 32 "github.com/opcr-io/oras-go/v2/content/file" 33 "github.com/opcr-io/oras-go/v2/content/memory" 34 "github.com/opcr-io/oras-go/v2/errdef" 35 "github.com/opcr-io/oras-go/v2/internal/cas" 36 "github.com/opcr-io/oras-go/v2/internal/docker" 37 "github.com/opencontainers/go-digest" 38 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 39 ) 40 41 // storageTracker tracks storage API counts. 42 type storageTracker struct { 43 content.Storage 44 fetch int64 45 push int64 46 exists int64 47 } 48 49 func (t *storageTracker) Fetch(ctx context.Context, target ocispec.Descriptor) (io.ReadCloser, error) { 50 atomic.AddInt64(&t.fetch, 1) 51 return t.Storage.Fetch(ctx, target) 52 } 53 54 func (t *storageTracker) Push(ctx context.Context, expected ocispec.Descriptor, content io.Reader) error { 55 atomic.AddInt64(&t.push, 1) 56 return t.Storage.Push(ctx, expected, content) 57 } 58 59 func (t *storageTracker) Exists(ctx context.Context, target ocispec.Descriptor) (bool, error) { 60 atomic.AddInt64(&t.exists, 1) 61 return t.Storage.Exists(ctx, target) 62 } 63 64 func TestCopy_FullCopy(t *testing.T) { 65 src := memory.New() 66 dst := memory.New() 67 68 // generate test content 69 var blobs [][]byte 70 var descs []ocispec.Descriptor 71 appendBlob := func(mediaType string, blob []byte) { 72 blobs = append(blobs, blob) 73 descs = append(descs, ocispec.Descriptor{ 74 MediaType: mediaType, 75 Digest: digest.FromBytes(blob), 76 Size: int64(len(blob)), 77 }) 78 } 79 generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) { 80 manifest := ocispec.Manifest{ 81 Config: config, 82 Layers: layers, 83 } 84 manifestJSON, err := json.Marshal(manifest) 85 if err != nil { 86 t.Fatal(err) 87 } 88 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 89 } 90 91 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 92 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1 93 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2 94 generateManifest(descs[0], descs[1:3]...) // Blob 3 95 96 ctx := context.Background() 97 for i := range blobs { 98 err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 99 if err != nil { 100 t.Fatalf("failed to push test content to src: %d: %v", i, err) 101 } 102 } 103 104 root := descs[3] 105 ref := "foobar" 106 err := src.Tag(ctx, root, ref) 107 if err != nil { 108 t.Fatal("fail to tag root node", err) 109 } 110 111 // test copy 112 gotDesc, err := oras.Copy(ctx, src, ref, dst, "", oras.CopyOptions{}) 113 if err != nil { 114 t.Fatalf("Copy() error = %v, wantErr %v", err, false) 115 } 116 if !reflect.DeepEqual(gotDesc, root) { 117 t.Errorf("Copy() = %v, want %v", gotDesc, root) 118 } 119 120 // verify contents 121 for i, desc := range descs { 122 exists, err := dst.Exists(ctx, desc) 123 if err != nil { 124 t.Fatalf("dst.Exists(%d) error = %v", i, err) 125 } 126 if !exists { 127 t.Errorf("dst.Exists(%d) = %v, want %v", i, exists, true) 128 } 129 } 130 131 // verify tag 132 gotDesc, err = dst.Resolve(ctx, ref) 133 if err != nil { 134 t.Fatal("dst.Resolve() error =", err) 135 } 136 if !reflect.DeepEqual(gotDesc, root) { 137 t.Errorf("dst.Resolve() = %v, want %v", gotDesc, root) 138 } 139 } 140 141 func TestCopy_ExistedRoot(t *testing.T) { 142 src := memory.New() 143 dst := memory.New() 144 145 // generate test content 146 var blobs [][]byte 147 var descs []ocispec.Descriptor 148 appendBlob := func(mediaType string, blob []byte) { 149 blobs = append(blobs, blob) 150 descs = append(descs, ocispec.Descriptor{ 151 MediaType: mediaType, 152 Digest: digest.FromBytes(blob), 153 Size: int64(len(blob)), 154 }) 155 } 156 generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) { 157 manifest := ocispec.Manifest{ 158 Config: config, 159 Layers: layers, 160 } 161 manifestJSON, err := json.Marshal(manifest) 162 if err != nil { 163 t.Fatal(err) 164 } 165 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 166 } 167 168 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 169 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1 170 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2 171 generateManifest(descs[0], descs[1:3]...) // Blob 3 172 173 ctx := context.Background() 174 for i := range blobs { 175 err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 176 if err != nil { 177 t.Fatalf("failed to push test content to src: %d: %v", i, err) 178 } 179 } 180 181 root := descs[3] 182 ref := "foobar" 183 newTag := "newtag" 184 err := src.Tag(ctx, root, ref) 185 if err != nil { 186 t.Fatal("fail to tag root node", err) 187 } 188 189 var skippedCount int64 190 copyOpts := oras.CopyOptions{ 191 CopyGraphOptions: oras.CopyGraphOptions{ 192 OnCopySkipped: func(ctx context.Context, desc ocispec.Descriptor) error { 193 atomic.AddInt64(&skippedCount, 1) 194 return nil 195 }, 196 }, 197 } 198 199 // copy with src tag 200 gotDesc, err := oras.Copy(ctx, src, ref, dst, "", copyOpts) 201 if err != nil { 202 t.Fatalf("Copy() error = %v, wantErr %v", err, false) 203 } 204 if !reflect.DeepEqual(gotDesc, root) { 205 t.Errorf("Copy() = %v, want %v", gotDesc, root) 206 } 207 // copy with new tag 208 gotDesc, err = oras.Copy(ctx, src, ref, dst, newTag, copyOpts) 209 if err != nil { 210 t.Fatalf("Copy() error = %v, wantErr %v", err, false) 211 } 212 if !reflect.DeepEqual(gotDesc, root) { 213 t.Errorf("Copy() = %v, want %v", gotDesc, root) 214 } 215 216 // verify contents 217 for i, desc := range descs { 218 exists, err := dst.Exists(ctx, desc) 219 if err != nil { 220 t.Fatalf("dst.Exists(%d) error = %v", i, err) 221 } 222 if !exists { 223 t.Errorf("dst.Exists(%d) = %v, want %v", i, exists, true) 224 } 225 } 226 227 // verify src tag 228 gotDesc, err = dst.Resolve(ctx, ref) 229 if err != nil { 230 t.Fatal("dst.Resolve() error =", err) 231 } 232 if !reflect.DeepEqual(gotDesc, root) { 233 t.Errorf("dst.Resolve() = %v, want %v", gotDesc, root) 234 } 235 // verify new tag 236 gotDesc, err = dst.Resolve(ctx, newTag) 237 if err != nil { 238 t.Fatal("dst.Resolve() error =", err) 239 } 240 if !reflect.DeepEqual(gotDesc, root) { 241 t.Errorf("dst.Resolve() = %v, want %v", gotDesc, root) 242 } 243 // verify invocation of onCopySkipped() 244 if got, want := skippedCount, int64(1); got != want { 245 t.Errorf("count(OnCopySkipped()) = %v, want %v", got, want) 246 } 247 } 248 249 func TestCopyGraph_FullCopy(t *testing.T) { 250 src := cas.NewMemory() 251 dst := cas.NewMemory() 252 253 // generate test content 254 var blobs [][]byte 255 var descs []ocispec.Descriptor 256 appendBlob := func(mediaType string, blob []byte) { 257 blobs = append(blobs, blob) 258 descs = append(descs, ocispec.Descriptor{ 259 MediaType: mediaType, 260 Digest: digest.FromBytes(blob), 261 Size: int64(len(blob)), 262 }) 263 } 264 generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) { 265 manifest := ocispec.Manifest{ 266 Config: config, 267 Layers: layers, 268 } 269 manifestJSON, err := json.Marshal(manifest) 270 if err != nil { 271 t.Fatal(err) 272 } 273 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 274 } 275 generateIndex := func(manifests ...ocispec.Descriptor) { 276 index := ocispec.Index{ 277 Manifests: manifests, 278 } 279 indexJSON, err := json.Marshal(index) 280 if err != nil { 281 t.Fatal(err) 282 } 283 appendBlob(ocispec.MediaTypeImageIndex, indexJSON) 284 } 285 286 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 287 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1 288 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2 289 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello")) // Blob 3 290 generateManifest(descs[0], descs[1:3]...) // Blob 4 291 generateManifest(descs[0], descs[3]) // Blob 5 292 generateManifest(descs[0], descs[1:4]...) // Blob 6 293 generateIndex(descs[4:6]...) // Blob 7 294 generateIndex(descs[6]) // Blob 8 295 generateIndex() // Blob 9 296 generateIndex(descs[7:10]...) // Blob 10 297 298 ctx := context.Background() 299 for i := range blobs { 300 err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 301 if err != nil { 302 t.Fatalf("failed to push test content to src: %d: %v", i, err) 303 } 304 } 305 306 // test copy graph 307 srcTracker := &storageTracker{Storage: src} 308 dstTracker := &storageTracker{Storage: dst} 309 root := descs[len(descs)-1] 310 if err := oras.CopyGraph(ctx, srcTracker, dstTracker, root, oras.CopyGraphOptions{}); err != nil { 311 t.Fatalf("CopyGraph() error = %v, wantErr %v", err, false) 312 } 313 314 // verify contents 315 contents := dst.Map() 316 if got, want := len(contents), len(blobs); got != want { 317 t.Errorf("len(dst) = %v, wantErr %v", got, want) 318 } 319 for i := range blobs { 320 got, err := content.FetchAll(ctx, dst, descs[i]) 321 if err != nil { 322 t.Errorf("content[%d] error = %v, wantErr %v", i, err, false) 323 continue 324 } 325 if want := blobs[i]; !bytes.Equal(got, want) { 326 t.Errorf("content[%d] = %v, want %v", i, got, want) 327 } 328 } 329 330 // verify API counts 331 if got, want := srcTracker.fetch, int64(len(blobs)); got != want { 332 t.Errorf("count(src.Fetch()) = %v, want %v", got, want) 333 } 334 if got, want := srcTracker.push, int64(0); got != want { 335 t.Errorf("count(src.Push()) = %v, want %v", got, want) 336 } 337 if got, want := srcTracker.exists, int64(0); got != want { 338 t.Errorf("count(src.Exists()) = %v, want %v", got, want) 339 } 340 if got, want := dstTracker.fetch, int64(0); got != want { 341 t.Errorf("count(dst.Fetch()) = %v, want %v", got, want) 342 } 343 if got, want := dstTracker.push, int64(len(blobs)); got != want { 344 t.Errorf("count(dst.Push()) = %v, want %v", got, want) 345 } 346 if got, want := dstTracker.exists, int64(len(blobs)); got != want { 347 t.Errorf("count(dst.Exists()) = %v, want %v", got, want) 348 } 349 } 350 351 func TestCopyGraph_PartialCopy(t *testing.T) { 352 src := cas.NewMemory() 353 dst := cas.NewMemory() 354 355 // generate test content 356 var blobs [][]byte 357 var descs []ocispec.Descriptor 358 appendBlob := func(mediaType string, blob []byte) { 359 blobs = append(blobs, blob) 360 descs = append(descs, ocispec.Descriptor{ 361 MediaType: mediaType, 362 Digest: digest.FromBytes(blob), 363 Size: int64(len(blob)), 364 }) 365 } 366 generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) { 367 manifest := ocispec.Manifest{ 368 Config: config, 369 Layers: layers, 370 } 371 manifestJSON, err := json.Marshal(manifest) 372 if err != nil { 373 t.Fatal(err) 374 } 375 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 376 } 377 generateIndex := func(manifests ...ocispec.Descriptor) { 378 index := ocispec.Index{ 379 Manifests: manifests, 380 } 381 indexJSON, err := json.Marshal(index) 382 if err != nil { 383 t.Fatal(err) 384 } 385 appendBlob(ocispec.MediaTypeImageIndex, indexJSON) 386 } 387 388 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 389 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1 390 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2 391 generateManifest(descs[0], descs[1:3]...) // Blob 3 392 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello")) // Blob 4 393 generateManifest(descs[0], descs[4]) // Blob 5 394 generateIndex(descs[3], descs[5]) // Blob 6 395 396 ctx := context.Background() 397 for i := range blobs { 398 err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 399 if err != nil { 400 t.Fatalf("failed to push test content to src: %d: %v", i, err) 401 } 402 } 403 404 // initial copy 405 root := descs[3] 406 if err := oras.CopyGraph(ctx, src, dst, root, oras.CopyGraphOptions{}); err != nil { 407 t.Fatalf("CopyGraph() error = %v, wantErr %v", err, false) 408 } 409 // verify contents 410 contents := dst.Map() 411 if got, want := len(contents), len(blobs[:4]); got != want { 412 t.Fatalf("len(dst) = %v, wantErr %v", got, want) 413 } 414 for i := range blobs[:4] { 415 got, err := content.FetchAll(ctx, dst, descs[i]) 416 if err != nil { 417 t.Fatalf("content[%d] error = %v, wantErr %v", i, err, false) 418 } 419 if want := blobs[i]; !bytes.Equal(got, want) { 420 t.Fatalf("content[%d] = %v, want %v", i, got, want) 421 } 422 } 423 424 // test copy 425 srcTracker := &storageTracker{Storage: src} 426 dstTracker := &storageTracker{Storage: dst} 427 root = descs[len(descs)-1] 428 if err := oras.CopyGraph(ctx, srcTracker, dstTracker, root, oras.CopyGraphOptions{}); err != nil { 429 t.Fatalf("CopyGraph() error = %v, wantErr %v", err, false) 430 } 431 432 // verify contents 433 contents = dst.Map() 434 if got, want := len(contents), len(blobs); got != want { 435 t.Errorf("len(dst) = %v, wantErr %v", got, want) 436 } 437 for i := range blobs { 438 got, err := content.FetchAll(ctx, dst, descs[i]) 439 if err != nil { 440 t.Errorf("content[%d] error = %v, wantErr %v", i, err, false) 441 continue 442 } 443 if want := blobs[i]; !bytes.Equal(got, want) { 444 t.Errorf("content[%d] = %v, want %v", i, got, want) 445 } 446 } 447 448 // verify API counts 449 if got, want := srcTracker.fetch, int64(3); got != want { 450 t.Errorf("count(src.Fetch()) = %v, want %v", got, want) 451 } 452 if got, want := srcTracker.push, int64(0); got != want { 453 t.Errorf("count(src.Push()) = %v, want %v", got, want) 454 } 455 if got, want := srcTracker.exists, int64(0); got != want { 456 t.Errorf("count(src.Exists()) = %v, want %v", got, want) 457 } 458 if got, want := dstTracker.fetch, int64(0); got != want { 459 t.Errorf("count(dst.Fetch()) = %v, want %v", got, want) 460 } 461 if got, want := dstTracker.push, int64(3); got != want { 462 t.Errorf("count(dst.Push()) = %v, want %v", got, want) 463 } 464 if got, want := dstTracker.exists, int64(5); got != want { 465 t.Errorf("count(dst.Exists()) = %v, want %v", got, want) 466 } 467 } 468 469 func TestCopy_WithOptions(t *testing.T) { 470 src := memory.New() 471 472 // generate test content 473 var blobs [][]byte 474 var descs []ocispec.Descriptor 475 appendBlob := func(mediaType string, blob []byte) { 476 blobs = append(blobs, blob) 477 descs = append(descs, ocispec.Descriptor{ 478 MediaType: mediaType, 479 Digest: digest.FromBytes(blob), 480 Size: int64(len(blob)), 481 }) 482 } 483 appendManifest := func(arc, os string, mediaType string, blob []byte) { 484 blobs = append(blobs, blob) 485 descs = append(descs, ocispec.Descriptor{ 486 MediaType: mediaType, 487 Digest: digest.FromBytes(blob), 488 Size: int64(len(blob)), 489 Platform: &ocispec.Platform{ 490 Architecture: arc, 491 OS: os, 492 }, 493 }) 494 } 495 generateManifest := func(arc, os string, config ocispec.Descriptor, layers ...ocispec.Descriptor) { 496 manifest := ocispec.Manifest{ 497 Config: config, 498 Layers: layers, 499 } 500 manifestJSON, err := json.Marshal(manifest) 501 if err != nil { 502 t.Fatal(err) 503 } 504 appendManifest(arc, os, ocispec.MediaTypeImageManifest, manifestJSON) 505 } 506 generateIndex := func(manifests ...ocispec.Descriptor) { 507 index := ocispec.Index{ 508 Manifests: manifests, 509 } 510 indexJSON, err := json.Marshal(index) 511 if err != nil { 512 t.Fatal(err) 513 } 514 appendBlob(ocispec.MediaTypeImageIndex, indexJSON) 515 } 516 517 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 518 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1 519 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2 520 generateManifest("test-arc-1", "test-os-1", descs[0], descs[1:3]...) // Blob 3 521 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello")) // Blob 4 522 generateManifest("test-arc-2", "test-os-2", descs[0], descs[4]) // Blob 5 523 generateIndex(descs[3], descs[5]) // Blob 6 524 525 ctx := context.Background() 526 for i := range blobs { 527 err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 528 if err != nil { 529 t.Fatalf("failed to push test content to src: %d: %v", i, err) 530 } 531 } 532 533 root := descs[6] 534 ref := "foobar" 535 err := src.Tag(ctx, root, ref) 536 if err != nil { 537 t.Fatal("fail to tag root node", err) 538 } 539 540 // test copy with media type filter 541 dst := memory.New() 542 opts := oras.DefaultCopyOptions 543 opts.MapRoot = func(ctx context.Context, src content.ReadOnlyStorage, root ocispec.Descriptor) (ocispec.Descriptor, error) { 544 if root.MediaType == ocispec.MediaTypeImageIndex { 545 return root, nil 546 } else { 547 return ocispec.Descriptor{}, errdef.ErrNotFound 548 } 549 } 550 gotDesc, err := oras.Copy(ctx, src, ref, dst, "", opts) 551 if err != nil { 552 t.Fatalf("Copy() error = %v, wantErr %v", err, false) 553 } 554 if !reflect.DeepEqual(gotDesc, root) { 555 t.Errorf("Copy() = %v, want %v", gotDesc, root) 556 } 557 558 // verify contents 559 for i, desc := range descs { 560 exists, err := dst.Exists(ctx, desc) 561 if err != nil { 562 t.Fatalf("dst.Exists(%d) error = %v", i, err) 563 } 564 if !exists { 565 t.Errorf("dst.Exists(%d) = %v, want %v", i, exists, true) 566 } 567 } 568 569 // verify tag 570 gotDesc, err = dst.Resolve(ctx, ref) 571 if err != nil { 572 t.Fatal("dst.Resolve() error =", err) 573 } 574 if !reflect.DeepEqual(gotDesc, root) { 575 t.Errorf("dst.Resolve() = %v, want %v", gotDesc, root) 576 } 577 578 // test copy with platform filter and hooks 579 dst = memory.New() 580 preCopyCount := int64(0) 581 postCopyCount := int64(0) 582 opts = oras.CopyOptions{ 583 MapRoot: func(ctx context.Context, src content.ReadOnlyStorage, root ocispec.Descriptor) (ocispec.Descriptor, error) { 584 manifests, err := content.Successors(ctx, src, root) 585 if err != nil { 586 return ocispec.Descriptor{}, errdef.ErrNotFound 587 } 588 589 // platform filter 590 for _, m := range manifests { 591 if m.Platform.Architecture == "test-arc-2" && m.Platform.OS == "test-os-2" { 592 return m, nil 593 } 594 } 595 return ocispec.Descriptor{}, errdef.ErrNotFound 596 }, 597 CopyGraphOptions: oras.CopyGraphOptions{ 598 PreCopy: func(ctx context.Context, desc ocispec.Descriptor) error { 599 atomic.AddInt64(&preCopyCount, 1) 600 return nil 601 }, 602 PostCopy: func(ctx context.Context, desc ocispec.Descriptor) error { 603 atomic.AddInt64(&postCopyCount, 1) 604 return nil 605 }, 606 }, 607 } 608 wantDesc := descs[5] 609 gotDesc, err = oras.Copy(ctx, src, ref, dst, "", opts) 610 if err != nil { 611 t.Fatalf("Copy() error = %v, wantErr %v", err, false) 612 } 613 if !reflect.DeepEqual(gotDesc, wantDesc) { 614 t.Errorf("Copy() = %v, want %v", gotDesc, wantDesc) 615 } 616 617 // verify contents 618 for i, desc := range append([]ocispec.Descriptor{descs[0]}, descs[4:6]...) { 619 exists, err := dst.Exists(ctx, desc) 620 if err != nil { 621 t.Fatalf("dst.Exists(%d) error = %v", i, err) 622 } 623 if !exists { 624 t.Errorf("dst.Exists(%d) = %v, want %v", i, exists, true) 625 } 626 } 627 628 // verify tag 629 gotDesc, err = dst.Resolve(ctx, ref) 630 if err != nil { 631 t.Fatal("dst.Resolve() error =", err) 632 } 633 if !reflect.DeepEqual(gotDesc, wantDesc) { 634 t.Errorf("dst.Resolve() = %v, want %v", gotDesc, wantDesc) 635 } 636 637 // verify API counts 638 if got, want := preCopyCount, int64(3); got != want { 639 t.Errorf("count(PreCopy()) = %v, want %v", got, want) 640 } 641 if got, want := postCopyCount, int64(3); got != want { 642 t.Errorf("count(PostCopy()) = %v, want %v", got, want) 643 } 644 645 // test copy with root filter, but no matching node can be found 646 dst = memory.New() 647 opts = oras.CopyOptions{ 648 MapRoot: func(ctx context.Context, src content.ReadOnlyStorage, root ocispec.Descriptor) (ocispec.Descriptor, error) { 649 if root.MediaType == "test" { 650 return root, nil 651 } else { 652 return ocispec.Descriptor{}, errdef.ErrNotFound 653 } 654 }, 655 CopyGraphOptions: oras.DefaultCopyGraphOptions, 656 } 657 658 _, err = oras.Copy(ctx, src, ref, dst, "", opts) 659 if !errors.Is(err, errdef.ErrNotFound) { 660 t.Fatalf("Copy() error = %v, wantErr %v", err, errdef.ErrNotFound) 661 } 662 663 // test copy with MaxMetadataBytes = 1 664 dst = memory.New() 665 opts = oras.CopyOptions{ 666 CopyGraphOptions: oras.CopyGraphOptions{ 667 MaxMetadataBytes: 1, 668 }, 669 } 670 if _, err := oras.Copy(ctx, src, ref, dst, "", opts); !errors.Is(err, errdef.ErrSizeExceedsLimit) { 671 t.Fatalf("CopyGraph() error = %v, wantErr %v", err, errdef.ErrSizeExceedsLimit) 672 } 673 } 674 675 func TestCopy_WithTargetPlatformOptions(t *testing.T) { 676 src := memory.New() 677 arc_1 := "test-arc-1" 678 os_1 := "test-os-1" 679 variant_1 := "v1" 680 arc_2 := "test-arc-2" 681 os_2 := "test-os-2" 682 variant_2 := "v2" 683 684 // generate test content 685 var blobs [][]byte 686 var descs []ocispec.Descriptor 687 appendBlob := func(mediaType string, blob []byte) { 688 blobs = append(blobs, blob) 689 descs = append(descs, ocispec.Descriptor{ 690 MediaType: mediaType, 691 Digest: digest.FromBytes(blob), 692 Size: int64(len(blob)), 693 }) 694 } 695 appendManifest := func(arc, os, variant string, mediaType string, blob []byte) { 696 blobs = append(blobs, blob) 697 descs = append(descs, ocispec.Descriptor{ 698 MediaType: mediaType, 699 Digest: digest.FromBytes(blob), 700 Size: int64(len(blob)), 701 Platform: &ocispec.Platform{ 702 Architecture: arc, 703 OS: os, 704 Variant: variant, 705 }, 706 }) 707 } 708 generateManifest := func(arc, os, variant string, config ocispec.Descriptor, layers ...ocispec.Descriptor) { 709 manifest := ocispec.Manifest{ 710 Config: config, 711 Layers: layers, 712 } 713 manifestJSON, err := json.Marshal(manifest) 714 if err != nil { 715 t.Fatal(err) 716 } 717 appendManifest(arc, os, variant, ocispec.MediaTypeImageManifest, manifestJSON) 718 } 719 generateIndex := func(manifests ...ocispec.Descriptor) { 720 index := ocispec.Index{ 721 Manifests: manifests, 722 } 723 indexJSON, err := json.Marshal(index) 724 if err != nil { 725 t.Fatal(err) 726 } 727 appendBlob(ocispec.MediaTypeImageIndex, indexJSON) 728 } 729 730 appendBlob(ocispec.MediaTypeImageConfig, []byte(`{"mediaType":"application/vnd.oci.image.config.v1+json", 731 "created":"2022-07-29T08:13:55Z", 732 "author":"test author", 733 "architecture":"test-arc-1", 734 "os":"test-os-1", 735 "variant":"v1"}`)) // Blob 0 736 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1 737 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2 738 generateManifest(arc_1, os_1, variant_1, descs[0], descs[1:3]...) // Blob 3 739 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello1")) // Blob 4 740 generateManifest(arc_2, os_2, variant_1, descs[0], descs[4]) // Blob 5 741 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello2")) // Blob 6 742 generateManifest(arc_1, os_1, variant_2, descs[0], descs[6]) // Blob 7 743 generateIndex(descs[3], descs[5], descs[7]) // Blob 8 744 745 ctx := context.Background() 746 for i := range blobs { 747 err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 748 if err != nil { 749 t.Fatalf("failed to push test content to src: %d: %v", i, err) 750 } 751 } 752 753 root := descs[8] 754 ref := "foobar" 755 err := src.Tag(ctx, root, ref) 756 if err != nil { 757 t.Fatal("fail to tag root node", err) 758 } 759 760 // test copy with platform filter for the image index 761 dst := memory.New() 762 opts := oras.CopyOptions{} 763 targetPlatform := ocispec.Platform{ 764 Architecture: arc_2, 765 OS: os_2, 766 } 767 opts.WithTargetPlatform(&targetPlatform) 768 wantDesc := descs[5] 769 gotDesc, err := oras.Copy(ctx, src, ref, dst, "", opts) 770 if err != nil { 771 t.Fatalf("Copy() error = %v, wantErr %v", err, false) 772 } 773 if !reflect.DeepEqual(gotDesc, wantDesc) { 774 t.Errorf("Copy() = %v, want %v", gotDesc, wantDesc) 775 } 776 777 // verify contents 778 for i, desc := range append([]ocispec.Descriptor{descs[0]}, descs[4:6]...) { 779 exists, err := dst.Exists(ctx, desc) 780 if err != nil { 781 t.Fatalf("dst.Exists(%d) error = %v", i, err) 782 } 783 if !exists { 784 t.Errorf("dst.Exists(%d) = %v, want %v", i, exists, true) 785 } 786 } 787 788 // verify tag 789 gotDesc, err = dst.Resolve(ctx, ref) 790 if err != nil { 791 t.Fatal("dst.Resolve() error =", err) 792 } 793 if !reflect.DeepEqual(gotDesc, wantDesc) { 794 t.Errorf("dst.Resolve() = %v, want %v", gotDesc, wantDesc) 795 } 796 797 // test copy with platform filter for the image index, and multiple 798 // manifests match the required platform. Should return the first matching 799 // entry. 800 dst = memory.New() 801 targetPlatform = ocispec.Platform{ 802 Architecture: arc_1, 803 OS: os_1, 804 } 805 opts = oras.CopyOptions{} 806 opts.WithTargetPlatform(&targetPlatform) 807 wantDesc = descs[3] 808 gotDesc, err = oras.Copy(ctx, src, ref, dst, "", opts) 809 if err != nil { 810 t.Fatalf("Copy() error = %v, wantErr %v", err, false) 811 } 812 if !reflect.DeepEqual(gotDesc, wantDesc) { 813 t.Errorf("Copy() = %v, want %v", gotDesc, wantDesc) 814 } 815 816 // verify contents 817 for i, desc := range append([]ocispec.Descriptor{descs[0]}, descs[1:3]...) { 818 exists, err := dst.Exists(ctx, desc) 819 if err != nil { 820 t.Fatalf("dst.Exists(%d) error = %v", i, err) 821 } 822 if !exists { 823 t.Errorf("dst.Exists(%d) = %v, want %v", i, exists, true) 824 } 825 } 826 827 // verify tag 828 gotDesc, err = dst.Resolve(ctx, ref) 829 if err != nil { 830 t.Fatal("dst.Resolve() error =", err) 831 } 832 if !reflect.DeepEqual(gotDesc, wantDesc) { 833 t.Errorf("dst.Resolve() = %v, want %v", gotDesc, wantDesc) 834 } 835 836 // test copy with platform filter and existing MapRoot func for the image 837 // index, but there is no matching node. Should return not found error. 838 dst = memory.New() 839 opts = oras.CopyOptions{ 840 MapRoot: func(ctx context.Context, src content.ReadOnlyStorage, root ocispec.Descriptor) (ocispec.Descriptor, error) { 841 if root.MediaType == ocispec.MediaTypeImageIndex { 842 return root, nil 843 } else { 844 return ocispec.Descriptor{}, errdef.ErrNotFound 845 } 846 }, 847 } 848 targetPlatform = ocispec.Platform{ 849 Architecture: arc_1, 850 OS: os_2, 851 } 852 opts.WithTargetPlatform(&targetPlatform) 853 854 _, err = oras.Copy(ctx, src, ref, dst, "", opts) 855 expected := fmt.Sprintf("%s: %v: no matching manifest was found in the manifest list", root.Digest, errdef.ErrNotFound) 856 if err.Error() != expected { 857 t.Fatalf("Copy() error = %v, wantErr %v", err, expected) 858 } 859 860 // test copy with platform filter for the manifest 861 dst = memory.New() 862 opts = oras.CopyOptions{} 863 targetPlatform = ocispec.Platform{ 864 Architecture: arc_1, 865 OS: os_1, 866 } 867 opts.WithTargetPlatform(&targetPlatform) 868 869 root = descs[7] 870 err = src.Tag(ctx, root, ref) 871 if err != nil { 872 t.Fatal("fail to tag root node", err) 873 } 874 875 wantDesc = descs[7] 876 gotDesc, err = oras.Copy(ctx, src, ref, dst, "", opts) 877 if err != nil { 878 t.Fatalf("Copy() error = %v, wantErr %v", err, false) 879 } 880 if !reflect.DeepEqual(gotDesc, wantDesc) { 881 t.Errorf("Copy() = %v, want %v", gotDesc, wantDesc) 882 } 883 884 // verify contents 885 for i, desc := range append([]ocispec.Descriptor{descs[0]}, descs[6]) { 886 exists, err := dst.Exists(ctx, desc) 887 if err != nil { 888 t.Fatalf("dst.Exists(%d) error = %v", i, err) 889 } 890 if !exists { 891 t.Errorf("dst.Exists(%d) = %v, want %v", i, exists, true) 892 } 893 } 894 895 // verify tag 896 gotDesc, err = dst.Resolve(ctx, ref) 897 if err != nil { 898 t.Fatal("dst.Resolve() error =", err) 899 } 900 if !reflect.DeepEqual(gotDesc, wantDesc) { 901 t.Errorf("dst.Resolve() = %v, want %v", gotDesc, wantDesc) 902 } 903 904 // test copy with platform filter for the manifest, but there is no matching 905 // node. Should return not found error. 906 dst = memory.New() 907 opts = oras.CopyOptions{} 908 targetPlatform = ocispec.Platform{ 909 Architecture: arc_1, 910 OS: os_1, 911 Variant: variant_2, 912 } 913 opts.WithTargetPlatform(&targetPlatform) 914 915 _, err = oras.Copy(ctx, src, ref, dst, "", opts) 916 expected = fmt.Sprintf("%s: %v: platform in manifest does not match target platform", root.Digest, errdef.ErrNotFound) 917 if err.Error() != expected { 918 t.Fatalf("Copy() error = %v, wantErr %v", err, expected) 919 } 920 921 // test copy with platform filter, but the node's media type is not 922 // supported. Should return unsupported error 923 dst = memory.New() 924 opts = oras.CopyOptions{} 925 targetPlatform = ocispec.Platform{ 926 Architecture: arc_1, 927 OS: os_1, 928 } 929 opts.WithTargetPlatform(&targetPlatform) 930 931 root = descs[1] 932 err = src.Tag(ctx, root, ref) 933 if err != nil { 934 t.Fatal("fail to tag root node", err) 935 } 936 937 _, err = oras.Copy(ctx, src, ref, dst, "", opts) 938 if !errors.Is(err, errdef.ErrUnsupported) { 939 t.Fatalf("Copy() error = %v, wantErr %v", err, errdef.ErrUnsupported) 940 } 941 942 // generate incorrect test content 943 blobs = nil 944 descs = nil 945 appendBlob(docker.MediaTypeConfig, []byte(`{"mediaType":"application/vnd.oci.image.config.v1+json", 946 "created":"2022-07-29T08:13:55Z", 947 "author":"test author 1", 948 "architecture":"test-arc-1", 949 "os":"test-os-1", 950 "variant":"v1"}`)) // Blob 0 951 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo1")) // Blob 1 952 generateManifest(arc_1, os_1, variant_1, descs[0], descs[1]) // Blob 2 953 generateIndex(descs[2]) // Blob 3 954 955 ctx = context.Background() 956 for i := range blobs { 957 err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 958 if err != nil { 959 t.Fatalf("failed to push test content to src: %d: %v", i, err) 960 } 961 } 962 963 dst = memory.New() 964 opts = oras.CopyOptions{} 965 targetPlatform = ocispec.Platform{ 966 Architecture: arc_1, 967 OS: os_1, 968 } 969 opts.WithTargetPlatform(&targetPlatform) 970 971 // test copy with platform filter for the manifest, but the manifest is 972 // invalid by having docker mediaType config in the manifest and oci 973 // mediaType in the image config. Should return error. 974 root = descs[2] 975 err = src.Tag(ctx, root, ref) 976 if err != nil { 977 t.Fatal("fail to tag root node", err) 978 } 979 980 _, err = oras.Copy(ctx, src, ref, dst, "", opts) 981 expected = fmt.Sprintf("fail to recognize platform from unknown config %s: expect %s", docker.MediaTypeConfig, ocispec.MediaTypeImageConfig) 982 if err.Error() != expected { 983 t.Fatalf("Copy() error = %v, wantErr %v", err, expected) 984 } 985 986 // generate test content with null config blob 987 blobs = nil 988 descs = nil 989 appendBlob(ocispec.MediaTypeImageConfig, []byte("null")) // Blob 0 990 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo2")) // Blob 1 991 generateManifest(arc_1, os_1, variant_1, descs[0], descs[1]) // Blob 2 992 generateIndex(descs[2]) // Blob 3 993 994 ctx = context.Background() 995 for i := range blobs { 996 err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 997 if err != nil { 998 t.Fatalf("failed to push test content to src: %d: %v", i, err) 999 } 1000 } 1001 1002 dst = memory.New() 1003 opts = oras.CopyOptions{} 1004 targetPlatform = ocispec.Platform{ 1005 Architecture: arc_1, 1006 OS: os_1, 1007 } 1008 opts.WithTargetPlatform(&targetPlatform) 1009 1010 // test copy with platform filter for the manifest with null config blob 1011 // should return not found error 1012 root = descs[2] 1013 err = src.Tag(ctx, root, ref) 1014 if err != nil { 1015 t.Fatal("fail to tag root node", err) 1016 } 1017 1018 _, err = oras.Copy(ctx, src, ref, dst, "", opts) 1019 expected = fmt.Sprintf("%s: %v: platform in manifest does not match target platform", root.Digest, errdef.ErrNotFound) 1020 if err.Error() != expected { 1021 t.Fatalf("Copy() error = %v, wantErr %v", err, expected) 1022 } 1023 1024 // generate test content with empty config blob 1025 blobs = nil 1026 descs = nil 1027 appendBlob(ocispec.MediaTypeImageConfig, []byte("")) // Blob 0 1028 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo3")) // Blob 1 1029 generateManifest(arc_1, os_1, variant_1, descs[0], descs[1]) // Blob 2 1030 generateIndex(descs[2]) // Blob 3 1031 1032 ctx = context.Background() 1033 for i := range blobs { 1034 err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 1035 if err != nil { 1036 t.Fatalf("failed to push test content to src: %d: %v", i, err) 1037 } 1038 } 1039 1040 dst = memory.New() 1041 opts = oras.CopyOptions{} 1042 targetPlatform = ocispec.Platform{ 1043 Architecture: arc_1, 1044 OS: os_1, 1045 } 1046 opts.WithTargetPlatform(&targetPlatform) 1047 1048 // test copy with platform filter for the manifest with empty config blob 1049 // should return not found error 1050 root = descs[2] 1051 err = src.Tag(ctx, root, ref) 1052 if err != nil { 1053 t.Fatal("fail to tag root node", err) 1054 } 1055 1056 _, err = oras.Copy(ctx, src, ref, dst, "", opts) 1057 expected = fmt.Sprintf("%s: %v: platform in manifest does not match target platform", root.Digest, errdef.ErrNotFound) 1058 if err.Error() != expected { 1059 t.Fatalf("Copy() error = %v, wantErr %v", err, expected) 1060 } 1061 1062 // test copy with no platform filter and nil opts.MapRoot 1063 // opts.MapRoot should be nil 1064 opts = oras.CopyOptions{} 1065 opts.WithTargetPlatform(nil) 1066 if opts.MapRoot != nil { 1067 t.Fatal("opts.MapRoot not equal to nil when platform is not provided") 1068 } 1069 1070 // test copy with no platform filter and custom opts.MapRoot 1071 // should return ErrNotFound 1072 opts = oras.CopyOptions{ 1073 MapRoot: func(ctx context.Context, src content.ReadOnlyStorage, root ocispec.Descriptor) (ocispec.Descriptor, error) { 1074 if root.MediaType == "test" { 1075 return root, nil 1076 } else { 1077 return ocispec.Descriptor{}, errdef.ErrNotFound 1078 } 1079 }, 1080 CopyGraphOptions: oras.DefaultCopyGraphOptions, 1081 } 1082 opts.WithTargetPlatform(nil) 1083 1084 _, err = oras.Copy(ctx, src, ref, dst, "", opts) 1085 if !errors.Is(err, errdef.ErrNotFound) { 1086 t.Fatalf("Copy() error = %v, wantErr %v", err, errdef.ErrNotFound) 1087 } 1088 } 1089 1090 func TestCopy_RestoreDuplicates(t *testing.T) { 1091 src := memory.New() 1092 temp := t.TempDir() 1093 dst, err := file.New(temp) 1094 if err != nil { 1095 t.Fatal("file.New() error =", err) 1096 } 1097 defer dst.Close() 1098 1099 // generate test content 1100 var blobs [][]byte 1101 var descs []ocispec.Descriptor 1102 appendBlob := func(mediaType string, blob []byte, title string) { 1103 blobs = append(blobs, blob) 1104 desc := ocispec.Descriptor{ 1105 MediaType: mediaType, 1106 Digest: digest.FromBytes(blob), 1107 Size: int64(len(blob)), 1108 } 1109 if title != "" { 1110 desc.Annotations = map[string]string{ 1111 ocispec.AnnotationTitle: title, 1112 } 1113 } 1114 descs = append(descs, desc) 1115 } 1116 generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) { 1117 manifest := ocispec.Manifest{ 1118 Config: config, 1119 Layers: layers, 1120 } 1121 manifestJSON, err := json.Marshal(manifest) 1122 if err != nil { 1123 t.Fatal(err) 1124 } 1125 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON, "") 1126 } 1127 1128 appendBlob(ocispec.MediaTypeImageConfig, []byte("{}"), "") // Blob 0 1129 // 2 blobs with same content 1130 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello"), "foo.txt") // Blob 1 1131 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello"), "bar.txt") // Blob 2 1132 generateManifest(descs[0], descs[1:3]...) // Blob 3 1133 1134 ctx := context.Background() 1135 for i := range blobs { 1136 exists, err := src.Exists(ctx, descs[i]) 1137 if err != nil { 1138 t.Fatalf("failed to check existence in src: %d: %v", i, err) 1139 } 1140 if exists { 1141 continue 1142 } 1143 if err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i])); err != nil { 1144 t.Fatalf("failed to push test content to src: %d: %v", i, err) 1145 } 1146 } 1147 1148 root := descs[3] 1149 ref := "latest" 1150 err = src.Tag(ctx, root, ref) 1151 if err != nil { 1152 t.Fatal("fail to tag root node", err) 1153 } 1154 1155 // test copy 1156 gotDesc, err := oras.Copy(ctx, src, ref, dst, "", oras.CopyOptions{}) 1157 if err != nil { 1158 t.Fatalf("Copy() error = %v, wantErr %v", err, false) 1159 } 1160 if !reflect.DeepEqual(gotDesc, root) { 1161 t.Errorf("Copy() = %v, want %v", gotDesc, root) 1162 } 1163 1164 // verify contents 1165 for i, desc := range descs { 1166 exists, err := dst.Exists(ctx, desc) 1167 if err != nil { 1168 t.Fatalf("dst.Exists(%d) error = %v", i, err) 1169 } 1170 if !exists { 1171 t.Errorf("dst.Exists(%d) = %v, want %v", i, exists, true) 1172 } 1173 } 1174 1175 // verify tag 1176 gotDesc, err = dst.Resolve(ctx, ref) 1177 if err != nil { 1178 t.Fatal("dst.Resolve() error =", err) 1179 } 1180 if !reflect.DeepEqual(gotDesc, root) { 1181 t.Errorf("dst.Resolve() = %v, want %v", gotDesc, root) 1182 } 1183 } 1184 1185 func TestCopy_DiscardDuplicates(t *testing.T) { 1186 src := memory.New() 1187 temp := t.TempDir() 1188 dst, err := file.New(temp) 1189 if err != nil { 1190 t.Fatal("file.New() error =", err) 1191 } 1192 dst.ForceCAS = true 1193 defer dst.Close() 1194 1195 // generate test content 1196 var blobs [][]byte 1197 var descs []ocispec.Descriptor 1198 appendBlob := func(mediaType string, blob []byte, title string) { 1199 blobs = append(blobs, blob) 1200 desc := ocispec.Descriptor{ 1201 MediaType: mediaType, 1202 Digest: digest.FromBytes(blob), 1203 Size: int64(len(blob)), 1204 } 1205 if title != "" { 1206 desc.Annotations = map[string]string{ 1207 ocispec.AnnotationTitle: title, 1208 } 1209 } 1210 descs = append(descs, desc) 1211 } 1212 generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) { 1213 manifest := ocispec.Manifest{ 1214 Config: config, 1215 Layers: layers, 1216 } 1217 manifestJSON, err := json.Marshal(manifest) 1218 if err != nil { 1219 t.Fatal(err) 1220 } 1221 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON, "") 1222 } 1223 1224 appendBlob(ocispec.MediaTypeImageConfig, []byte("{}"), "") // Blob 0 1225 // 2 blobs with same content 1226 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello"), "foo.txt") // Blob 1 1227 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello"), "bar.txt") // Blob 2 1228 generateManifest(descs[0], descs[1:3]...) // Blob 3 1229 1230 ctx := context.Background() 1231 for i := range blobs { 1232 exists, err := src.Exists(ctx, descs[i]) 1233 if err != nil { 1234 t.Fatalf("failed to check existence in src: %d: %v", i, err) 1235 } 1236 if exists { 1237 continue 1238 } 1239 if err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i])); err != nil { 1240 t.Fatalf("failed to push test content to src: %d: %v", i, err) 1241 } 1242 } 1243 1244 root := descs[3] 1245 ref := "latest" 1246 err = src.Tag(ctx, root, ref) 1247 if err != nil { 1248 t.Fatal("fail to tag root node", err) 1249 } 1250 1251 // test copy 1252 gotDesc, err := oras.Copy(ctx, src, ref, dst, "", oras.CopyOptions{}) 1253 if err != nil { 1254 t.Fatalf("Copy() error = %v, wantErr %v", err, false) 1255 } 1256 if !reflect.DeepEqual(gotDesc, root) { 1257 t.Errorf("Copy() = %v, want %v", gotDesc, root) 1258 } 1259 1260 // verify only one of foo.txt and bar.txt exists 1261 fooExists, err := dst.Exists(ctx, descs[1]) 1262 if err != nil { 1263 t.Fatalf("dst.Exists(foo) error = %v", err) 1264 } 1265 barExists, err := dst.Exists(ctx, descs[2]) 1266 if err != nil { 1267 t.Fatalf("dst.Exists(bar) error = %v", err) 1268 } 1269 if fooExists == barExists { 1270 t.Error("Only one of foo.txt and bar.txt should exist") 1271 } 1272 } 1273 1274 func TestCopyGraph_WithOptions(t *testing.T) { 1275 src := cas.NewMemory() 1276 dst := cas.NewMemory() 1277 1278 // generate test content 1279 var blobs [][]byte 1280 var descs []ocispec.Descriptor 1281 appendBlob := func(mediaType string, blob []byte) { 1282 blobs = append(blobs, blob) 1283 descs = append(descs, ocispec.Descriptor{ 1284 MediaType: mediaType, 1285 Digest: digest.FromBytes(blob), 1286 Size: int64(len(blob)), 1287 }) 1288 } 1289 generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) { 1290 manifest := ocispec.Manifest{ 1291 Config: config, 1292 Layers: layers, 1293 } 1294 manifestJSON, err := json.Marshal(manifest) 1295 if err != nil { 1296 t.Fatal(err) 1297 } 1298 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 1299 } 1300 generateIndex := func(manifests ...ocispec.Descriptor) { 1301 index := ocispec.Index{ 1302 Manifests: manifests, 1303 } 1304 indexJSON, err := json.Marshal(index) 1305 if err != nil { 1306 t.Fatal(err) 1307 } 1308 appendBlob(ocispec.MediaTypeImageIndex, indexJSON) 1309 } 1310 1311 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 1312 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1 1313 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2 1314 generateManifest(descs[0], descs[1:3]...) // Blob 3 1315 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello")) // Blob 4 1316 generateManifest(descs[0], descs[4]) // Blob 5 1317 generateIndex(descs[3], descs[5]) // Blob 6 1318 1319 ctx := context.Background() 1320 for i := range blobs { 1321 err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 1322 if err != nil { 1323 t.Fatalf("failed to push test content to src: %d: %v", i, err) 1324 } 1325 } 1326 1327 // initial copy 1328 root := descs[3] 1329 opts := oras.DefaultCopyGraphOptions 1330 opts.FindSuccessors = func(ctx context.Context, fetcher content.Fetcher, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { 1331 successors, err := content.Successors(ctx, fetcher, desc) 1332 if err != nil { 1333 return nil, err 1334 } 1335 // filter media type 1336 var filtered []ocispec.Descriptor 1337 for _, s := range successors { 1338 if s.MediaType != ocispec.MediaTypeImageConfig { 1339 filtered = append(filtered, s) 1340 } 1341 } 1342 return filtered, nil 1343 } 1344 if err := oras.CopyGraph(ctx, src, dst, root, opts); err != nil { 1345 t.Fatalf("CopyGraph() error = %v, wantErr %v", err, false) 1346 } 1347 // verify contents 1348 contents := dst.Map() 1349 if got, want := len(contents), len(blobs[1:4]); got != want { 1350 t.Fatalf("len(dst) = %v, wantErr %v", got, want) 1351 } 1352 for i := 1; i < 4; i++ { 1353 got, err := content.FetchAll(ctx, dst, descs[i]) 1354 if err != nil { 1355 t.Fatalf("content[%d] error = %v, wantErr %v", i, err, false) 1356 } 1357 if want := blobs[i]; !bytes.Equal(got, want) { 1358 t.Fatalf("content[%d] = %v, want %v", i, got, want) 1359 } 1360 } 1361 1362 // test successor descriptors not obtained from src 1363 root = descs[3] 1364 opts = oras.DefaultCopyGraphOptions 1365 opts.FindSuccessors = func(ctx context.Context, fetcher content.Fetcher, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { 1366 if content.Equal(desc, root) { 1367 return descs[1:3], nil 1368 } 1369 return content.Successors(ctx, fetcher, desc) 1370 } 1371 if err := oras.CopyGraph(ctx, src, cas.NewMemory(), root, opts); err != nil { 1372 t.Fatalf("CopyGraph() error = %v, wantErr %v", err, false) 1373 } 1374 1375 // test partial copy 1376 var preCopyCount int64 1377 var postCopyCount int64 1378 var skippedCount int64 1379 opts = oras.CopyGraphOptions{ 1380 PreCopy: func(ctx context.Context, desc ocispec.Descriptor) error { 1381 atomic.AddInt64(&preCopyCount, 1) 1382 return nil 1383 }, 1384 PostCopy: func(ctx context.Context, desc ocispec.Descriptor) error { 1385 atomic.AddInt64(&postCopyCount, 1) 1386 return nil 1387 }, 1388 OnCopySkipped: func(ctx context.Context, desc ocispec.Descriptor) error { 1389 atomic.AddInt64(&skippedCount, 1) 1390 return nil 1391 }, 1392 } 1393 root = descs[len(descs)-1] 1394 if err := oras.CopyGraph(ctx, src, dst, root, opts); err != nil { 1395 t.Fatalf("CopyGraph() error = %v, wantErr %v", err, false) 1396 } 1397 1398 // verify contents 1399 contents = dst.Map() 1400 if got, want := len(contents), len(blobs); got != want { 1401 t.Errorf("len(dst) = %v, wantErr %v", got, want) 1402 } 1403 for i := range blobs { 1404 got, err := content.FetchAll(ctx, dst, descs[i]) 1405 if err != nil { 1406 t.Errorf("content[%d] error = %v, wantErr %v", i, err, false) 1407 continue 1408 } 1409 if want := blobs[i]; !bytes.Equal(got, want) { 1410 t.Errorf("content[%d] = %v, want %v", i, got, want) 1411 } 1412 } 1413 1414 // verify API counts 1415 if got, want := preCopyCount, int64(4); got != want { 1416 t.Errorf("count(PreCopy()) = %v, want %v", got, want) 1417 } 1418 if got, want := postCopyCount, int64(4); got != want { 1419 t.Errorf("count(PostCopy()) = %v, want %v", got, want) 1420 } 1421 if got, want := skippedCount, int64(1); got != want { 1422 t.Errorf("count(OnCopySkipped()) = %v, want %v", got, want) 1423 } 1424 1425 // test CopyGraph with MaxMetadataBytes = 1 1426 root = descs[6] 1427 dst = cas.NewMemory() 1428 opts = oras.CopyGraphOptions{ 1429 MaxMetadataBytes: 1, 1430 } 1431 if err := oras.CopyGraph(ctx, src, dst, root, opts); !errors.Is(err, errdef.ErrSizeExceedsLimit) { 1432 t.Fatalf("CopyGraph() error = %v, wantErr %v", err, errdef.ErrSizeExceedsLimit) 1433 } 1434 } 1435 1436 func TestCopyGraph_WithConcurrencyLimit(t *testing.T) { 1437 src := cas.NewMemory() 1438 // generate test content 1439 var blobs [][]byte 1440 var descs []ocispec.Descriptor 1441 appendBlob := func(mediaType string, blob []byte) { 1442 blobs = append(blobs, blob) 1443 descs = append(descs, ocispec.Descriptor{ 1444 MediaType: mediaType, 1445 Digest: digest.FromBytes(blob), 1446 Size: int64(len(blob)), 1447 }) 1448 } 1449 generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) { 1450 manifest := ocispec.Manifest{ 1451 MediaType: ocispec.MediaTypeImageManifest, 1452 Config: config, 1453 Layers: layers, 1454 } 1455 manifestJSON, err := json.Marshal(manifest) 1456 if err != nil { 1457 t.Fatal(err) 1458 } 1459 appendBlob(manifest.MediaType, manifestJSON) 1460 } 1461 generateArtifact := func(subject *ocispec.Descriptor, artifactType string, blobs ...ocispec.Descriptor) { 1462 manifest := ocispec.Artifact{ 1463 MediaType: ocispec.MediaTypeArtifactManifest, 1464 Subject: subject, 1465 Blobs: blobs, 1466 ArtifactType: artifactType, 1467 } 1468 manifestJSON, err := json.Marshal(manifest) 1469 if err != nil { 1470 t.Fatal(err) 1471 } 1472 appendBlob(manifest.MediaType, manifestJSON) 1473 } 1474 generateIndex := func(manifests ...ocispec.Descriptor) { 1475 index := ocispec.Index{ 1476 MediaType: ocispec.MediaTypeImageIndex, 1477 Manifests: manifests, 1478 } 1479 indexJSON, err := json.Marshal(index) 1480 if err != nil { 1481 t.Fatal(err) 1482 } 1483 appendBlob(index.MediaType, indexJSON) 1484 } 1485 1486 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 1487 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1 1488 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2 1489 generateManifest(descs[0], descs[1:3]...) // Blob 3 1490 generateArtifact(&descs[3], "artifact.1") // Blob 4 1491 generateArtifact(&descs[3], "artifact.2") // Blob 5 1492 generateArtifact(&descs[3], "artifact.3") // Blob 6 1493 generateArtifact(&descs[3], "artifact.4") // Blob 7 1494 generateIndex(descs[3:8]...) // Blob 8 1495 1496 ctx := context.Background() 1497 for i := range blobs { 1498 err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 1499 if err != nil { 1500 t.Fatalf("failed to push test content to src: %d: %v", i, err) 1501 } 1502 } 1503 1504 // test different concurrency limit 1505 root := descs[len(descs)-1] 1506 directSuccessorsNum := 5 1507 opts := oras.DefaultCopyGraphOptions 1508 for i := 1; i <= directSuccessorsNum; i++ { 1509 dst := cas.NewMemory() 1510 opts.Concurrency = i 1511 if err := oras.CopyGraph(ctx, src, dst, root, opts); err != nil { 1512 t.Fatalf("CopyGraph(concurrency: %d) error = %v, wantErr %v", i, err, false) 1513 } 1514 1515 // verify contents 1516 contents := dst.Map() 1517 if got, want := len(contents), len(blobs); got != want { 1518 t.Errorf("len(dst) = %v, wantErr %v", got, want) 1519 } 1520 for i := range blobs { 1521 got, err := content.FetchAll(ctx, dst, descs[i]) 1522 if err != nil { 1523 t.Errorf("content[%d] error = %v, wantErr %v", i, err, false) 1524 continue 1525 } 1526 if want := blobs[i]; !bytes.Equal(got, want) { 1527 t.Errorf("content[%d] = %v, want %v", i, got, want) 1528 } 1529 } 1530 } 1531 } 1532 1533 func TestCopyGraph_ForeignLayers(t *testing.T) { 1534 src := cas.NewMemory() 1535 dst := cas.NewMemory() 1536 1537 // generate test content 1538 var blobs [][]byte 1539 var descs []ocispec.Descriptor 1540 appendBlob := func(mediaType string, blob []byte) { 1541 desc := ocispec.Descriptor{ 1542 MediaType: mediaType, 1543 Digest: digest.FromBytes(blob), 1544 Size: int64(len(blob)), 1545 } 1546 if mediaType == ocispec.MediaTypeImageLayerNonDistributable { 1547 desc.URLs = append(desc.URLs, "http://127.0.0.1/dummy") 1548 blob = nil 1549 } 1550 descs = append(descs, desc) 1551 blobs = append(blobs, blob) 1552 } 1553 generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) { 1554 manifest := ocispec.Manifest{ 1555 Config: config, 1556 Layers: layers, 1557 } 1558 manifestJSON, err := json.Marshal(manifest) 1559 if err != nil { 1560 t.Fatal(err) 1561 } 1562 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 1563 } 1564 1565 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 1566 appendBlob(ocispec.MediaTypeImageLayerNonDistributable, []byte("hello")) // Blob 1 1567 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 2 1568 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 3 1569 generateManifest(descs[0], descs[1:4]...) // Blob 4 1570 1571 ctx := context.Background() 1572 for i := range blobs { 1573 if blobs[i] == nil { 1574 continue 1575 } 1576 err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 1577 if err != nil { 1578 t.Fatalf("failed to push test content to src: %d: %v", i, err) 1579 } 1580 } 1581 1582 // test copy 1583 srcTracker := &storageTracker{Storage: src} 1584 dstTracker := &storageTracker{Storage: dst} 1585 root := descs[len(descs)-1] 1586 if err := oras.CopyGraph(ctx, srcTracker, dstTracker, root, oras.CopyGraphOptions{}); err != nil { 1587 t.Fatalf("CopyGraph() error = %v, wantErr %v", err, false) 1588 } 1589 1590 // verify contents 1591 contents := dst.Map() 1592 if got, want := len(contents), len(blobs)-1; got != want { 1593 t.Errorf("len(dst) = %v, wantErr %v", got, want) 1594 } 1595 for i := range blobs { 1596 if blobs[i] == nil { 1597 continue 1598 } 1599 got, err := content.FetchAll(ctx, dst, descs[i]) 1600 if err != nil { 1601 t.Errorf("content[%d] error = %v, wantErr %v", i, err, false) 1602 continue 1603 } 1604 if want := blobs[i]; !bytes.Equal(got, want) { 1605 t.Errorf("content[%d] = %v, want %v", i, got, want) 1606 } 1607 } 1608 1609 // verify API counts 1610 if got, want := srcTracker.fetch, int64(len(blobs)-1); got != want { 1611 t.Errorf("count(src.Fetch()) = %v, want %v", got, want) 1612 } 1613 if got, want := srcTracker.push, int64(0); got != want { 1614 t.Errorf("count(src.Push()) = %v, want %v", got, want) 1615 } 1616 if got, want := srcTracker.exists, int64(0); got != want { 1617 t.Errorf("count(src.Exists()) = %v, want %v", got, want) 1618 } 1619 if got, want := dstTracker.fetch, int64(0); got != want { 1620 t.Errorf("count(dst.Fetch()) = %v, want %v", got, want) 1621 } 1622 if got, want := dstTracker.push, int64(len(blobs)-1); got != want { 1623 t.Errorf("count(dst.Push()) = %v, want %v", got, want) 1624 } 1625 if got, want := dstTracker.exists, int64(len(blobs)-1); got != want { 1626 t.Errorf("count(dst.Exists()) = %v, want %v", got, want) 1627 } 1628 } 1629 1630 func TestCopyGraph_ForeignLayers_Mixed(t *testing.T) { 1631 src := cas.NewMemory() 1632 dst := cas.NewMemory() 1633 1634 // generate test content 1635 var blobs [][]byte 1636 var descs []ocispec.Descriptor 1637 appendBlob := func(mediaType string, blob []byte) { 1638 desc := ocispec.Descriptor{ 1639 MediaType: mediaType, 1640 Digest: digest.FromBytes(blob), 1641 Size: int64(len(blob)), 1642 } 1643 if mediaType == ocispec.MediaTypeImageLayerNonDistributable { 1644 desc.URLs = append(desc.URLs, "http://127.0.0.1/dummy") 1645 blob = nil 1646 } 1647 descs = append(descs, desc) 1648 blobs = append(blobs, blob) 1649 } 1650 generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) { 1651 manifest := ocispec.Manifest{ 1652 Config: config, 1653 Layers: layers, 1654 } 1655 manifestJSON, err := json.Marshal(manifest) 1656 if err != nil { 1657 t.Fatal(err) 1658 } 1659 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 1660 } 1661 1662 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 1663 appendBlob(ocispec.MediaTypeImageLayerNonDistributable, []byte("hello")) // Blob 1 1664 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello")) // Blob 2 1665 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 3 1666 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 4 1667 generateManifest(descs[0], descs[1:5]...) // Blob 5 1668 1669 ctx := context.Background() 1670 for i := range blobs { 1671 if blobs[i] == nil { 1672 continue 1673 } 1674 err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 1675 if err != nil { 1676 t.Fatalf("failed to push test content to src: %d: %v", i, err) 1677 } 1678 } 1679 1680 // test copy 1681 srcTracker := &storageTracker{Storage: src} 1682 dstTracker := &storageTracker{Storage: dst} 1683 root := descs[len(descs)-1] 1684 if err := oras.CopyGraph(ctx, srcTracker, dstTracker, root, oras.CopyGraphOptions{ 1685 Concurrency: 1, 1686 }); err != nil { 1687 t.Fatalf("CopyGraph() error = %v, wantErr %v", err, false) 1688 } 1689 1690 // verify contents 1691 contents := dst.Map() 1692 if got, want := len(contents), len(blobs)-1; got != want { 1693 t.Errorf("len(dst) = %v, wantErr %v", got, want) 1694 } 1695 for i := range blobs { 1696 if blobs[i] == nil { 1697 continue 1698 } 1699 got, err := content.FetchAll(ctx, dst, descs[i]) 1700 if err != nil { 1701 t.Errorf("content[%d] error = %v, wantErr %v", i, err, false) 1702 continue 1703 } 1704 if want := blobs[i]; !bytes.Equal(got, want) { 1705 t.Errorf("content[%d] = %v, want %v", i, got, want) 1706 } 1707 } 1708 1709 // verify API counts 1710 if got, want := srcTracker.fetch, int64(len(blobs)-1); got != want { 1711 t.Errorf("count(src.Fetch()) = %v, want %v", got, want) 1712 } 1713 if got, want := srcTracker.push, int64(0); got != want { 1714 t.Errorf("count(src.Push()) = %v, want %v", got, want) 1715 } 1716 if got, want := srcTracker.exists, int64(0); got != want { 1717 t.Errorf("count(src.Exists()) = %v, want %v", got, want) 1718 } 1719 if got, want := dstTracker.fetch, int64(0); got != want { 1720 t.Errorf("count(dst.Fetch()) = %v, want %v", got, want) 1721 } 1722 if got, want := dstTracker.push, int64(len(blobs)-1); got != want { 1723 t.Errorf("count(dst.Push()) = %v, want %v", got, want) 1724 } 1725 if got, want := dstTracker.exists, int64(len(blobs)-1); got != want { 1726 t.Errorf("count(dst.Exists()) = %v, want %v", got, want) 1727 } 1728 }