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