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