...

Source file src/cuelabs.dev/go/oci/ociregistry/ocimem/check_test.go

Documentation: cuelabs.dev/go/oci/ociregistry/ocimem

     1  package ocimem
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"testing"
     8  
     9  	"cuelabs.dev/go/oci/ociregistry"
    10  	"cuelabs.dev/go/oci/ociregistry/ocitest"
    11  	"github.com/go-quicktest/qt"
    12  	"github.com/opencontainers/go-digest"
    13  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    14  )
    15  
    16  var pushManifestTests = []struct {
    17  	testName     string
    18  	preload      ocitest.RepoContent
    19  	config       Config
    20  	tag          string
    21  	mediaType    string
    22  	manifestData func(content ocitest.PushedRepoContent) []byte
    23  	wantError    string
    24  }{{
    25  	testName:  "NonExistentConfigReference",
    26  	mediaType: ocispec.MediaTypeImageManifest,
    27  	manifestData: func(ocitest.PushedRepoContent) []byte {
    28  		return mustJSONMarshal(ociregistry.Manifest{
    29  			MediaType: ocispec.MediaTypeImageManifest,
    30  			Config: ociregistry.Descriptor{
    31  				MediaType: "application/something",
    32  				Size:      1,
    33  				Digest:    digest.FromString("a"),
    34  			},
    35  		})
    36  	},
    37  	wantError: `invalid manifest: blob for config not found`,
    38  }, {
    39  	testName: "NonExistentLayerReference",
    40  	preload: ocitest.RepoContent{
    41  		Blobs: map[string]string{
    42  			"a": "{}",
    43  		},
    44  	},
    45  	mediaType: ocispec.MediaTypeImageManifest,
    46  	manifestData: func(content ocitest.PushedRepoContent) []byte {
    47  		return mustJSONMarshal(ociregistry.Manifest{
    48  			MediaType: ocispec.MediaTypeImageManifest,
    49  			Config:    content.Blobs["a"],
    50  			Layers: []ociregistry.Descriptor{{
    51  				MediaType: "application/something",
    52  				Size:      1,
    53  				Digest:    digest.FromString("b"),
    54  			}},
    55  		})
    56  	},
    57  	wantError: `invalid manifest: blob for layers\[0\] not found`,
    58  }, {
    59  	testName: "NonExistentSubjectReference",
    60  	preload: ocitest.RepoContent{
    61  		Blobs: map[string]string{
    62  			"a": "{}",
    63  		},
    64  	},
    65  	mediaType: ocispec.MediaTypeImageManifest,
    66  	manifestData: func(content ocitest.PushedRepoContent) []byte {
    67  		return mustJSONMarshal(ociregistry.Manifest{
    68  			MediaType: ocispec.MediaTypeImageManifest,
    69  			Config:    content.Blobs["a"],
    70  			Subject: &ociregistry.Descriptor{
    71  				MediaType: "application/something",
    72  				Size:      1,
    73  				Digest:    digest.FromString("b"),
    74  			},
    75  		})
    76  	},
    77  	// Non-existent subject references are explicitly allowed.
    78  }, {
    79  	testName:  "NonExistentImageIndexManifestReference",
    80  	mediaType: ocispec.MediaTypeImageIndex,
    81  	manifestData: func(content ocitest.PushedRepoContent) []byte {
    82  		return mustJSONMarshal(ocispec.Index{
    83  			MediaType: ocispec.MediaTypeImageIndex,
    84  			Manifests: []ociregistry.Descriptor{{
    85  				MediaType: ocispec.MediaTypeImageManifest,
    86  				Size:      1,
    87  				Digest:    digest.FromString("a"),
    88  			}},
    89  		})
    90  	},
    91  	wantError: `invalid manifest: manifest for manifests\[0\] not found`,
    92  }, {
    93  	testName:  "NonExistentImageIndexSubjectReference",
    94  	mediaType: ocispec.MediaTypeImageIndex,
    95  	manifestData: func(content ocitest.PushedRepoContent) []byte {
    96  		return mustJSONMarshal(ocispec.Index{
    97  			MediaType: ocispec.MediaTypeImageIndex,
    98  			Subject: &ociregistry.Descriptor{
    99  				MediaType: "application/something",
   100  				Size:      1,
   101  				Digest:    digest.FromString("b"),
   102  			},
   103  		})
   104  	},
   105  	// Non-existent subject references are explicitly allowed.
   106  }, {
   107  	testName: "CannotOverwriteTagWhenImmutabilityEnabled",
   108  	preload: ocitest.RepoContent{
   109  		Blobs: map[string]string{
   110  			"a": "{}",
   111  			"b": "other",
   112  		},
   113  		Manifests: map[string]ociregistry.Manifest{
   114  			"m": {
   115  				MediaType: ocispec.MediaTypeImageManifest,
   116  				Config: ociregistry.Descriptor{
   117  					Digest: "a",
   118  				},
   119  				Layers: []ociregistry.Descriptor{{
   120  					Digest: "a",
   121  				}},
   122  			},
   123  		},
   124  		Tags: map[string]string{
   125  			"sometag": "m",
   126  		},
   127  	},
   128  	config: Config{
   129  		ImmutableTags: true,
   130  	},
   131  	mediaType: ocispec.MediaTypeImageManifest,
   132  	tag:       "sometag",
   133  	manifestData: func(content ocitest.PushedRepoContent) []byte {
   134  		return mustJSONMarshal(ociregistry.Manifest{
   135  			MediaType: ocispec.MediaTypeImageManifest,
   136  			Config:    content.Blobs["a"],
   137  			Layers:    []ociregistry.Descriptor{content.Blobs["a"]},
   138  			Annotations: map[string]string{
   139  				"different": "thing",
   140  			},
   141  		})
   142  	},
   143  	wantError: `requested access to the resource is denied: cannot overwrite tag`,
   144  }, {
   145  	testName: "CanRewriteTagWithIdenticalContentsWhenImmutabilityEnabled",
   146  	preload: ocitest.RepoContent{
   147  		Blobs: map[string]string{
   148  			"a": "{}",
   149  			"b": "other",
   150  		},
   151  		Manifests: map[string]ociregistry.Manifest{
   152  			"m": {
   153  				MediaType: ocispec.MediaTypeImageManifest,
   154  				Config: ociregistry.Descriptor{
   155  					Digest: "a",
   156  				},
   157  				Layers: []ociregistry.Descriptor{{
   158  					Digest: "a",
   159  				}},
   160  			},
   161  		},
   162  		Tags: map[string]string{
   163  			"sometag": "m",
   164  		},
   165  	},
   166  	config: Config{
   167  		ImmutableTags: true,
   168  	},
   169  	mediaType: ocispec.MediaTypeImageManifest,
   170  	tag:       "sometag",
   171  	manifestData: func(content ocitest.PushedRepoContent) []byte {
   172  		return content.ManifestData["m"]
   173  	},
   174  }, {
   175  	testName: "CannotRewriteTagWithIdenticalContentsButDifferentMediaTypeWhenImmutabilityEnabled",
   176  	preload: ocitest.RepoContent{
   177  		Blobs: map[string]string{
   178  			"a": "{}",
   179  			"b": "other",
   180  		},
   181  		Manifests: map[string]ociregistry.Manifest{
   182  			"m": {
   183  				MediaType: ocispec.MediaTypeImageManifest,
   184  				Config: ociregistry.Descriptor{
   185  					Digest: "a",
   186  				},
   187  				Layers: []ociregistry.Descriptor{{
   188  					Digest: "a",
   189  				}},
   190  			},
   191  		},
   192  		Tags: map[string]string{
   193  			"sometag": "m",
   194  		},
   195  	},
   196  	config: Config{
   197  		ImmutableTags: true,
   198  	},
   199  	mediaType: "application/vnd.docker.container.image.v1+json",
   200  	tag:       "sometag",
   201  	manifestData: func(content ocitest.PushedRepoContent) []byte {
   202  		return content.ManifestData["m"]
   203  	},
   204  	wantError: `requested access to the resource is denied: mismatched media type`,
   205  }, {
   206  	testName: "CanOverwriteTagWhenImmutabilityNotEnabled",
   207  	preload: ocitest.RepoContent{
   208  		Blobs: map[string]string{
   209  			"a": "{}",
   210  			"b": "other",
   211  		},
   212  		Manifests: map[string]ociregistry.Manifest{
   213  			"m": {
   214  				MediaType: ocispec.MediaTypeImageManifest,
   215  				Config: ociregistry.Descriptor{
   216  					Digest: "a",
   217  				},
   218  				Layers: []ociregistry.Descriptor{{
   219  					Digest: "a",
   220  				}},
   221  			},
   222  		},
   223  		Tags: map[string]string{
   224  			"sometag": "m",
   225  		},
   226  	},
   227  	mediaType: ocispec.MediaTypeImageManifest,
   228  	tag:       "sometag",
   229  	manifestData: func(content ocitest.PushedRepoContent) []byte {
   230  		return mustJSONMarshal(ociregistry.Manifest{
   231  			MediaType: ocispec.MediaTypeImageManifest,
   232  			Config:    content.Blobs["a"],
   233  			Layers:    []ociregistry.Descriptor{content.Blobs["a"]},
   234  			Annotations: map[string]string{
   235  				"different": "thing",
   236  			},
   237  		})
   238  	},
   239  }}
   240  
   241  func TestPushManifest(t *testing.T) {
   242  	for _, test := range pushManifestTests {
   243  		t.Run(test.testName, func(t *testing.T) {
   244  			ctx := context.Background()
   245  			r := ocitest.NewRegistry(t, NewWithConfig(&test.config))
   246  			content := r.MustPushContent(ocitest.RegistryContent{
   247  				"test": test.preload,
   248  			})["test"]
   249  			data := test.manifestData(content)
   250  			_, err := r.R.PushManifest(ctx, "test", test.tag, data, test.mediaType)
   251  			if test.wantError != "" {
   252  				qt.Assert(t, qt.ErrorMatches(err, test.wantError))
   253  			} else {
   254  				qt.Assert(t, qt.IsNil(err))
   255  			}
   256  		})
   257  	}
   258  }
   259  
   260  var deleteBlobTests = []struct {
   261  	testName  string
   262  	config    Config
   263  	preload   ocitest.RepoContent
   264  	getDigest func(content ocitest.PushedRepoContent) ociregistry.Digest
   265  	wantError string
   266  }{{
   267  	testName: "NonExistentRepo",
   268  	getDigest: func(content ocitest.PushedRepoContent) ociregistry.Digest {
   269  		return digest.FromString("blshdfsvg")
   270  	},
   271  	wantError: "repository name not known to registry",
   272  }, {
   273  	testName: "NonExistentBlob",
   274  	preload: ocitest.RepoContent{
   275  		Blobs: map[string]string{
   276  			"a": "{}",
   277  		},
   278  	},
   279  	getDigest: func(content ocitest.PushedRepoContent) ociregistry.Digest {
   280  		return digest.FromString("blshdfsvg")
   281  	},
   282  	wantError: "blob unknown to registry",
   283  }, {
   284  	testName: "TaggedBlobWithImmutableTags",
   285  	config: Config{
   286  		ImmutableTags: true,
   287  	},
   288  	preload: ocitest.RepoContent{
   289  		Blobs: map[string]string{
   290  			"a": "{}",
   291  		},
   292  		Manifests: map[string]ociregistry.Manifest{
   293  			"m": {
   294  				MediaType: ocispec.MediaTypeImageManifest,
   295  				Config: ociregistry.Descriptor{
   296  					Digest: "a",
   297  				},
   298  				Layers: []ociregistry.Descriptor{{
   299  					Digest: "a",
   300  				}},
   301  			},
   302  		},
   303  		Tags: map[string]string{
   304  			"sometag": "m",
   305  		},
   306  	},
   307  	getDigest: func(content ocitest.PushedRepoContent) ociregistry.Digest {
   308  		return content.Blobs["a"].Digest
   309  	},
   310  	wantError: "requested access to the resource is denied: deletion of tagged blob not permitted",
   311  }, {
   312  	testName: "IndirectlyTaggedBlobWithImmutableTags",
   313  	config: Config{
   314  		ImmutableTags: true,
   315  	},
   316  	preload: ocitest.RepoContent{
   317  		Blobs: map[string]string{
   318  			"a": "{}",
   319  			"b": "other",
   320  		},
   321  		Manifests: map[string]ociregistry.Manifest{
   322  			"m0": {
   323  				MediaType: ocispec.MediaTypeImageManifest,
   324  				Config: ociregistry.Descriptor{
   325  					Digest: "a",
   326  				},
   327  			},
   328  			"m1": {
   329  				MediaType: ocispec.MediaTypeImageManifest,
   330  				Config: ociregistry.Descriptor{
   331  					Digest: "b",
   332  				},
   333  				Subject: &ociregistry.Descriptor{
   334  					Digest: "m0",
   335  				},
   336  			},
   337  		},
   338  		Tags: map[string]string{
   339  			"sometag": "m1",
   340  		},
   341  	},
   342  	getDigest: func(content ocitest.PushedRepoContent) ociregistry.Digest {
   343  		return content.Blobs["a"].Digest
   344  	},
   345  	wantError: "requested access to the resource is denied: deletion of tagged blob not permitted",
   346  }}
   347  
   348  func TestDeleteBlob(t *testing.T) {
   349  	for _, test := range deleteBlobTests {
   350  		t.Run(test.testName, func(t *testing.T) {
   351  			ctx := context.Background()
   352  			r := ocitest.NewRegistry(t, NewWithConfig(&test.config))
   353  			content := r.MustPushContent(ocitest.RegistryContent{
   354  				"test": test.preload,
   355  			})["test"]
   356  			digest := test.getDigest(content)
   357  			err := r.R.DeleteBlob(ctx, "test", digest)
   358  			if test.wantError != "" {
   359  				qt.Assert(t, qt.ErrorMatches(err, test.wantError))
   360  			} else {
   361  				qt.Assert(t, qt.IsNil(err))
   362  			}
   363  			// Regardless of the result, the blob shouldn't be there afterwards
   364  			// unless the operation was denied.
   365  			if !errors.Is(err, ociregistry.ErrDenied) {
   366  				_, err := r.R.ResolveBlob(ctx, "test", digest)
   367  				qt.Assert(t, qt.Not(qt.IsNil(err)))
   368  			}
   369  		})
   370  	}
   371  }
   372  
   373  var deleteManifestTests = []struct {
   374  	testName  string
   375  	config    Config
   376  	preload   ocitest.RepoContent
   377  	getDigest func(content ocitest.PushedRepoContent) ociregistry.Digest
   378  	wantError string
   379  }{{
   380  	testName: "NonExistentRepo",
   381  	getDigest: func(content ocitest.PushedRepoContent) ociregistry.Digest {
   382  		return digest.FromString("blshdfsvg")
   383  	},
   384  	wantError: "repository name not known to registry",
   385  }, {
   386  	testName: "NonExistentManifest",
   387  	preload: ocitest.RepoContent{
   388  		Blobs: map[string]string{
   389  			"a": "{}",
   390  		},
   391  	},
   392  	getDigest: func(content ocitest.PushedRepoContent) ociregistry.Digest {
   393  		return digest.FromString("blshdfsvg")
   394  	},
   395  	wantError: "manifest unknown to registry",
   396  }, {
   397  	testName: "TaggedManifestWithImmutableTags",
   398  	config: Config{
   399  		ImmutableTags: true,
   400  	},
   401  	preload: ocitest.RepoContent{
   402  		Blobs: map[string]string{
   403  			"a": "{}",
   404  		},
   405  		Manifests: map[string]ociregistry.Manifest{
   406  			"m": {
   407  				MediaType: ocispec.MediaTypeImageManifest,
   408  				Config: ociregistry.Descriptor{
   409  					Digest: "a",
   410  				},
   411  			},
   412  		},
   413  		Tags: map[string]string{
   414  			"sometag": "m",
   415  		},
   416  	},
   417  	getDigest: func(content ocitest.PushedRepoContent) ociregistry.Digest {
   418  		return content.Manifests["m"].Digest
   419  	},
   420  	wantError: "requested access to the resource is denied: deletion of tagged manifest not permitted",
   421  }, {
   422  	testName: "IndirectlyTaggedManifestWithImmutableTags",
   423  	config: Config{
   424  		ImmutableTags: true,
   425  	},
   426  	preload: ocitest.RepoContent{
   427  		Blobs: map[string]string{
   428  			"a": "{}",
   429  			"b": "other",
   430  		},
   431  		Manifests: map[string]ociregistry.Manifest{
   432  			"m0": {
   433  				MediaType: ocispec.MediaTypeImageManifest,
   434  				Config: ociregistry.Descriptor{
   435  					Digest: "a",
   436  				},
   437  			},
   438  			"m1": {
   439  				MediaType: ocispec.MediaTypeImageManifest,
   440  				Config: ociregistry.Descriptor{
   441  					Digest: "b",
   442  				},
   443  				Subject: &ociregistry.Descriptor{
   444  					Digest: "m0",
   445  				},
   446  			},
   447  		},
   448  		Tags: map[string]string{
   449  			"sometag": "m1",
   450  		},
   451  	},
   452  	getDigest: func(content ocitest.PushedRepoContent) ociregistry.Digest {
   453  		return content.Manifests["m0"].Digest
   454  	},
   455  	wantError: "requested access to the resource is denied: deletion of tagged manifest not permitted",
   456  }}
   457  
   458  func TestDeleteManifest(t *testing.T) {
   459  	for _, test := range deleteManifestTests {
   460  		t.Run(test.testName, func(t *testing.T) {
   461  			ctx := context.Background()
   462  			r := ocitest.NewRegistry(t, NewWithConfig(&test.config))
   463  			content := r.MustPushContent(ocitest.RegistryContent{
   464  				"test": test.preload,
   465  			})["test"]
   466  			digest := test.getDigest(content)
   467  			err := r.R.DeleteManifest(ctx, "test", digest)
   468  			if test.wantError != "" {
   469  				qt.Assert(t, qt.ErrorMatches(err, test.wantError))
   470  			} else {
   471  				qt.Assert(t, qt.IsNil(err))
   472  			}
   473  			// Regardless of the result, the manifest shouldn't be there afterwards
   474  			// unless the operation was denied.
   475  			if !errors.Is(err, ociregistry.ErrDenied) {
   476  				_, err := r.R.ResolveManifest(ctx, "test", digest)
   477  				qt.Assert(t, qt.Not(qt.IsNil(err)))
   478  			}
   479  		})
   480  	}
   481  }
   482  
   483  var deleteTagTests = []struct {
   484  	testName  string
   485  	config    Config
   486  	preload   ocitest.RepoContent
   487  	tag       string
   488  	wantError string
   489  }{{
   490  	testName:  "NonExistentRepo",
   491  	tag:       "foo",
   492  	wantError: "repository name not known to registry",
   493  }, {
   494  	testName: "NonExistentTag",
   495  	preload: ocitest.RepoContent{
   496  		Blobs: map[string]string{
   497  			"a": "{}",
   498  		},
   499  	},
   500  	tag:       "foo",
   501  	wantError: "manifest unknown to registry: tag does not exist",
   502  }, {
   503  	testName: "WithImmutableTags",
   504  	config: Config{
   505  		ImmutableTags: true,
   506  	},
   507  	preload: ocitest.RepoContent{
   508  		Blobs: map[string]string{
   509  			"a": "{}",
   510  		},
   511  		Manifests: map[string]ociregistry.Manifest{
   512  			"m": {
   513  				MediaType: ocispec.MediaTypeImageManifest,
   514  				Config: ociregistry.Descriptor{
   515  					Digest: "a",
   516  				},
   517  			},
   518  		},
   519  		Tags: map[string]string{
   520  			"sometag": "m",
   521  		},
   522  	},
   523  	tag:       "sometag",
   524  	wantError: "requested access to the resource is denied: tag deletion not permitted",
   525  }, {
   526  	testName: "Success",
   527  	preload: ocitest.RepoContent{
   528  		Blobs: map[string]string{
   529  			"a": "{}",
   530  			"b": "other",
   531  		},
   532  		Manifests: map[string]ociregistry.Manifest{
   533  			"m0": {
   534  				MediaType: ocispec.MediaTypeImageManifest,
   535  				Config: ociregistry.Descriptor{
   536  					Digest: "a",
   537  				},
   538  			},
   539  			"m1": {
   540  				MediaType: ocispec.MediaTypeImageManifest,
   541  				Config: ociregistry.Descriptor{
   542  					Digest: "b",
   543  				},
   544  				Subject: &ociregistry.Descriptor{
   545  					Digest: "m0",
   546  				},
   547  			},
   548  		},
   549  		Tags: map[string]string{
   550  			"sometag": "m1",
   551  		},
   552  	},
   553  	tag: "sometag",
   554  }}
   555  
   556  func TestDeleteTag(t *testing.T) {
   557  	for _, test := range deleteTagTests {
   558  		t.Run(test.testName, func(t *testing.T) {
   559  			ctx := context.Background()
   560  			r := ocitest.NewRegistry(t, NewWithConfig(&test.config))
   561  			content := r.MustPushContent(ocitest.RegistryContent{
   562  				"test": test.preload,
   563  			})["test"]
   564  			err := r.R.DeleteTag(ctx, "test", test.tag)
   565  			if test.wantError != "" {
   566  				qt.Assert(t, qt.ErrorMatches(err, test.wantError))
   567  			} else {
   568  				qt.Assert(t, qt.IsNil(err))
   569  			}
   570  			// Regardless of the result, the tag shouldn't be there afterwards
   571  			// unless the operation was denied.
   572  			if !errors.Is(err, ociregistry.ErrDenied) {
   573  				_, err := r.R.ResolveTag(ctx, "test", test.tag)
   574  				qt.Assert(t, qt.Not(qt.IsNil(err)))
   575  			}
   576  			// The manifest should remain present even though the tag
   577  			// itself has been deleted.
   578  			if tagDesc, ok := content.Manifests[test.preload.Tags[test.tag]]; ok {
   579  				_, err := r.R.ResolveManifest(ctx, "test", tagDesc.Digest)
   580  				qt.Assert(t, qt.IsNil(err))
   581  			}
   582  		})
   583  	}
   584  }
   585  
   586  func mustJSONMarshal(x any) []byte {
   587  	data, err := json.Marshal(x)
   588  	if err != nil {
   589  		panic(err)
   590  	}
   591  	return data
   592  }
   593  

View as plain text