zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/storage/common/common_test.go (about)

     1  package storage_test
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"os"
     8  	"testing"
     9  
    10  	godigest "github.com/opencontainers/go-digest"
    11  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    12  	"github.com/rs/zerolog"
    13  	. "github.com/smartystreets/goconvey/convey"
    14  
    15  	zerr "zotregistry.dev/zot/errors"
    16  	"zotregistry.dev/zot/pkg/extensions/monitoring"
    17  	"zotregistry.dev/zot/pkg/log"
    18  	"zotregistry.dev/zot/pkg/storage"
    19  	"zotregistry.dev/zot/pkg/storage/cache"
    20  	common "zotregistry.dev/zot/pkg/storage/common"
    21  	"zotregistry.dev/zot/pkg/storage/imagestore"
    22  	"zotregistry.dev/zot/pkg/storage/local"
    23  	. "zotregistry.dev/zot/pkg/test/image-utils"
    24  	"zotregistry.dev/zot/pkg/test/mocks"
    25  )
    26  
    27  var ErrTestError = errors.New("TestError")
    28  
    29  func TestValidateManifest(t *testing.T) {
    30  	Convey("Make manifest", t, func(c C) {
    31  		dir := t.TempDir()
    32  
    33  		log := log.Logger{Logger: zerolog.New(os.Stdout)}
    34  		metrics := monitoring.NewMetricsServer(false, log)
    35  		cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{
    36  			RootDir:     dir,
    37  			Name:        "cache",
    38  			UseRelPaths: true,
    39  		}, log)
    40  		imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver)
    41  
    42  		content := []byte("this is a blob")
    43  		digest := godigest.FromBytes(content)
    44  		So(digest, ShouldNotBeNil)
    45  
    46  		_, blen, err := imgStore.FullBlobUpload("test", bytes.NewReader(content), digest)
    47  		So(err, ShouldBeNil)
    48  		So(blen, ShouldEqual, len(content))
    49  
    50  		cblob, cdigest := GetRandomImageConfig()
    51  		_, clen, err := imgStore.FullBlobUpload("test", bytes.NewReader(cblob), cdigest)
    52  		So(err, ShouldBeNil)
    53  		So(clen, ShouldEqual, len(cblob))
    54  
    55  		Convey("bad manifest schema version", func() {
    56  			manifest := ispec.Manifest{
    57  				Config: ispec.Descriptor{
    58  					MediaType: ispec.MediaTypeImageConfig,
    59  					Digest:    cdigest,
    60  					Size:      int64(len(cblob)),
    61  				},
    62  				Layers: []ispec.Descriptor{
    63  					{
    64  						MediaType: ispec.MediaTypeImageLayer,
    65  						Digest:    digest,
    66  						Size:      int64(len(content)),
    67  					},
    68  				},
    69  			}
    70  
    71  			manifest.SchemaVersion = 999
    72  
    73  			body, err := json.Marshal(manifest)
    74  			So(err, ShouldBeNil)
    75  
    76  			_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, body)
    77  			So(err, ShouldNotBeNil)
    78  			var internalErr *zerr.Error
    79  			So(errors.As(err, &internalErr), ShouldBeTrue)
    80  			So(internalErr.GetDetails(), ShouldContainKey, "jsonSchemaValidation")
    81  			So(internalErr.GetDetails()["jsonSchemaValidation"], ShouldEqual, "[schemaVersion: Must be less than or equal to 2]")
    82  		})
    83  
    84  		Convey("bad config blob", func() {
    85  			manifest := ispec.Manifest{
    86  				Config: ispec.Descriptor{
    87  					MediaType: ispec.MediaTypeImageConfig,
    88  					Digest:    cdigest,
    89  					Size:      int64(len(cblob)),
    90  				},
    91  				Layers: []ispec.Descriptor{
    92  					{
    93  						MediaType: ispec.MediaTypeImageLayer,
    94  						Digest:    digest,
    95  						Size:      int64(len(content)),
    96  					},
    97  				},
    98  			}
    99  
   100  			manifest.SchemaVersion = 2
   101  
   102  			configBlobPath := imgStore.BlobPath("test", cdigest)
   103  
   104  			err := os.WriteFile(configBlobPath, []byte("bad config blob"), 0o000)
   105  			So(err, ShouldBeNil)
   106  
   107  			body, err := json.Marshal(manifest)
   108  			So(err, ShouldBeNil)
   109  
   110  			// this was actually an umoci error on config blob
   111  			_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, body)
   112  			So(err, ShouldBeNil)
   113  		})
   114  
   115  		Convey("manifest with non-distributable layers", func() {
   116  			content := []byte("this blob doesn't exist")
   117  			digest := godigest.FromBytes(content)
   118  			So(digest, ShouldNotBeNil)
   119  
   120  			manifest := ispec.Manifest{
   121  				Config: ispec.Descriptor{
   122  					MediaType: ispec.MediaTypeImageConfig,
   123  					Digest:    cdigest,
   124  					Size:      int64(len(cblob)),
   125  				},
   126  				Layers: []ispec.Descriptor{
   127  					{
   128  						MediaType: ispec.MediaTypeImageLayerNonDistributable, //nolint:staticcheck
   129  						Digest:    digest,
   130  						Size:      int64(len(content)),
   131  					},
   132  				},
   133  			}
   134  
   135  			manifest.SchemaVersion = 2
   136  
   137  			body, err := json.Marshal(manifest)
   138  			So(err, ShouldBeNil)
   139  
   140  			_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, body)
   141  			So(err, ShouldBeNil)
   142  		})
   143  	})
   144  }
   145  
   146  func TestGetReferrersErrors(t *testing.T) {
   147  	Convey("make storage", t, func(c C) {
   148  		dir := t.TempDir()
   149  
   150  		log := log.Logger{Logger: zerolog.New(os.Stdout)}
   151  		metrics := monitoring.NewMetricsServer(false, log)
   152  		cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{
   153  			RootDir:     dir,
   154  			Name:        "cache",
   155  			UseRelPaths: true,
   156  		}, log)
   157  
   158  		imgStore := local.NewImageStore(dir, false, true, log, metrics, nil, cacheDriver)
   159  
   160  		artifactType := "application/vnd.example.icecream.v1"
   161  		validDigest := godigest.FromBytes([]byte("blob"))
   162  
   163  		Convey("Trigger invalid digest error", func(c C) {
   164  			_, err := common.GetReferrers(imgStore, "zot-test", "invalidDigest",
   165  				[]string{artifactType}, log)
   166  			So(err, ShouldNotBeNil)
   167  		})
   168  
   169  		Convey("Trigger repo not found error", func(c C) {
   170  			_, err := common.GetReferrers(imgStore, "zot-test", validDigest,
   171  				[]string{artifactType}, log)
   172  			So(err, ShouldNotBeNil)
   173  		})
   174  
   175  		storageCtlr := storage.StoreController{DefaultStore: imgStore}
   176  		err := WriteImageToFileSystem(CreateDefaultImage(), "zot-test", "0.0.1", storageCtlr)
   177  		So(err, ShouldBeNil)
   178  
   179  		digest := godigest.FromBytes([]byte("{}"))
   180  
   181  		index := ispec.Index{
   182  			Manifests: []ispec.Descriptor{
   183  				{
   184  					MediaType: "application/vnd.example.invalid.v1",
   185  					Digest:    digest,
   186  				},
   187  			},
   188  		}
   189  
   190  		indexBuf, err := json.Marshal(index)
   191  		So(err, ShouldBeNil)
   192  
   193  		Convey("Trigger GetBlobContent() not found", func(c C) {
   194  			imgStore = &mocks.MockedImageStore{
   195  				GetIndexContentFn: func(repo string) ([]byte, error) {
   196  					return indexBuf, nil
   197  				},
   198  				GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
   199  					return []byte{}, zerr.ErrBlobNotFound
   200  				},
   201  			}
   202  
   203  			_, err = common.GetReferrers(imgStore, "zot-test", validDigest,
   204  				[]string{artifactType}, log)
   205  			So(err, ShouldNotBeNil)
   206  		})
   207  
   208  		Convey("Trigger GetBlobContent() generic error", func(c C) {
   209  			imgStore = &mocks.MockedImageStore{
   210  				GetIndexContentFn: func(repo string) ([]byte, error) {
   211  					return indexBuf, nil
   212  				},
   213  				GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
   214  					return []byte{}, zerr.ErrBadBlob
   215  				},
   216  			}
   217  
   218  			_, err = common.GetReferrers(imgStore, "zot-test", validDigest,
   219  				[]string{artifactType}, log)
   220  			So(err, ShouldNotBeNil)
   221  		})
   222  
   223  		Convey("Trigger unmarshal error on manifest image mediaType", func(c C) {
   224  			index = ispec.Index{
   225  				Manifests: []ispec.Descriptor{
   226  					{
   227  						MediaType: ispec.MediaTypeImageManifest,
   228  						Digest:    digest,
   229  					},
   230  				},
   231  			}
   232  
   233  			indexBuf, err = json.Marshal(index)
   234  			So(err, ShouldBeNil)
   235  
   236  			imgStore = &mocks.MockedImageStore{
   237  				GetIndexContentFn: func(repo string) ([]byte, error) {
   238  					return indexBuf, nil
   239  				},
   240  				GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
   241  					return []byte{}, nil
   242  				},
   243  			}
   244  
   245  			_, err = common.GetReferrers(imgStore, "zot-test", validDigest,
   246  				[]string{artifactType}, log)
   247  			So(err, ShouldNotBeNil)
   248  		})
   249  
   250  		Convey("Trigger nil subject", func(c C) {
   251  			index = ispec.Index{
   252  				Manifests: []ispec.Descriptor{
   253  					{
   254  						MediaType: ispec.MediaTypeImageManifest,
   255  						Digest:    digest,
   256  					},
   257  				},
   258  			}
   259  
   260  			indexBuf, err = json.Marshal(index)
   261  			So(err, ShouldBeNil)
   262  
   263  			ociManifest := ispec.Manifest{
   264  				Subject: nil,
   265  			}
   266  
   267  			ociManifestBuf, err := json.Marshal(ociManifest)
   268  			So(err, ShouldBeNil)
   269  
   270  			imgStore = &mocks.MockedImageStore{
   271  				GetIndexContentFn: func(repo string) ([]byte, error) {
   272  					return indexBuf, nil
   273  				},
   274  				GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
   275  					return ociManifestBuf, nil
   276  				},
   277  			}
   278  
   279  			_, err = common.GetReferrers(imgStore, "zot-test", validDigest,
   280  				[]string{artifactType}, log)
   281  			So(err, ShouldBeNil)
   282  		})
   283  
   284  		Convey("Index bad blob", func() {
   285  			imgStore = &mocks.MockedImageStore{
   286  				GetIndexContentFn: func(repo string) ([]byte, error) {
   287  					return []byte(`{
   288  						"manifests": [{
   289  							"digest": "digest",
   290  							"mediaType": "application/vnd.oci.image.index.v1+json"
   291  						}]
   292  					}`), nil
   293  				},
   294  				GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
   295  					return []byte("bad blob"), nil
   296  				},
   297  			}
   298  
   299  			_, err = common.GetReferrers(imgStore, "zot-test", validDigest,
   300  				[]string{}, log)
   301  			So(err, ShouldNotBeNil)
   302  		})
   303  
   304  		Convey("Index bad artifac type", func() {
   305  			imgStore = &mocks.MockedImageStore{
   306  				GetIndexContentFn: func(repo string) ([]byte, error) {
   307  					return []byte(`{
   308  						"manifests": [{
   309  							"digest": "digest",
   310  							"mediaType": "application/vnd.oci.image.index.v1+json"
   311  						}]
   312  					}`), nil
   313  				},
   314  				GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
   315  					return []byte(`{ 
   316  						"subject": {"digest": "` + validDigest.String() + `"}
   317  						}`), nil
   318  				},
   319  			}
   320  
   321  			ref, err := common.GetReferrers(imgStore, "zot-test", validDigest,
   322  				[]string{"art.type"}, log)
   323  			So(err, ShouldBeNil)
   324  			So(len(ref.Manifests), ShouldEqual, 0)
   325  		})
   326  	})
   327  }
   328  
   329  func TestGetImageIndexErrors(t *testing.T) {
   330  	log := log.Logger{Logger: zerolog.New(os.Stdout)}
   331  
   332  	Convey("Trigger invalid digest error", t, func(c C) {
   333  		imgStore := &mocks.MockedImageStore{}
   334  
   335  		_, err := common.GetImageIndex(imgStore, "zot-test", "invalidDigest", log)
   336  		So(err, ShouldNotBeNil)
   337  	})
   338  
   339  	Convey("Trigger GetBlobContent error", t, func(c C) {
   340  		imgStore := &mocks.MockedImageStore{
   341  			GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
   342  				return []byte{}, zerr.ErrBlobNotFound
   343  			},
   344  		}
   345  
   346  		validDigest := godigest.FromBytes([]byte("blob"))
   347  
   348  		_, err := common.GetImageIndex(imgStore, "zot-test", validDigest, log)
   349  		So(err, ShouldNotBeNil)
   350  	})
   351  
   352  	Convey("Trigger unmarshal error", t, func(c C) {
   353  		imgStore := &mocks.MockedImageStore{
   354  			GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
   355  				return []byte{}, nil
   356  			},
   357  		}
   358  
   359  		validDigest := godigest.FromBytes([]byte("blob"))
   360  
   361  		_, err := common.GetImageIndex(imgStore, "zot-test", validDigest, log)
   362  		So(err, ShouldNotBeNil)
   363  	})
   364  }
   365  
   366  func TestGetBlobDescriptorFromRepo(t *testing.T) {
   367  	log := log.Logger{Logger: zerolog.New(os.Stdout)}
   368  	metrics := monitoring.NewMetricsServer(false, log)
   369  
   370  	tdir := t.TempDir()
   371  	cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{
   372  		RootDir:     tdir,
   373  		Name:        "cache",
   374  		UseRelPaths: true,
   375  	}, log)
   376  
   377  	driver := local.New(true)
   378  	imgStore := imagestore.NewImageStore(tdir, tdir, true,
   379  		true, log, metrics, nil, driver, cacheDriver)
   380  
   381  	repoName := "zot-test"
   382  
   383  	Convey("Test error paths", t, func() {
   384  		storeController := storage.StoreController{DefaultStore: imgStore}
   385  
   386  		image := CreateRandomMultiarch()
   387  
   388  		tag := "index"
   389  
   390  		err := WriteMultiArchImageToFileSystem(image, repoName, tag, storeController)
   391  		So(err, ShouldBeNil)
   392  
   393  		blob := image.Images[0].Layers[0]
   394  		blobDigest := godigest.FromBytes(blob)
   395  		blobSize := len(blob)
   396  
   397  		desc, err := common.GetBlobDescriptorFromIndex(imgStore, ispec.Index{Manifests: []ispec.Descriptor{
   398  			{
   399  				Digest:    image.Digest(),
   400  				MediaType: ispec.MediaTypeImageIndex,
   401  			},
   402  		}}, repoName, blobDigest, log)
   403  		So(err, ShouldBeNil)
   404  		So(desc.Digest, ShouldEqual, blobDigest)
   405  		So(desc.Size, ShouldEqual, blobSize)
   406  
   407  		desc, err = common.GetBlobDescriptorFromRepo(imgStore, repoName, blobDigest, log)
   408  		So(err, ShouldBeNil)
   409  		So(desc.Digest, ShouldEqual, blobDigest)
   410  		So(desc.Size, ShouldEqual, blobSize)
   411  
   412  		indexBlobPath := imgStore.BlobPath(repoName, image.Digest())
   413  		err = os.Chmod(indexBlobPath, 0o000)
   414  		So(err, ShouldBeNil)
   415  
   416  		defer func() {
   417  			err = os.Chmod(indexBlobPath, 0o644)
   418  			So(err, ShouldBeNil)
   419  		}()
   420  
   421  		_, err = common.GetBlobDescriptorFromIndex(imgStore, ispec.Index{Manifests: []ispec.Descriptor{
   422  			{
   423  				Digest:    image.Digest(),
   424  				MediaType: ispec.MediaTypeImageIndex,
   425  			},
   426  		}}, repoName, blobDigest, log)
   427  		So(err, ShouldNotBeNil)
   428  
   429  		manifestDigest := image.Images[0].Digest()
   430  		manifestBlobPath := imgStore.BlobPath(repoName, manifestDigest)
   431  		err = os.Chmod(manifestBlobPath, 0o000)
   432  		So(err, ShouldBeNil)
   433  
   434  		defer func() {
   435  			err = os.Chmod(manifestBlobPath, 0o644)
   436  			So(err, ShouldBeNil)
   437  		}()
   438  
   439  		_, err = common.GetBlobDescriptorFromRepo(imgStore, repoName, blobDigest, log)
   440  		So(err, ShouldNotBeNil)
   441  
   442  		_, err = common.GetBlobDescriptorFromRepo(imgStore, repoName, manifestDigest, log)
   443  		So(err, ShouldBeNil)
   444  	})
   445  }
   446  
   447  func TestIsSignature(t *testing.T) {
   448  	Convey("Unknown media type", t, func(c C) {
   449  		isSingature := common.IsSignature(ispec.Descriptor{
   450  			MediaType: "unknown media type",
   451  		})
   452  		So(isSingature, ShouldBeFalse)
   453  	})
   454  }
   455  
   456  func TestDedupeGeneratorErrors(t *testing.T) {
   457  	log := log.Logger{Logger: zerolog.New(os.Stdout)}
   458  
   459  	// Ideally this would be covered by the end-to-end test,
   460  	// but the coverage for the error is unpredictable, prone to race conditions
   461  	Convey("GetNextDigestWithBlobPaths errors", t, func(c C) {
   462  		imgStore := &mocks.MockedImageStore{
   463  			GetRepositoriesFn: func() ([]string, error) {
   464  				return []string{"repo1", "repo2"}, nil
   465  			},
   466  			GetNextDigestWithBlobPathsFn: func(repos []string, lastDigests []godigest.Digest) (
   467  				godigest.Digest, []string, error,
   468  			) {
   469  				return "sha256:123", []string{}, ErrTestError
   470  			},
   471  		}
   472  
   473  		generator := &common.DedupeTaskGenerator{
   474  			ImgStore: imgStore,
   475  			Dedupe:   true,
   476  			Log:      log,
   477  		}
   478  
   479  		task, err := generator.Next()
   480  		So(err, ShouldNotBeNil)
   481  		So(task, ShouldBeNil)
   482  	})
   483  }