oras.land/oras-go/v2@v2.5.1-0.20240520045656-aef90e4d04c4/internal/manifestutil/parser_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 manifestutil
    17  
    18  import (
    19  	"bytes"
    20  	"context"
    21  	"encoding/json"
    22  	"errors"
    23  	"fmt"
    24  	"io"
    25  	"reflect"
    26  	"testing"
    27  
    28  	"github.com/opencontainers/go-digest"
    29  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    30  	"golang.org/x/sync/errgroup"
    31  	"oras.land/oras-go/v2/content/memory"
    32  	"oras.land/oras-go/v2/internal/cas"
    33  	"oras.land/oras-go/v2/internal/container/set"
    34  	"oras.land/oras-go/v2/internal/docker"
    35  )
    36  
    37  var ErrBadFetch = errors.New("bad fetch error")
    38  
    39  // testStorage implements Fetcher
    40  type testStorage struct {
    41  	store    *memory.Store
    42  	badFetch set.Set[digest.Digest]
    43  }
    44  
    45  func (s *testStorage) Push(ctx context.Context, expected ocispec.Descriptor, reader io.Reader) error {
    46  	return s.store.Push(ctx, expected, reader)
    47  }
    48  
    49  func (s *testStorage) Fetch(ctx context.Context, target ocispec.Descriptor) (io.ReadCloser, error) {
    50  	if s.badFetch.Contains(target.Digest) {
    51  		return nil, ErrBadFetch
    52  	}
    53  	return s.store.Fetch(ctx, target)
    54  }
    55  
    56  // func (s *testStorage) Exists(ctx context.Context, target ocispec.Descriptor) (bool, error) {
    57  // 	return s.store.Exists(ctx, target)
    58  // }
    59  
    60  // func (s *testStorage) Predecessors(ctx context.Context, node ocispec.Descriptor) ([]ocispec.Descriptor, error) {
    61  // 	return s.store.Predecessors(ctx, node)
    62  // }
    63  
    64  func TestConfig(t *testing.T) {
    65  	storage := cas.NewMemory()
    66  
    67  	// generate test content
    68  	var blobs [][]byte
    69  	var descs []ocispec.Descriptor
    70  	appendBlob := func(mediaType string, blob []byte) {
    71  		blobs = append(blobs, blob)
    72  		descs = append(descs, ocispec.Descriptor{
    73  			MediaType: mediaType,
    74  			Digest:    digest.FromBytes(blob),
    75  			Size:      int64(len(blob)),
    76  		})
    77  	}
    78  	generateManifest := func(mediaType string, config ocispec.Descriptor, layers ...ocispec.Descriptor) {
    79  		manifest := ocispec.Manifest{
    80  			Config: config,
    81  			Layers: layers,
    82  		}
    83  		manifestJSON, err := json.Marshal(manifest)
    84  		if err != nil {
    85  			t.Fatal(err)
    86  		}
    87  		appendBlob(mediaType, manifestJSON)
    88  	}
    89  
    90  	appendBlob(ocispec.MediaTypeImageConfig, []byte("config"))           // Blob 0
    91  	appendBlob(ocispec.MediaTypeImageLayer, []byte("foo"))               // Blob 1
    92  	generateManifest(ocispec.MediaTypeImageManifest, descs[0], descs[1]) // Blob 2
    93  	generateManifest(docker.MediaTypeManifest, descs[0], descs[1])       // Blob 3
    94  	generateManifest("whatever", descs[0], descs[1])                     // Blob 4
    95  
    96  	ctx := context.Background()
    97  	for i := range blobs {
    98  		err := storage.Push(ctx, descs[i], bytes.NewReader(blobs[i]))
    99  		if err != nil {
   100  			t.Fatalf("failed to push test content to src: %d: %v", i, err)
   101  		}
   102  	}
   103  
   104  	tests := []struct {
   105  		name    string
   106  		desc    ocispec.Descriptor
   107  		want    *ocispec.Descriptor
   108  		wantErr bool
   109  	}{
   110  		{
   111  			name: "OCI Image Manifest",
   112  			desc: descs[2],
   113  			want: &descs[0],
   114  		},
   115  		{
   116  			name:    "Docker Manifest",
   117  			desc:    descs[3],
   118  			want:    &descs[0],
   119  			wantErr: false,
   120  		},
   121  		{
   122  			name: "Other media type",
   123  			desc: descs[4],
   124  			want: nil,
   125  		},
   126  	}
   127  	for _, tt := range tests {
   128  		t.Run(tt.name, func(t *testing.T) {
   129  			got, err := Config(ctx, storage, tt.desc)
   130  			if (err != nil) != tt.wantErr {
   131  				t.Errorf("Config() error = %v, wantErr %v", err, tt.wantErr)
   132  				return
   133  			}
   134  			if !reflect.DeepEqual(got, tt.want) {
   135  				t.Errorf("Config() = %v, want %v", got, tt.want)
   136  			}
   137  		})
   138  	}
   139  }
   140  
   141  func TestManifests(t *testing.T) {
   142  	storage := cas.NewMemory()
   143  
   144  	// generate test content
   145  	var blobs [][]byte
   146  	var descs []ocispec.Descriptor
   147  	appendBlob := func(mediaType string, blob []byte) {
   148  		blobs = append(blobs, blob)
   149  		descs = append(descs, ocispec.Descriptor{
   150  			MediaType: mediaType,
   151  			Digest:    digest.FromBytes(blob),
   152  			Size:      int64(len(blob)),
   153  		})
   154  	}
   155  	generateManifest := func(subject *ocispec.Descriptor, config ocispec.Descriptor, layers ...ocispec.Descriptor) {
   156  		manifest := ocispec.Manifest{
   157  			Subject: subject,
   158  			Config:  config,
   159  			Layers:  layers,
   160  		}
   161  		manifestJSON, err := json.Marshal(manifest)
   162  		if err != nil {
   163  			t.Fatal(err)
   164  		}
   165  		appendBlob(ocispec.MediaTypeImageManifest, manifestJSON)
   166  	}
   167  	generateIndex := func(mediaType string, subject *ocispec.Descriptor, manifests ...ocispec.Descriptor) {
   168  		index := ocispec.Index{
   169  			Subject:   subject,
   170  			Manifests: manifests,
   171  		}
   172  		indexJSON, err := json.Marshal(index)
   173  		if err != nil {
   174  			t.Fatal(err)
   175  		}
   176  		appendBlob(mediaType, indexJSON)
   177  	}
   178  
   179  	appendBlob(ocispec.MediaTypeImageConfig, []byte("config"))           // Blob 0
   180  	appendBlob(ocispec.MediaTypeImageLayer, []byte("foo"))               // Blob 1
   181  	appendBlob(ocispec.MediaTypeImageLayer, []byte("bar"))               // Blob 2
   182  	appendBlob(ocispec.MediaTypeImageLayer, []byte("hello"))             // Blob 3
   183  	generateManifest(nil, descs[0], descs[1:3]...)                       // Blob 4
   184  	generateManifest(nil, descs[0], descs[3])                            // Blob 5
   185  	appendBlob(ocispec.MediaTypeImageConfig, []byte("{}"))               // Blob 6
   186  	appendBlob("test/sig", []byte("sig"))                                // Blob 7
   187  	generateManifest(&descs[4], descs[5], descs[6])                      // Blob 8
   188  	generateIndex(ocispec.MediaTypeImageIndex, &descs[8], descs[4:6]...) // Blob 9
   189  	generateIndex(docker.MediaTypeManifestList, nil, descs[4:6]...)      // Blob 10
   190  	generateIndex("whatever", &descs[8], descs[4:6]...)                  // Blob 11
   191  
   192  	ctx := context.Background()
   193  	for i := range blobs {
   194  		err := storage.Push(ctx, descs[i], bytes.NewReader(blobs[i]))
   195  		if err != nil {
   196  			t.Fatalf("failed to push test content to src: %d: %v", i, err)
   197  		}
   198  	}
   199  
   200  	tests := []struct {
   201  		name    string
   202  		desc    ocispec.Descriptor
   203  		want    []ocispec.Descriptor
   204  		wantErr bool
   205  	}{
   206  		{
   207  			name: "OCI Image Index",
   208  			desc: descs[9],
   209  			want: descs[4:6],
   210  		},
   211  		{
   212  			name:    "Docker Manifest List",
   213  			desc:    descs[10],
   214  			want:    descs[4:6],
   215  			wantErr: false,
   216  		},
   217  		{
   218  			name: "Other media type",
   219  			desc: descs[11],
   220  			want: nil,
   221  		},
   222  	}
   223  	for _, tt := range tests {
   224  		t.Run(tt.name, func(t *testing.T) {
   225  			got, err := Manifests(ctx, storage, tt.desc)
   226  			if (err != nil) != tt.wantErr {
   227  				t.Errorf("Manifests() error = %v, wantErr %v", err, tt.wantErr)
   228  				return
   229  			}
   230  			if !reflect.DeepEqual(got, tt.want) {
   231  				t.Errorf("Manifests() = %v, want %v", got, tt.want)
   232  			}
   233  		})
   234  	}
   235  }
   236  
   237  func TestSubject(t *testing.T) {
   238  	storage := cas.NewMemory()
   239  
   240  	// generate test content
   241  	var blobs [][]byte
   242  	var descs []ocispec.Descriptor
   243  	appendBlob := func(mediaType string, blob []byte) {
   244  		blobs = append(blobs, blob)
   245  		descs = append(descs, ocispec.Descriptor{
   246  			MediaType: mediaType,
   247  			Digest:    digest.FromBytes(blob),
   248  			Size:      int64(len(blob)),
   249  		})
   250  	}
   251  	generateManifest := func(config ocispec.Descriptor, subject *ocispec.Descriptor, layers ...ocispec.Descriptor) {
   252  		manifest := ocispec.Manifest{
   253  			Config:  config,
   254  			Subject: subject,
   255  			Layers:  layers,
   256  		}
   257  		manifestJSON, err := json.Marshal(manifest)
   258  		if err != nil {
   259  			t.Fatal(err)
   260  		}
   261  		appendBlob(ocispec.MediaTypeImageManifest, manifestJSON)
   262  	}
   263  	appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0
   264  	appendBlob(ocispec.MediaTypeImageLayer, []byte("blob"))    // Blob 1
   265  	generateManifest(descs[0], nil, descs[1])                  // Blob 2, manifest
   266  	generateManifest(descs[0], &descs[2], descs[1])            // Blob 3, referrer of blob 2
   267  
   268  	ctx := context.Background()
   269  	for i := range blobs {
   270  		err := storage.Push(ctx, descs[i], bytes.NewReader(blobs[i]))
   271  		if err != nil {
   272  			t.Fatalf("failed to push test content to src: %d: %v", i, err)
   273  		}
   274  	}
   275  	got, err := Subject(ctx, storage, descs[3])
   276  	if err != nil {
   277  		t.Fatalf("error when getting subject: %v", err)
   278  	}
   279  	if !reflect.DeepEqual(*got, descs[2]) {
   280  		t.Errorf("Subject() = %v, want %v", got, descs[2])
   281  	}
   282  	got, err = Subject(ctx, storage, descs[0])
   283  	if err != nil {
   284  		t.Fatalf("error when getting subject: %v", err)
   285  	}
   286  	if got != nil {
   287  		t.Errorf("Subject() = %v, want %v", got, nil)
   288  	}
   289  }
   290  
   291  func TestSubject_ErrorPath(t *testing.T) {
   292  	s := testStorage{
   293  		store:    memory.New(),
   294  		badFetch: set.New[digest.Digest](),
   295  	}
   296  	ctx := context.Background()
   297  
   298  	// generate test content
   299  	var blobs [][]byte
   300  	var descs []ocispec.Descriptor
   301  	appendBlob := func(mediaType string, artifactType string, blob []byte) {
   302  		blobs = append(blobs, blob)
   303  		descs = append(descs, ocispec.Descriptor{
   304  			MediaType:    mediaType,
   305  			ArtifactType: artifactType,
   306  			Annotations:  map[string]string{"test": "content"},
   307  			Digest:       digest.FromBytes(blob),
   308  			Size:         int64(len(blob)),
   309  		})
   310  	}
   311  	generateImageManifest := func(config ocispec.Descriptor, subject *ocispec.Descriptor, layers ...ocispec.Descriptor) {
   312  		manifest := ocispec.Manifest{
   313  			MediaType:   ocispec.MediaTypeImageManifest,
   314  			Config:      config,
   315  			Subject:     subject,
   316  			Layers:      layers,
   317  			Annotations: map[string]string{"test": "content"},
   318  		}
   319  		manifestJSON, err := json.Marshal(manifest)
   320  		if err != nil {
   321  			t.Fatal(err)
   322  		}
   323  		appendBlob(ocispec.MediaTypeImageManifest, manifest.Config.MediaType, manifestJSON)
   324  	}
   325  	appendBlob("image manifest", "image config", []byte("config"))    // Blob 0
   326  	appendBlob(ocispec.MediaTypeImageLayer, "layer", []byte("foo"))   // Blob 1
   327  	appendBlob(ocispec.MediaTypeImageLayer, "layer", []byte("bar"))   // Blob 2
   328  	appendBlob(ocispec.MediaTypeImageLayer, "layer", []byte("hello")) // Blob 3
   329  	generateImageManifest(descs[0], nil, descs[1])                    // Blob 4
   330  	generateImageManifest(descs[0], &descs[4], descs[2])              // Blob 5
   331  	s.badFetch.Add(descs[5].Digest)
   332  
   333  	eg, egCtx := errgroup.WithContext(ctx)
   334  	for i := range blobs {
   335  		eg.Go(func(i int) func() error {
   336  			return func() error {
   337  				err := s.Push(egCtx, descs[i], bytes.NewReader(blobs[i]))
   338  				if err != nil {
   339  					return fmt.Errorf("failed to push test content to src: %d: %v", i, err)
   340  				}
   341  				return nil
   342  			}
   343  		}(i))
   344  	}
   345  	if err := eg.Wait(); err != nil {
   346  		t.Fatal(err)
   347  	}
   348  
   349  	_, err := Subject(ctx, &s, descs[5])
   350  	if !errors.Is(err, ErrBadFetch) {
   351  		t.Errorf("Store.Referrers() error = %v, want %v", err, ErrBadFetch)
   352  	}
   353  }