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