github.com/docker/cnab-to-oci@v0.3.0-beta4/remotes/fixup_test.go (about) 1 package remotes 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "io" 8 "io/ioutil" 9 "os" 10 "testing" 11 12 "github.com/cnabio/cnab-go/bundle" 13 "github.com/containerd/containerd/images" 14 "github.com/containerd/containerd/platforms" 15 "github.com/docker/cnab-to-oci/relocation" 16 "github.com/docker/distribution/reference" 17 "github.com/opencontainers/go-digest" 18 ocischemav1 "github.com/opencontainers/image-spec/specs-go/v1" 19 "gotest.tools/assert" 20 ) 21 22 func TestFixupBundleWithAutoUpdate(t *testing.T) { 23 index := ocischemav1.Manifest{} 24 bufManifest, err := json.Marshal(index) 25 assert.NilError(t, err) 26 fetcher := &mockFetcher{indexBuffers: []*bytes.Buffer{ 27 // Manifest index 28 bytes.NewBuffer(bufManifest), 29 }} 30 pusher := &mockPusher{} 31 resolver := &mockResolver{ 32 pusher: pusher, 33 fetcher: fetcher, 34 resolvedDescriptors: []ocischemav1.Descriptor{ 35 // Resolving source Invocation image manifest descriptor my.registry/namespace/my-app-invoc 36 { 37 MediaType: ocischemav1.MediaTypeImageManifest, 38 Size: 42, 39 Digest: "sha256:beef1aa7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0343", 40 }, 41 // Target Invocation image manifest descriptor my.registry/namespace/my-app@sha256:beef1aa7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0343 for mounting 42 {}, 43 // Resolving source service image manifest descriptor my.registry/namespace/my-service 44 { 45 MediaType: ocischemav1.MediaTypeImageManifest, 46 Size: 43, 47 Digest: "sha256:beef1aa7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0344", 48 }, 49 // Target service image manifest descriptor my.registry/namespace/my-app@sha256:beef1aa7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0344 for mounting 50 {}, 51 }, 52 } 53 b := &bundle.Bundle{ 54 SchemaVersion: "v1.0.0", 55 InvocationImages: []bundle.InvocationImage{ 56 { 57 BaseImage: bundle.BaseImage{ 58 Image: "my.registry/namespace/my-app-invoc", 59 ImageType: "docker", 60 }, 61 }, 62 }, 63 Images: map[string]bundle.Image{ 64 "my-service": { 65 BaseImage: bundle.BaseImage{ 66 Image: "my.registry/namespace/my-service", 67 ImageType: "docker", 68 }, 69 }, 70 }, 71 Name: "my-app", 72 Version: "0.1.0", 73 } 74 ref, err := reference.ParseNamed("my.registry/namespace/my-app") 75 assert.NilError(t, err) 76 _, err = FixupBundle(context.TODO(), b, ref, resolver, WithAutoBundleUpdate()) 77 assert.NilError(t, err) 78 expectedBundle := &bundle.Bundle{ 79 SchemaVersion: "v1.0.0", 80 InvocationImages: []bundle.InvocationImage{ 81 { 82 BaseImage: bundle.BaseImage{ 83 Image: "my.registry/namespace/my-app-invoc", 84 ImageType: "docker", 85 MediaType: ocischemav1.MediaTypeImageManifest, 86 Size: 42, 87 Digest: "sha256:beef1aa7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0343", 88 }, 89 }, 90 }, 91 Images: map[string]bundle.Image{ 92 "my-service": { 93 BaseImage: bundle.BaseImage{ 94 Image: "my.registry/namespace/my-service", 95 ImageType: "docker", 96 MediaType: ocischemav1.MediaTypeImageManifest, 97 Size: 43, 98 Digest: "sha256:beef1aa7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0344", 99 }, 100 }, 101 }, 102 Name: "my-app", 103 Version: "0.1.0", 104 } 105 assert.DeepEqual(t, b, expectedBundle) 106 } 107 108 func TestFixupBundlePushImages(t *testing.T) { 109 index := ocischemav1.Manifest{} 110 bufManifest, err := json.Marshal(index) 111 assert.NilError(t, err) 112 fetcher := &mockFetcher{indexBuffers: []*bytes.Buffer{ 113 // Manifest index 114 bytes.NewBuffer(bufManifest), 115 }} 116 pusher := &mockPusher{} 117 resolver := &mockResolver{ 118 pusher: pusher, 119 fetcher: fetcher, 120 resolvedDescriptors: []ocischemav1.Descriptor{ 121 // Invocation image will not be resolved first, so push will occurs 122 { 123 // just a code to raise an error in the mock 124 Size: -1, 125 }, 126 // Invocation image is resolved after push 127 { 128 MediaType: ocischemav1.MediaTypeImageManifest, 129 Size: 42, 130 Digest: "sha256:beef1aa7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0343", 131 }, 132 // Image will be resolved after push based on Digest 133 { 134 MediaType: ocischemav1.MediaTypeImageManifest, 135 Size: 43, 136 Digest: "sha256:beef1aa7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0344", 137 }, 138 }, 139 } 140 b := &bundle.Bundle{ 141 SchemaVersion: "v1.0.0", 142 InvocationImages: []bundle.InvocationImage{ 143 { 144 BaseImage: bundle.BaseImage{ 145 Image: "my.registry/namespace/my-app-invoc", 146 ImageType: "docker", 147 }, 148 }, 149 }, 150 Images: map[string]bundle.Image{ 151 "my-service": { 152 BaseImage: bundle.BaseImage{ 153 Digest: "sha256:beef1aa7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0344", 154 Image: "", 155 ImageType: "docker", 156 }, 157 }, 158 }, 159 Name: "my-app", 160 Version: "0.1.0", 161 } 162 imageClient := newMockImageClient() 163 ref, err := reference.ParseNamed("my.registry/namespace/my-app") 164 assert.NilError(t, err) 165 _, err = FixupBundle(context.TODO(), b, ref, resolver, WithAutoBundleUpdate(), WithPushImages(imageClient, os.Stdout)) 166 assert.NilError(t, err) 167 // 2 images has been pushed 168 assert.Equal(t, imageClient.pushedImages, 2) 169 expectedBundle := &bundle.Bundle{ 170 SchemaVersion: "v1.0.0", 171 InvocationImages: []bundle.InvocationImage{ 172 { 173 BaseImage: bundle.BaseImage{ 174 Image: "my.registry/namespace/my-app-invoc", 175 ImageType: "docker", 176 MediaType: ocischemav1.MediaTypeImageManifest, 177 Size: 42, 178 Digest: "sha256:beef1aa7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0343", 179 }, 180 }, 181 }, 182 Images: map[string]bundle.Image{ 183 "my-service": { 184 BaseImage: bundle.BaseImage{ 185 Image: "", 186 ImageType: "docker", 187 MediaType: ocischemav1.MediaTypeImageManifest, 188 Size: 43, 189 Digest: "sha256:beef1aa7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0344", 190 }, 191 }, 192 }, 193 Name: "my-app", 194 Version: "0.1.0", 195 } 196 assert.DeepEqual(t, b, expectedBundle) 197 } 198 199 func TestFixupBundleCheckResolveOrder(t *testing.T) { 200 index := ocischemav1.Manifest{} 201 bufManifest, err := json.Marshal(index) 202 assert.NilError(t, err) 203 fetcher := &mockFetcher{indexBuffers: []*bytes.Buffer{ 204 // Manifest index 205 bytes.NewBuffer(bufManifest), 206 }} 207 pusher := &mockPusher{} 208 209 // Construct a test case 210 // - invocation map will be found in relocation map, resolved and copy 211 // - first service image will be found in relocation map but not resolved 212 // then the service image in the bundle will be resolved and copy 213 // - second service image will be found in relocation map but not resolved 214 // then the service image from the bundle will not be resolved 215 // then the image will be found locally and pushed 216 // - third service, image is not found in relocation map 217 // but resolved from bundle 218 // - fourth service, image is not found in relocation map, not resolvable 219 // but pushed 220 221 b := &bundle.Bundle{ 222 SchemaVersion: "v1.0.0", 223 InvocationImages: []bundle.InvocationImage{ 224 { 225 BaseImage: bundle.BaseImage{ 226 Image: "resolvable-from-relocation-map", 227 ImageType: "docker", 228 }, 229 }, 230 }, 231 Images: map[string]bundle.Image{ 232 "resolved-from-bundle": { 233 BaseImage: bundle.BaseImage{ 234 Digest: "sha256:beef1aa7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0344", 235 Image: "not-resolved-from-relocation-map", 236 ImageType: "docker", 237 }, 238 }, 239 "local-push": { 240 BaseImage: bundle.BaseImage{ 241 Digest: "sha256:beef1aa7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0344", 242 Image: "local-image-with-relocation-entry", 243 ImageType: "docker", 244 }, 245 }, 246 "not-in-relocation": { 247 BaseImage: bundle.BaseImage{ 248 Digest: "sha256:beef1aa7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0344", 249 Image: "not-in-relocation", 250 ImageType: "docker", 251 }, 252 }, 253 "not-in-relocation-but-local": { 254 BaseImage: bundle.BaseImage{ 255 Digest: "sha256:beef1aa7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0344", 256 Image: "local-image-not-in-relocation", 257 ImageType: "docker", 258 }, 259 }, 260 }, 261 Name: "my-app", 262 Version: "0.1.0", 263 } 264 relocationMap := relocation.ImageRelocationMap{ 265 "resolvable-from-relocation-map": "my.registry/other-namespace/my-app-invoc@sha256:beef1aa7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0344", 266 "not-resolved-from-relocation-map": "my.registry/other-namespace/my-app-invoc@sha256:beef1aa7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0344", 267 "local-image-with-relocation-entry": "my.registry/other-namespace/my-app-invoc@sha256:beef1aa7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0344", 268 } 269 nbImagePushed := 2 // "local-push" and "not-in-relocation-but-local" services 270 271 resolver := &mockResolver{ 272 pusher: pusher, 273 fetcher: fetcher, 274 resolvedDescriptors: []ocischemav1.Descriptor{ 275 // Invocation image first 276 // Resolvable 277 { 278 MediaType: ocischemav1.MediaTypeImageManifest, 279 Size: 42, 280 Digest: "sha256:beef1aa7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0343", 281 }, 282 // This one is from the copy task 283 { 284 MediaType: ocischemav1.MediaTypeImageManifest, 285 Size: 42, 286 Digest: "sha256:beef1aa7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0343", 287 }, 288 289 // First image "resolved-from-bundle" 290 // not resolvable from relocation map 291 { 292 Size: -1, 293 }, 294 // resolved by second pass, from the bundle 295 { 296 MediaType: ocischemav1.MediaTypeImageManifest, 297 Size: 42, 298 Digest: "sha256:beef1aa7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0343", 299 }, 300 // copy task 301 { 302 MediaType: ocischemav1.MediaTypeImageManifest, 303 Size: 42, 304 Digest: "sha256:beef1aa7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0343", 305 }, 306 307 // Second image "local-push" 308 // not resolvable from relocation map 309 { 310 Size: -1, 311 }, 312 // not resolvable from bundle 313 { 314 Size: -1, 315 }, 316 // image is pushed, resolve is called at the end 317 { 318 MediaType: ocischemav1.MediaTypeImageManifest, 319 Size: 42, 320 Digest: "sha256:beef1aa7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0343", 321 }, 322 323 // Third image "not-in-relocation" 324 // not in relocation map but resolvable 325 { 326 MediaType: ocischemav1.MediaTypeImageManifest, 327 Size: 42, 328 Digest: "sha256:beef1aa7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0343", 329 }, 330 // copy task 331 { 332 MediaType: ocischemav1.MediaTypeImageManifest, 333 Size: 42, 334 Digest: "sha256:beef1aa7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0343", 335 }, 336 337 // Fourth image "not-in-relocation-but-local" 338 // not resolvable 339 { 340 Size: -1, 341 }, 342 // image is pushed, resolve is called at the end 343 { 344 MediaType: ocischemav1.MediaTypeImageManifest, 345 Size: 42, 346 Digest: "sha256:beef1aa7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0343", 347 }, 348 }, 349 } 350 imageClient := newMockImageClient() 351 ref, err := reference.ParseNamed("my.registry/namespace/my-app") 352 assert.NilError(t, err) 353 _, err = FixupBundle(context.TODO(), b, ref, resolver, WithAutoBundleUpdate(), WithPushImages(imageClient, os.Stdout), WithRelocationMap(relocationMap)) 354 assert.NilError(t, err) 355 assert.Equal(t, imageClient.pushedImages, nbImagePushed) 356 } 357 358 func TestFixupBundleFailsWithDifferentDigests(t *testing.T) { 359 index := ocischemav1.Manifest{} 360 bufManifest, err := json.Marshal(index) 361 assert.NilError(t, err) 362 fetcher := &mockFetcher{indexBuffers: []*bytes.Buffer{ 363 // Manifest index 364 bytes.NewBuffer(bufManifest), 365 }} 366 pusher := &mockPusher{} 367 resolver := &mockResolver{ 368 pusher: pusher, 369 fetcher: fetcher, 370 resolvedDescriptors: []ocischemav1.Descriptor{ 371 // Invocation image manifest descriptor 372 { 373 MediaType: ocischemav1.MediaTypeImageManifest, 374 Size: 42, 375 Digest: "sha256:c0ffeea7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0343", 376 }, 377 {}, 378 }, 379 } 380 b := &bundle.Bundle{ 381 SchemaVersion: "v1.0.0", 382 InvocationImages: []bundle.InvocationImage{ 383 { 384 BaseImage: bundle.BaseImage{ 385 Image: "my.registry/namespace/my-app-invoc", 386 ImageType: "docker", 387 Digest: "beef00a7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0343", 388 Size: 42, 389 MediaType: ocischemav1.MediaTypeImageManifest, 390 }, 391 }, 392 }, 393 Name: "my-app", 394 Version: "0.1.0", 395 } 396 ref, err := reference.ParseNamed("my.registry/namespace/my-app") 397 assert.NilError(t, err) 398 _, err = FixupBundle(context.TODO(), b, ref, resolver) 399 assert.ErrorContains(t, err, "digest differs") 400 } 401 402 func TestFixupBundleFailsWithDifferentSizes(t *testing.T) { 403 index := ocischemav1.Manifest{} 404 bufManifest, err := json.Marshal(index) 405 assert.NilError(t, err) 406 fetcher := &mockFetcher{indexBuffers: []*bytes.Buffer{ 407 // Manifest index 408 bytes.NewBuffer(bufManifest), 409 }} 410 pusher := &mockPusher{} 411 resolver := &mockResolver{ 412 pusher: pusher, 413 fetcher: fetcher, 414 resolvedDescriptors: []ocischemav1.Descriptor{ 415 // Invocation image manifest descriptor 416 { 417 MediaType: ocischemav1.MediaTypeImageManifest, 418 Size: 43, 419 Digest: "sha256:c0ffeea7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0343", 420 }, 421 {}, 422 }, 423 } 424 425 b := &bundle.Bundle{ 426 SchemaVersion: "v1.0.0", 427 InvocationImages: []bundle.InvocationImage{ 428 { 429 BaseImage: bundle.BaseImage{ 430 Image: "my.registry/namespace/my-app-invoc", 431 ImageType: "docker", 432 Digest: "sha256:c0ffeea7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0343", 433 Size: 42, 434 MediaType: ocischemav1.MediaTypeImageManifest, 435 }, 436 }, 437 }, 438 Name: "my-app", 439 Version: "0.1.0", 440 } 441 ref, err := reference.ParseNamed("my.registry/namespace/my-app") 442 assert.NilError(t, err) 443 _, err = FixupBundle(context.TODO(), b, ref, resolver) 444 assert.ErrorContains(t, err, "size differs") 445 } 446 447 func TestFixupBundleFailsWithDifferentMediaTypes(t *testing.T) { 448 index := ocischemav1.Manifest{} 449 bufManifest, err := json.Marshal(index) 450 assert.NilError(t, err) 451 fetcher := &mockFetcher{indexBuffers: []*bytes.Buffer{ 452 // Manifest index 453 bytes.NewBuffer(bufManifest), 454 }} 455 pusher := &mockPusher{} 456 resolver := &mockResolver{ 457 pusher: pusher, 458 fetcher: fetcher, 459 resolvedDescriptors: []ocischemav1.Descriptor{ 460 // Invocation image manifest descriptor 461 { 462 MediaType: ocischemav1.MediaTypeImageIndex, 463 Size: 42, 464 Digest: "sha256:c0ffeea7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0343", 465 }, 466 {}, 467 }, 468 } 469 470 b := &bundle.Bundle{ 471 SchemaVersion: "v1.0.0", 472 InvocationImages: []bundle.InvocationImage{ 473 { 474 BaseImage: bundle.BaseImage{ 475 Image: "my.registry/namespace/my-app-invoc", 476 ImageType: "docker", 477 Digest: "sha256:c0ffeea7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0343", 478 Size: 42, 479 MediaType: ocischemav1.MediaTypeImageManifest, 480 }, 481 }, 482 }, 483 Name: "my-app", 484 Version: "0.1.0", 485 } 486 ref, err := reference.ParseNamed("my.registry/namespace/my-app") 487 assert.NilError(t, err) 488 _, err = FixupBundle(context.TODO(), b, ref, resolver) 489 assert.ErrorContains(t, err, "media type differs") 490 } 491 492 func TestFixupPlatformShortPaths(t *testing.T) { 493 // those cases should not need to fetch any data 494 cases := []struct { 495 name string 496 platform string 497 mediaType string 498 }{ 499 { 500 name: "no-filter", 501 mediaType: ocischemav1.MediaTypeImageIndex, 502 }, 503 { 504 name: "oci-image", 505 platform: "linux/amd64", 506 mediaType: ocischemav1.MediaTypeImageManifest, 507 }, 508 { 509 name: "docker-image", 510 platform: "linux/amd64", 511 mediaType: images.MediaTypeDockerSchema2Manifest, 512 }, 513 { 514 name: "docker-image-schema1", 515 platform: "linux/amd64", 516 mediaType: images.MediaTypeDockerSchema1Manifest, 517 }, 518 } 519 520 for _, c := range cases { 521 t.Run(c.name, func(t *testing.T) { 522 var filter platforms.Matcher 523 if c.platform != "" { 524 filter = platforms.NewMatcher(platforms.MustParse(c.platform)) 525 } 526 assert.NilError(t, fixupPlatforms(context.Background(), &bundle.BaseImage{}, relocation.ImageRelocationMap{}, &imageFixupInfo{ 527 resolvedDescriptor: ocischemav1.Descriptor{ 528 MediaType: c.mediaType, 529 }, 530 }, nil, filter)) 531 }) 532 } 533 } 534 535 func TestFixupPlatforms(t *testing.T) { 536 cases := []struct { 537 name string 538 manifest *testManifest 539 filter []string 540 expectedResult *testManifest 541 expectedError string 542 }{ 543 { 544 name: "single-filter", 545 manifest: newTestManifest("linux/amd64", "windows/amd64"), 546 filter: []string{"linux/amd64"}, 547 expectedResult: newTestManifest("linux/amd64"), 548 }, 549 { 550 name: "multi-filter", 551 manifest: newTestManifest("linux/amd64", "windows/amd64", "linux/arm64"), 552 filter: []string{"linux/amd64", "linux/arm64"}, 553 expectedResult: newTestManifest("linux/amd64", "linux/arm64"), 554 }, 555 556 { 557 name: "no-match", 558 manifest: newTestManifest("linux/amd64", "windows/amd64"), 559 filter: []string{"linux/arm64"}, 560 expectedError: `no descriptor matching the platform filter found in "docker.io/docker/test@sha256:4ff4130e3c087b3dd1ce3d7e9d29316e707c0a793783aa76380a14c1dba9b536"`, 561 }, 562 } 563 for _, c := range cases { 564 t.Run(c.name, func(t *testing.T) { 565 // parse filter 566 plats, err := toPlatforms(c.filter) 567 assert.NilError(t, err) 568 filter := platforms.Any(plats...) 569 570 // setup fixupinfo, baseImage 571 sourceBytes, err := json.Marshal(c.manifest) 572 assert.NilError(t, err) 573 sourceDigest := digest.FromBytes(sourceBytes) 574 sourceRepo, err := reference.ParseNormalizedNamed("docker/test") 575 assert.NilError(t, err) 576 targetRepo, err := reference.ParseNormalizedNamed("docker/target") 577 assert.NilError(t, err) 578 sourceRef, err := reference.WithDigest(sourceRepo, sourceDigest) 579 assert.NilError(t, err) 580 bi := bundle.BaseImage{ 581 Image: sourceRef.String(), 582 } 583 fixupInfo := &imageFixupInfo{ 584 resolvedDescriptor: ocischemav1.Descriptor{ 585 Digest: sourceDigest, 586 Size: int64(len(sourceBytes)), 587 MediaType: ocischemav1.MediaTypeImageIndex, 588 }, 589 targetRepo: targetRepo, 590 sourceRef: sourceRef, 591 } 592 593 // setup source fetcher 594 sourceFetcher := newSourceFetcherWithLocalData(bytesFetcher(sourceBytes)) 595 596 // fixup 597 err = fixupPlatforms(context.Background(), &bi, relocation.ImageRelocationMap{}, fixupInfo, sourceFetcher, filter) 598 if c.expectedError != "" { 599 assert.ErrorContains(t, err, c.expectedError) 600 return 601 } 602 assert.NilError(t, err) 603 604 // baseImage.Image should have changed 605 // assert.Check(t, bi.Image != sourceRef.String()) 606 // resolved digest should have changed 607 assert.Check(t, fixupInfo.resolvedDescriptor.Digest != sourceDigest) 608 609 // parsing back the resolved manifest and making sure extra fields are still there 610 resolvedReader, err := sourceFetcher.Fetch(context.Background(), fixupInfo.resolvedDescriptor) 611 assert.NilError(t, err) 612 defer resolvedReader.Close() 613 resolvedBytes, err := ioutil.ReadAll(resolvedReader) 614 assert.NilError(t, err) 615 var resolvedManifest testManifest 616 assert.NilError(t, json.Unmarshal(resolvedBytes, &resolvedManifest)) 617 assert.DeepEqual(t, &resolvedManifest, c.expectedResult) 618 }) 619 } 620 } 621 622 type testManifest struct { 623 Manifests []testDescriptor `json:"manifests"` 624 Foo string `json:"foo"` 625 } 626 627 type testDescriptor struct { 628 Platform *ocischemav1.Platform `json:"platform,omitempty"` 629 Bar string `json:"bar"` 630 } 631 632 func newTestManifest(plats ...string) *testManifest { 633 m := &testManifest{ 634 Foo: "bar", 635 } 636 for _, p := range plats { 637 plat := platforms.MustParse(p) 638 m.Manifests = append(m.Manifests, testDescriptor{ 639 Bar: "baz", 640 Platform: &plat, 641 }) 642 } 643 return m 644 } 645 646 type bytesFetcher []byte 647 648 func (f bytesFetcher) Fetch(_ context.Context, _ ocischemav1.Descriptor) (io.ReadCloser, error) { 649 reader := bytes.NewReader(f) 650 return ioutil.NopCloser(reader), nil 651 }