From 3510ef0fb06e7443f37af7004f1b12c1afe3e203 Mon Sep 17 00:00:00 2001 From: Nicol Date: Fri, 7 Apr 2023 19:52:26 +0300 Subject: [PATCH] refactor: move pkg/extensions/search/common/oci_layout.go under pkg/test/ (#1325) Signed-off-by: Nicol Draghici --- pkg/extensions/search/common/common_test.go | 326 +------------ pkg/extensions/search/convert/convert_test.go | 278 ----------- pkg/extensions/search/convert/oci.go | 186 -------- pkg/extensions/search/resolver.go | 64 --- pkg/extensions/search/resolver_test.go | 143 ------ .../search/common => test}/oci_layout.go | 111 +++-- pkg/test/oci_layout_test.go | 441 ++++++++++++++++++ 7 files changed, 517 insertions(+), 1032 deletions(-) rename pkg/{extensions/search/common => test}/oci_layout.go (83%) create mode 100644 pkg/test/oci_layout_test.go diff --git a/pkg/extensions/search/common/common_test.go b/pkg/extensions/search/common/common_test.go index 5e39486d..466ec01a 100644 --- a/pkg/extensions/search/common/common_test.go +++ b/pkg/extensions/search/common/common_test.go @@ -2390,7 +2390,7 @@ func TestGetImageManifest(t *testing.T) { storeController := storage.StoreController{ DefaultStore: mockImageStore, } - olu := common.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) + olu := NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) _, _, err := olu.GetImageManifest("nonexistent-repo", "latest") So(err, ShouldNotBeNil) @@ -2406,7 +2406,7 @@ func TestGetImageManifest(t *testing.T) { storeController := storage.StoreController{ DefaultStore: mockImageStore, } - olu := common.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) + olu := NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) _, _, err := olu.GetImageManifest("test-repo", "latest") //nolint:goconst So(err, ShouldNotBeNil) @@ -3068,7 +3068,7 @@ func TestGetRepositories(t *testing.T) { DefaultStore: mockImageStore, SubStore: map[string]storage.ImageStore{"test": mockImageStore}, } - olu := common.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) + olu := NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) repoList, err := olu.GetRepositories() So(repoList, ShouldBeEmpty) @@ -3078,7 +3078,7 @@ func TestGetRepositories(t *testing.T) { DefaultStore: mocks.MockedImageStore{}, SubStore: map[string]storage.ImageStore{"test": mockImageStore}, } - olu = common.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) + olu = NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) repoList, err = olu.GetRepositories() So(repoList, ShouldBeEmpty) @@ -3388,7 +3388,7 @@ func TestGlobalSearch(t *testing.T) { ) So(err, ShouldBeNil) - olu := common.NewBaseOciLayoutUtils(ctlr.StoreController, log.NewLogger("debug", "")) + olu := NewBaseOciLayoutUtils(ctlr.StoreController, log.NewLogger("debug", "")) // Initialize the objects containing the expected data repos, err := olu.GetRepositories() @@ -3717,7 +3717,7 @@ func TestGlobalSearch(t *testing.T) { ) So(err, ShouldBeNil) - olu := common.NewBaseOciLayoutUtils(ctlr.StoreController, log.NewLogger("debug", "")) + olu := NewBaseOciLayoutUtils(ctlr.StoreController, log.NewLogger("debug", "")) // Initialize the objects containing the expected data repos, err := olu.GetRepositories() @@ -5998,320 +5998,6 @@ func updateManifestConfig(manifest ispec.Manifest, config ispec.Image) (ispec.Ma return manifest, err } -func TestBaseOciLayoutUtils(t *testing.T) { - manifestDigest := GetTestBlobDigest("zot-test", "config").String() - - Convey("GetImageManifestSize fail", t, func() { - mockStoreController := mocks.MockedImageStore{ - GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) { - return []byte{}, ErrTestError - }, - } - - storeController := storage.StoreController{DefaultStore: mockStoreController} - olu := common.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) - - size := olu.GetImageManifestSize("", "") - So(size, ShouldBeZeroValue) - }) - - Convey("GetImageConfigSize: fail GetImageBlobManifest", t, func() { - mockStoreController := mocks.MockedImageStore{ - GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) { - return []byte{}, ErrTestError - }, - } - - storeController := storage.StoreController{DefaultStore: mockStoreController} - olu := common.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) - - size := olu.GetImageConfigSize("", "") - So(size, ShouldBeZeroValue) - }) - - Convey("GetImageConfigSize: config GetBlobContent fail", t, func() { - mockStoreController := mocks.MockedImageStore{ - GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) { - if digest.String() == manifestDigest { - return []byte{}, ErrTestError - } - - return []byte( - ` - { - "schemaVersion": 2, - "mediaType": "application/vnd.oci.image.manifest.v1+json", - "config": { - "mediaType": "application/vnd.oci.image.config.v1+json", - "digest": manifestDigest, - "size": 1476 - }, - "layers": [ - { - "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", - "digest": "` + GetTestBlobDigest("zot-test", "layer").String() + `", - "size": 76097157 - } - ] - }`), nil - }, - } - - storeController := storage.StoreController{DefaultStore: mockStoreController} - olu := common.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) - - size := olu.GetImageConfigSize("", "") - So(size, ShouldBeZeroValue) - }) - - Convey("GetRepoLastUpdated: config GetBlobContent fail", t, func() { - mockStoreController := mocks.MockedImageStore{ - GetIndexContentFn: func(repo string) ([]byte, error) { - return []byte{}, ErrTestError - }, - } - - storeController := storage.StoreController{DefaultStore: mockStoreController} - olu := common.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) - - _, err := olu.GetRepoLastUpdated("") - So(err, ShouldNotBeNil) - }) - - Convey("GetImageTagsWithTimestamp: GetImageBlobManifest fails", t, func() { - index := ispec.Index{ - Manifests: []ispec.Descriptor{ - {Annotations: map[string]string{ispec.AnnotationRefName: "w"}}, {}, - }, - } - - indexBlob, err := json.Marshal(index) - So(err, ShouldBeNil) - - mockStoreController := mocks.MockedImageStore{ - GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) { - return nil, ErrTestError - }, - GetIndexContentFn: func(repo string) ([]byte, error) { - return indexBlob, nil - }, - } - - storeController := storage.StoreController{DefaultStore: mockStoreController} - olu := common.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) - - _, err = olu.GetImageTagsWithTimestamp("rep") - So(err, ShouldNotBeNil) - }) - - Convey("GetImageTagsWithTimestamp: GetImageInfo fails", t, func() { - index := ispec.Index{ - Manifests: []ispec.Descriptor{ - {Annotations: map[string]string{ispec.AnnotationRefName: "w"}}, {}, - }, - } - - indexBlob, err := json.Marshal(index) - So(err, ShouldBeNil) - - manifest := ispec.Manifest{ - Config: ispec.Descriptor{ - MediaType: "application/vnd.oci.image.config.v1+json", - Digest: "configDigest", - }, - Layers: []ispec.Descriptor{ - {}, - {}, - }, - } - - manifestBlob, err := json.Marshal(manifest) - So(err, ShouldBeNil) - - mockStoreController := mocks.MockedImageStore{ - GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) { - if digest.String() == "configDigest" { - return nil, ErrTestError - } - - return manifestBlob, nil - }, - GetIndexContentFn: func(repo string) ([]byte, error) { - return indexBlob, nil - }, - } - - storeController := storage.StoreController{DefaultStore: mockStoreController} - olu := common.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) - - _, err = olu.GetImageTagsWithTimestamp("repo") - So(err, ShouldNotBeNil) - }) - - Convey("GetExpandedRepoInfo: fails", t, func() { - index := ispec.Index{ - Manifests: []ispec.Descriptor{ - {}, - { - Annotations: map[string]string{ - ispec.AnnotationRefName: "w", - ispec.AnnotationVendor: "vend", - }, - }, - }, - } - - indexBlob, err := json.Marshal(index) - So(err, ShouldBeNil) - - manifest := ispec.Manifest{ - Annotations: map[string]string{ - ispec.AnnotationRefName: "w", - ispec.AnnotationVendor: "vend", - }, - Layers: []ispec.Descriptor{ - {}, - {}, - }, - } - - manifestBlob, err := json.Marshal(manifest) - So(err, ShouldBeNil) - - mockStoreController := mocks.MockedImageStore{ - GetIndexContentFn: func(repo string) ([]byte, error) { - return nil, ErrTestError - }, - } - - storeController := storage.StoreController{DefaultStore: mockStoreController} - olu := common.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) - - _, err = olu.GetExpandedRepoInfo("rep") - So(err, ShouldNotBeNil) - - // GetRepoLastUpdated fails - mockStoreController = mocks.MockedImageStore{ - GetIndexContentFn: func(repo string) ([]byte, error) { - return indexBlob, nil - }, - } - - storeController = storage.StoreController{DefaultStore: mockStoreController} - olu = common.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) - - _, err = olu.GetExpandedRepoInfo("rep") - So(err, ShouldNotBeNil) - - // anotations - - mockStoreController = mocks.MockedImageStore{ - GetIndexContentFn: func(repo string) ([]byte, error) { - return indexBlob, nil - }, - GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) { - return manifestBlob, nil - }, - } - - storeController = storage.StoreController{DefaultStore: mockStoreController} - olu = common.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) - - _, err = olu.GetExpandedRepoInfo("rep") - So(err, ShouldBeNil) - }) - - Convey("GetImageInfo fail", t, func() { - mockStoreController := mocks.MockedImageStore{ - GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) { - return []byte{}, ErrTestError - }, - } - - storeController := storage.StoreController{DefaultStore: mockStoreController} - olu := common.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) - - _, err := olu.GetImageInfo("", "") - So(err, ShouldNotBeNil) - }) - - Convey("CheckManifestSignature: notation", t, func() { - // GetReferrers - fails => checkNotarySignature returns false - mockStoreController := mocks.MockedImageStore{ - GetImageManifestFn: func(name, reference string) ([]byte, godigest.Digest, string, error) { - return []byte{}, "", "", zerr.ErrRepoNotFound - }, - GetReferrersFn: func(name string, digest godigest.Digest, mediaTypes []string) (ispec.Index, error) { - return ispec.Index{}, ErrTestError - }, - } - - storeController := storage.StoreController{DefaultStore: mockStoreController} - olu := common.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) - - check := olu.CheckManifestSignature("rep", godigest.FromString("")) - So(check, ShouldBeFalse) - - // checkNotarySignature -> true - dir := t.TempDir() - - port := GetFreePort() - baseURL := GetBaseURL(port) - conf := config.New() - conf.HTTP.Port = port - conf.Storage.RootDirectory = dir - defaultVal := true - conf.Extensions = &extconf.ExtensionConfig{ - Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, - } - - conf.Extensions.Search.CVE = nil - - ctlr := api.NewController(conf) - - ctlrManager := NewControllerManager(ctlr) - ctlrManager.StartAndWait(port) - defer ctlrManager.StopServer() - - // push test image to repo - config, layers, manifest, err := GetImageComponents(100) - So(err, ShouldBeNil) - - layersSize1 := 0 - for _, l := range layers { - layersSize1 += len(l) - } - - repo := "repo" - tag := "1.0.1" - err = UploadImage( - Image{ - Manifest: manifest, - Config: config, - Layers: layers, - Reference: tag, - }, - baseURL, - repo, - ) - So(err, ShouldBeNil) - - olu = common.NewBaseOciLayoutUtils(ctlr.StoreController, log.NewLogger("debug", "")) - manifestList, err := olu.GetImageManifests(repo) - So(err, ShouldBeNil) - So(len(manifestList), ShouldEqual, 1) - - isSigned := olu.CheckManifestSignature(repo, manifestList[0].Digest) - So(isSigned, ShouldBeFalse) - - err = SignImageUsingNotary(fmt.Sprintf("%s:%s", repo, tag), port) - So(err, ShouldBeNil) - - isSigned = olu.CheckManifestSignature(repo, manifestList[0].Digest) - So(isSigned, ShouldBeTrue) - }) -} - func TestSearchSize(t *testing.T) { Convey("Repo sizes", t, func() { port := GetFreePort() diff --git a/pkg/extensions/search/convert/convert_test.go b/pkg/extensions/search/convert/convert_test.go index b2ee14b8..ca5dc58a 100644 --- a/pkg/extensions/search/convert/convert_test.go +++ b/pkg/extensions/search/convert/convert_test.go @@ -4,20 +4,14 @@ import ( "context" "encoding/json" "errors" - "strconv" "testing" "time" "github.com/99designs/gqlgen/graphql" godigest "github.com/opencontainers/go-digest" - "github.com/opencontainers/image-spec/specs-go" ispec "github.com/opencontainers/image-spec/specs-go/v1" . "github.com/smartystreets/goconvey/convey" - "zotregistry.io/zot/pkg/api" - "zotregistry.io/zot/pkg/api/config" - extconf "zotregistry.io/zot/pkg/extensions/config" - "zotregistry.io/zot/pkg/extensions/search/common" "zotregistry.io/zot/pkg/extensions/search/convert" cveinfo "zotregistry.io/zot/pkg/extensions/search/cve" "zotregistry.io/zot/pkg/extensions/search/gql_generated" @@ -25,7 +19,6 @@ import ( "zotregistry.io/zot/pkg/meta/bolt" "zotregistry.io/zot/pkg/meta/repodb" boltdb_wrapper "zotregistry.io/zot/pkg/meta/repodb/boltdb-wrapper" - . "zotregistry.io/zot/pkg/test" "zotregistry.io/zot/pkg/test/mocks" ) @@ -340,274 +333,3 @@ func TestUpdateLastUpdatedTimestam(t *testing.T) { So(*img.LastUpdated, ShouldResemble, after) }) } - -func TestBuildImageInfo(t *testing.T) { - rootDir := t.TempDir() - - port := GetFreePort() - baseURL := GetBaseURL(port) - - conf := config.New() - conf.HTTP.Port = port - conf.Storage.RootDirectory = rootDir - defaultVal := true - conf.Extensions = &extconf.ExtensionConfig{ - Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, - } - - conf.Extensions.Search.CVE = nil - - ctlr := api.NewController(conf) - ctlrManager := NewControllerManager(ctlr) - - ctlrManager.StartAndWait(port) - defer ctlrManager.StopServer() - - olu := &common.BaseOciLayoutUtils{ - StoreController: ctlr.StoreController, - Log: ctlr.Log, - } - - Convey("Check image summary when the image has no history", t, func() { - imageName := "nohistory" - - config := ispec.Image{ - Platform: ispec.Platform{ - OS: "linux", - Architecture: "amd64", - }, - RootFS: ispec.RootFS{ - Type: "layers", - DiffIDs: []godigest.Digest{}, - }, - Author: "ZotUser", - } - - configBlob, err := json.Marshal(config) - So(err, ShouldBeNil) - - configDigest := godigest.FromBytes(configBlob) - layerDigest := godigest.FromString(imageName) - layerblob := []byte(imageName) - schemaVersion := 2 - ispecManifest := ispec.Manifest{ - Versioned: specs.Versioned{ - SchemaVersion: schemaVersion, - }, - Config: ispec.Descriptor{ - MediaType: "application/vnd.oci.image.config.v1+json", - Digest: configDigest, - Size: int64(len(configBlob)), - }, - Layers: []ispec.Descriptor{ // just 1 layer in manifest - { - MediaType: "application/vnd.oci.image.layer.v1.tar", - Digest: layerDigest, - Size: int64(len(layerblob)), - }, - }, - } - manifestLayersSize := ispecManifest.Layers[0].Size - manifestBlob, err := json.Marshal(ispecManifest) - So(err, ShouldBeNil) - manifestDigest := godigest.FromBytes(manifestBlob) - err = UploadImage( - Image{ - Manifest: ispecManifest, - Config: config, - Layers: [][]byte{ - layerblob, - }, - Reference: "0.0.1", - }, - baseURL, - imageName, - ) - So(err, ShouldBeNil) - - imageConfig, err := olu.GetImageConfigInfo(imageName, manifestDigest) - So(err, ShouldBeNil) - - isSigned := false - - imageSummary := convert.BuildImageInfo(imageName, imageName, manifestDigest, ispecManifest, - imageConfig, isSigned) - - So(len(imageSummary.Manifests[0].Layers), ShouldEqual, len(ispecManifest.Layers)) - imageSummaryLayerSize, err := strconv.Atoi(*imageSummary.Size) - So(err, ShouldBeNil) - So(imageSummaryLayerSize, ShouldEqual, manifestLayersSize) - }) - - Convey("Check image summary when layer count matche history entries", t, func() { - imageName := "valid" - - config := ispec.Image{ - Platform: ispec.Platform{ - OS: "linux", - Architecture: "amd64", - }, - RootFS: ispec.RootFS{ - Type: "layers", - DiffIDs: []godigest.Digest{}, - }, - Author: "ZotUser", - History: []ispec.History{ // should contain 3 elements, 2 of which corresponding to layers - { - EmptyLayer: false, - }, - { - EmptyLayer: false, - }, - { - EmptyLayer: true, - }, - }, - } - - configBlob, err := json.Marshal(config) - So(err, ShouldBeNil) - - configDigest := godigest.FromBytes(configBlob) - layerDigest := godigest.FromString("layer1") - layerblob := []byte("layer1") - layerDigest2 := godigest.FromString("layer2") - layerblob2 := []byte("layer2") - schemaVersion := 2 - ispecManifest := ispec.Manifest{ - Versioned: specs.Versioned{ - SchemaVersion: schemaVersion, - }, - Config: ispec.Descriptor{ - MediaType: "application/vnd.oci.image.config.v1+json", - Digest: configDigest, - Size: int64(len(configBlob)), - }, - Layers: []ispec.Descriptor{ // just 1 layer in manifest - { - MediaType: "application/vnd.oci.image.layer.v1.tar", - Digest: layerDigest, - Size: int64(len(layerblob)), - }, - { - MediaType: "application/vnd.oci.image.layer.v1.tar", - Digest: layerDigest2, - Size: int64(len(layerblob2)), - }, - }, - } - manifestLayersSize := ispecManifest.Layers[0].Size + ispecManifest.Layers[1].Size - manifestBlob, err := json.Marshal(ispecManifest) - So(err, ShouldBeNil) - manifestDigest := godigest.FromBytes(manifestBlob) - err = UploadImage( - Image{ - Manifest: ispecManifest, - Config: config, - Layers: [][]byte{ - layerblob, - layerblob2, - }, - Reference: "0.0.1", - }, - baseURL, - imageName, - ) - So(err, ShouldBeNil) - - imageConfig, err := olu.GetImageConfigInfo(imageName, manifestDigest) - So(err, ShouldBeNil) - - isSigned := false - - imageSummary := convert.BuildImageInfo(imageName, imageName, manifestDigest, ispecManifest, - imageConfig, isSigned) - - So(len(imageSummary.Manifests[0].Layers), ShouldEqual, len(ispecManifest.Layers)) - imageSummaryLayerSize, err := strconv.Atoi(*imageSummary.Size) - So(err, ShouldBeNil) - So(imageSummaryLayerSize, ShouldEqual, manifestLayersSize) - }) - - Convey("Check image summary when layer count does not match history", t, func() { - imageName := "invalid" - - config := ispec.Image{ - Platform: ispec.Platform{ - OS: "linux", - Architecture: "amd64", - }, - RootFS: ispec.RootFS{ - Type: "layers", - DiffIDs: []godigest.Digest{}, - }, - Author: "ZotUser", - History: []ispec.History{ // should contain 3 elements, 2 of which corresponding to layers - { - EmptyLayer: false, - }, - { - EmptyLayer: false, - }, - { - EmptyLayer: true, - }, - }, - } - - configBlob, err := json.Marshal(config) - So(err, ShouldBeNil) - - configDigest := godigest.FromBytes(configBlob) - layerDigest := godigest.FromString(imageName) - layerblob := []byte(imageName) - schemaVersion := 2 - ispecManifest := ispec.Manifest{ - Versioned: specs.Versioned{ - SchemaVersion: schemaVersion, - }, - Config: ispec.Descriptor{ - MediaType: "application/vnd.oci.image.config.v1+json", - Digest: configDigest, - Size: int64(len(configBlob)), - }, - Layers: []ispec.Descriptor{ // just 1 layer in manifest - { - MediaType: "application/vnd.oci.image.layer.v1.tar", - Digest: layerDigest, - Size: int64(len(layerblob)), - }, - }, - } - manifestLayersSize := ispecManifest.Layers[0].Size - manifestBlob, err := json.Marshal(ispecManifest) - So(err, ShouldBeNil) - manifestDigest := godigest.FromBytes(manifestBlob) - err = UploadImage( - Image{ - Manifest: ispecManifest, - Config: config, - Layers: [][]byte{ - layerblob, - }, - Reference: "0.0.1", - }, - baseURL, - imageName, - ) - So(err, ShouldBeNil) - - imageConfig, err := olu.GetImageConfigInfo(imageName, manifestDigest) - So(err, ShouldBeNil) - - isSigned := false - - imageSummary := convert.BuildImageInfo(imageName, imageName, manifestDigest, ispecManifest, - imageConfig, isSigned) - - So(len(imageSummary.Manifests[0].Layers), ShouldEqual, len(ispecManifest.Layers)) - imageSummaryLayerSize, err := strconv.Atoi(*imageSummary.Size) - So(err, ShouldBeNil) - So(imageSummaryLayerSize, ShouldEqual, manifestLayersSize) - }) -} diff --git a/pkg/extensions/search/convert/oci.go b/pkg/extensions/search/convert/oci.go index be591736..a4f7a37b 100644 --- a/pkg/extensions/search/convert/oci.go +++ b/pkg/extensions/search/convert/oci.go @@ -3,198 +3,12 @@ package convert import ( "strconv" - godigest "github.com/opencontainers/go-digest" ispec "github.com/opencontainers/image-spec/specs-go/v1" zerr "zotregistry.io/zot/errors" - "zotregistry.io/zot/pkg/extensions/search/common" "zotregistry.io/zot/pkg/extensions/search/gql_generated" - "zotregistry.io/zot/pkg/log" ) -func BuildImageInfo(repo string, tag string, manifestDigest godigest.Digest, - manifest ispec.Manifest, imageConfig ispec.Image, isSigned bool, -) *gql_generated.ImageSummary { - layers := []*gql_generated.LayerSummary{} - size := int64(0) - log := log.NewLogger("debug", "") - allHistory := []*gql_generated.LayerHistory{} - formattedManifestDigest := manifestDigest.String() - configDigest := manifest.Config.Digest.String() - annotations := common.GetAnnotations(manifest.Annotations, imageConfig.Config.Labels) - lastUpdated := common.GetImageLastUpdated(imageConfig) - - authors := annotations.Authors - if authors == "" { - authors = imageConfig.Author - } - - history := imageConfig.History - if len(history) == 0 { - for _, layer := range manifest.Layers { - size += layer.Size - digest := layer.Digest.String() - layerSize := strconv.FormatInt(layer.Size, 10) - - layer := &gql_generated.LayerSummary{ - Size: &layerSize, - Digest: &digest, - } - - layers = append( - layers, - layer, - ) - - allHistory = append(allHistory, &gql_generated.LayerHistory{ - Layer: layer, - HistoryDescription: &gql_generated.HistoryDescription{}, - }) - } - - formattedSize := strconv.FormatInt(size, 10) - - imageInfo := &gql_generated.ImageSummary{ - RepoName: &repo, - Tag: &tag, - Manifests: []*gql_generated.ManifestSummary{ - { - Digest: &formattedManifestDigest, - ConfigDigest: &configDigest, - Layers: layers, - Size: &formattedSize, - History: allHistory, - Platform: &gql_generated.Platform{ - Os: &imageConfig.OS, - Arch: &imageConfig.Architecture, - }, - LastUpdated: &lastUpdated, - }, - }, - Size: &formattedSize, - Description: &annotations.Description, - Title: &annotations.Title, - Documentation: &annotations.Documentation, - Licenses: &annotations.Licenses, - Labels: &annotations.Labels, - Source: &annotations.Source, - Authors: &authors, - Vendor: &annotations.Vendor, - LastUpdated: &lastUpdated, - IsSigned: &isSigned, - } - - return imageInfo - } - - // iterator over manifest layers - var layersIterator int - // since we are appending pointers, it is important to iterate with an index over slice - for i := range history { - allHistory = append(allHistory, &gql_generated.LayerHistory{ - HistoryDescription: &gql_generated.HistoryDescription{ - Created: history[i].Created, - CreatedBy: &history[i].CreatedBy, - Author: &history[i].Author, - Comment: &history[i].Comment, - EmptyLayer: &history[i].EmptyLayer, - }, - }) - - if history[i].EmptyLayer { - continue - } - - if layersIterator+1 > len(manifest.Layers) { - formattedSize := strconv.FormatInt(size, 10) - - log.Error().Err(zerr.ErrBadLayerCount).Msg("error on creating layer history for ImageSummary") - - return &gql_generated.ImageSummary{ - RepoName: &repo, - Tag: &tag, - Manifests: []*gql_generated.ManifestSummary{ - { - Digest: &formattedManifestDigest, - ConfigDigest: &configDigest, - Layers: layers, - Size: &formattedSize, - History: allHistory, - Platform: &gql_generated.Platform{ - Os: &imageConfig.OS, - Arch: &imageConfig.Architecture, - }, - LastUpdated: &lastUpdated, - }, - }, - Size: &formattedSize, - Description: &annotations.Description, - Vendor: &annotations.Vendor, - Title: &annotations.Title, - Documentation: &annotations.Documentation, - Licenses: &annotations.Licenses, - Labels: &annotations.Labels, - Source: &annotations.Source, - Authors: &authors, - LastUpdated: &lastUpdated, - IsSigned: &isSigned, - } - } - - size += manifest.Layers[layersIterator].Size - digest := manifest.Layers[layersIterator].Digest.String() - layerSize := strconv.FormatInt(manifest.Layers[layersIterator].Size, 10) - - layer := &gql_generated.LayerSummary{ - Size: &layerSize, - Digest: &digest, - } - - layers = append( - layers, - layer, - ) - - allHistory[i].Layer = layer - - layersIterator++ - } - - formattedSize := strconv.FormatInt(size, 10) - - imageInfo := &gql_generated.ImageSummary{ - RepoName: &repo, - Tag: &tag, - Manifests: []*gql_generated.ManifestSummary{ - { - Digest: &formattedManifestDigest, - ConfigDigest: &configDigest, - Layers: layers, - History: allHistory, - Platform: &gql_generated.Platform{ - Os: &imageConfig.OS, - Arch: &imageConfig.Architecture, - }, - Size: &formattedSize, - LastUpdated: &lastUpdated, - }, - }, - Size: &formattedSize, - Description: &annotations.Description, - Title: &annotations.Title, - Documentation: &annotations.Documentation, - Licenses: &annotations.Licenses, - Labels: &annotations.Labels, - Source: &annotations.Source, - Vendor: &annotations.Vendor, - Authors: &authors, - LastUpdated: &lastUpdated, - IsSigned: &isSigned, - } - - return imageInfo -} - // updateRepoBlobsMap adds all the image blobs and their respective size to the repo blobs map // and returnes the total size of the image. func updateRepoBlobsMap(imageBlobs map[string]int64, repoBlob2Size map[string]int64) int64 { diff --git a/pkg/extensions/search/resolver.go b/pkg/extensions/search/resolver.go index ea0eb018..b8e66128 100644 --- a/pkg/extensions/search/resolver.go +++ b/pkg/extensions/search/resolver.go @@ -1213,67 +1213,3 @@ func getReferrers(repoDB repodb.RepoDB, repo string, referredDigest string, arti return results, nil } - -// get passed context from authzHandler and filter out repos based on permissions. -func userAvailableRepos(ctx context.Context, repoList []string) ([]string, error) { - var availableRepos []string - - // authz request context (set in authz middleware) - acCtx, err := localCtx.GetAccessControlContext(ctx) - if err != nil { - err := zerr.ErrBadType - - return []string{}, err - } - - if acCtx != nil { - for _, r := range repoList { - if acCtx.IsAdmin || acCtx.CanReadRepo(r) { - availableRepos = append(availableRepos, r) - } - } - } else { - availableRepos = repoList - } - - return availableRepos, nil -} - -func extractImageDetails( - ctx context.Context, - layoutUtils common.OciLayoutUtils, - repo, tag string, //nolint:unparam // function only called in the tests - log log.Logger) ( - godigest.Digest, *ispec.Manifest, *ispec.Image, error, -) { - validRepoList, err := userAvailableRepos(ctx, []string{repo}) - if err != nil { - log.Error().Err(err).Msg("unable to retrieve access token") - - return "", nil, nil, err - } - - if len(validRepoList) == 0 { - log.Error().Err(err).Msg("user is not authorized") - - return "", nil, nil, zerr.ErrUnauthorizedAccess - } - - manifest, dig, err := layoutUtils.GetImageManifest(repo, tag) - if err != nil { - log.Error().Err(err).Msg("Could not retrieve image ispec manifest") - - return "", nil, nil, err - } - - digest := dig - - imageConfig, err := layoutUtils.GetImageConfigInfo(repo, digest) - if err != nil { - log.Error().Err(err).Msg("Could not retrieve image config") - - return "", nil, nil, err - } - - return digest, &manifest, &imageConfig, nil -} diff --git a/pkg/extensions/search/resolver_test.go b/pkg/extensions/search/resolver_test.go index 6c991900..69a4f8f4 100644 --- a/pkg/extensions/search/resolver_test.go +++ b/pkg/extensions/search/resolver_test.go @@ -5,7 +5,6 @@ import ( "encoding/json" "errors" "fmt" - "strings" "testing" "time" @@ -23,7 +22,6 @@ import ( "zotregistry.io/zot/pkg/meta/bolt" "zotregistry.io/zot/pkg/meta/repodb" boltdb_wrapper "zotregistry.io/zot/pkg/meta/repodb/boltdb-wrapper" - localCtx "zotregistry.io/zot/pkg/requestcontext" "zotregistry.io/zot/pkg/storage" "zotregistry.io/zot/pkg/test/mocks" ) @@ -1422,147 +1420,6 @@ func TestGetReferrers(t *testing.T) { }) } -func TestExtractImageDetails(t *testing.T) { - Convey("repoListWithNewestImage", t, func() { - // log := log.Logger{Logger: zerolog.New(os.Stdout)} - content := []byte("this is a blob5") - testLogger := log.NewLogger("debug", "") - layerDigest := godigest.FromBytes(content) - config := ispec.Image{ - Platform: ispec.Platform{ - Architecture: "amd64", - OS: "linux", - }, - RootFS: ispec.RootFS{ - Type: "layers", - DiffIDs: []godigest.Digest{}, - }, - Author: "some author", - } - - ctx := context.TODO() - authzCtxKey := localCtx.GetContextKey() - ctx = context.WithValue(ctx, authzCtxKey, - localCtx.AccessControlContext{ - ReadGlobPatterns: map[string]bool{"*": true, "**": true}, - Username: "jane_doe", - }) - configBlobContent, _ := json.MarshalIndent(&config, "", "\t") - configDigest := godigest.FromBytes(configBlobContent) - - localTestManifest := ispec.Manifest{ - Config: ispec.Descriptor{ - MediaType: "application/vnd.oci.image.config.v1+json", - Digest: configDigest, - Size: int64(len(configBlobContent)), - }, - Layers: []ispec.Descriptor{ - { - MediaType: "application/vnd.oci.image.layer.v1.tar", - Digest: layerDigest, - Size: int64(len(content)), - }, - }, - } - localTestDigestTry, _ := json.Marshal(localTestManifest) - localTestDigest := godigest.FromBytes(localTestDigestTry) - - Convey("extractImageDetails good workflow", func() { - mockOlum := mocks.OciLayoutUtilsMock{ - GetImageConfigInfoFn: func(repo string, digest godigest.Digest) ( - ispec.Image, error, - ) { - return config, nil - }, - GetImageManifestFn: func(repo string, tag string) ( - ispec.Manifest, godigest.Digest, error, - ) { - return localTestManifest, localTestDigest, nil - }, - } - resDigest, resManifest, resIspecImage, resErr := extractImageDetails(ctx, - mockOlum, "zot-test", "latest", testLogger) - So(string(resDigest), ShouldContainSubstring, "sha256:d004018b9f") - So(resManifest.Config.Digest.String(), ShouldContainSubstring, configDigest.Encoded()) - - So(resIspecImage.Architecture, ShouldContainSubstring, "amd64") - So(resErr, ShouldBeNil) - }) - - Convey("extractImageDetails bad ispec.ImageManifest", func() { - mockOlum := mocks.OciLayoutUtilsMock{ - GetImageConfigInfoFn: func(repo string, digest godigest.Digest) ( - ispec.Image, error, - ) { - return config, nil - }, - GetImageManifestFn: func(repo string, tag string) ( - ispec.Manifest, godigest.Digest, error, - ) { - return ispec.Manifest{}, localTestDigest, ErrTestError - }, - } - resDigest, resManifest, resIspecImage, resErr := extractImageDetails(ctx, - mockOlum, "zot-test", "latest", testLogger) - So(resErr, ShouldEqual, ErrTestError) - So(string(resDigest), ShouldEqual, "") - So(resManifest, ShouldBeNil) - - So(resIspecImage, ShouldBeNil) - }) - - Convey("extractImageDetails bad imageConfig", func() { - mockOlum := mocks.OciLayoutUtilsMock{ - GetImageConfigInfoFn: func(repo string, digest godigest.Digest) ( - ispec.Image, error, - ) { - return config, nil - }, - GetImageManifestFn: func(repo string, tag string) ( - ispec.Manifest, godigest.Digest, error, - ) { - return localTestManifest, localTestDigest, ErrTestError - }, - } - resDigest, resManifest, resIspecImage, resErr := extractImageDetails(ctx, - mockOlum, "zot-test", "latest", testLogger) - So(string(resDigest), ShouldEqual, "") - So(resManifest, ShouldBeNil) - - So(resIspecImage, ShouldBeNil) - So(resErr, ShouldEqual, ErrTestError) - }) - - Convey("extractImageDetails without proper authz", func() { - ctx = context.WithValue(ctx, authzCtxKey, - localCtx.AccessControlContext{ - ReadGlobPatterns: map[string]bool{}, - Username: "jane_doe", - }) - mockOlum := mocks.OciLayoutUtilsMock{ - GetImageConfigInfoFn: func(repo string, digest godigest.Digest) ( - ispec.Image, error, - ) { - return config, nil - }, - GetImageManifestFn: func(repo string, tag string) ( - ispec.Manifest, godigest.Digest, error, - ) { - return localTestManifest, localTestDigest, ErrTestError - }, - } - resDigest, resManifest, resIspecImage, resErr := extractImageDetails(ctx, - mockOlum, "zot-test", "latest", testLogger) - So(string(resDigest), ShouldEqual, "") - So(resManifest, ShouldBeNil) - - So(resIspecImage, ShouldBeNil) - So(resErr, ShouldNotBeNil) - So(strings.ToLower(resErr.Error()), ShouldContainSubstring, "unauthorized access") - }) - }) -} - func TestQueryResolverErrors(t *testing.T) { Convey("Errors", t, func() { log := log.NewLogger("debug", "") diff --git a/pkg/extensions/search/common/oci_layout.go b/pkg/test/oci_layout.go similarity index 83% rename from pkg/extensions/search/common/oci_layout.go rename to pkg/test/oci_layout.go index 6671426e..3184592d 100644 --- a/pkg/extensions/search/common/oci_layout.go +++ b/pkg/test/oci_layout.go @@ -1,5 +1,7 @@ -// Package common ... -package common +//go:build sync && scrub && metrics && search +// +build sync,scrub,metrics,search + +package test import ( "encoding/json" @@ -14,7 +16,8 @@ import ( godigest "github.com/opencontainers/go-digest" ispec "github.com/opencontainers/image-spec/specs-go/v1" - "zotregistry.io/zot/errors" + zerr "zotregistry.io/zot/errors" + "zotregistry.io/zot/pkg/extensions/search/common" "zotregistry.io/zot/pkg/log" "zotregistry.io/zot/pkg/storage" ) @@ -24,14 +27,16 @@ type OciLayoutUtils interface { //nolint: interfacebloat GetImageManifests(repo string) ([]ispec.Descriptor, error) GetImageBlobManifest(repo string, digest godigest.Digest) (ispec.Manifest, error) GetImageInfo(repo string, configDigest godigest.Digest) (ispec.Image, error) - GetImageTagsWithTimestamp(repo string) ([]TagInfo, error) + GetImageTagsWithTimestamp(repo string) ([]common.TagInfo, error) GetImagePlatform(imageInfo ispec.Image) (string, string) GetImageManifestSize(repo string, manifestDigest godigest.Digest) int64 - GetRepoLastUpdated(repo string) (TagInfo, error) - GetExpandedRepoInfo(name string) (RepoInfo, error) + GetRepoLastUpdated(repo string) (common.TagInfo, error) + GetExpandedRepoInfo(name string) (common.RepoInfo, error) GetImageConfigInfo(repo string, manifestDigest godigest.Digest) (ispec.Image, error) CheckManifestSignature(name string, digest godigest.Digest) bool GetRepositories() ([]string, error) + ExtractImageDetails(repo string, tag string, log log.Logger) (godigest.Digest, + *ispec.Manifest, *ispec.Image, error) } // OciLayoutInfo ... @@ -100,15 +105,15 @@ func (olu BaseOciLayoutUtils) GetImageManifests(repo string) ([]ispec.Descriptor buf, err := imageStore.GetIndexContent(repo) if err != nil { - if goerrors.Is(errors.ErrRepoNotFound, err) { + if goerrors.Is(zerr.ErrRepoNotFound, err) { olu.Log.Error().Err(err).Msg("index.json doesn't exist") - return nil, errors.ErrRepoNotFound + return nil, zerr.ErrRepoNotFound } olu.Log.Error().Err(err).Msg("unable to open index.json") - return nil, errors.ErrRepoNotFound + return nil, zerr.ErrRepoNotFound } var index ispec.Index @@ -116,7 +121,7 @@ func (olu BaseOciLayoutUtils) GetImageManifests(repo string) ([]ispec.Descriptor if err := json.Unmarshal(buf, &index); err != nil { olu.Log.Error().Err(err).Str("dir", path.Join(imageStore.RootDir(), repo)).Msg("invalid JSON") - return nil, errors.ErrRepoNotFound + return nil, zerr.ErrRepoNotFound } return index.Manifests, nil @@ -175,8 +180,8 @@ func (olu BaseOciLayoutUtils) GetImageInfo(repo string, configDigest godigest.Di } // GetImageTagsWithTimestamp returns a list of image tags with timestamp available in the specified repository. -func (olu BaseOciLayoutUtils) GetImageTagsWithTimestamp(repo string) ([]TagInfo, error) { - tagsInfo := make([]TagInfo, 0) +func (olu BaseOciLayoutUtils) GetImageTagsWithTimestamp(repo string) ([]common.TagInfo, error) { + tagsInfo := make([]common.TagInfo, 0) manifests, err := olu.GetImageManifests(repo) if err != nil { @@ -204,13 +209,13 @@ func (olu BaseOciLayoutUtils) GetImageTagsWithTimestamp(repo string) ([]TagInfo, return tagsInfo, err } - timeStamp := GetImageLastUpdated(imageInfo) + timeStamp := common.GetImageLastUpdated(imageInfo) tagsInfo = append(tagsInfo, - TagInfo{ + common.TagInfo{ Name: val, Timestamp: timeStamp, - Descriptor: Descriptor{ + Descriptor: common.Descriptor{ Digest: digest, MediaType: manifest.MediaType, }, @@ -325,44 +330,44 @@ func (olu BaseOciLayoutUtils) GetImageConfigSize(repo string, manifestDigest god return imageBlobManifest.Config.Size } -func (olu BaseOciLayoutUtils) GetRepoLastUpdated(repo string) (TagInfo, error) { +func (olu BaseOciLayoutUtils) GetRepoLastUpdated(repo string) (common.TagInfo, error) { tagsInfo, err := olu.GetImageTagsWithTimestamp(repo) if err != nil || len(tagsInfo) == 0 { - return TagInfo{}, err + return common.TagInfo{}, err } - latestTag := GetLatestTag(tagsInfo) + latestTag := common.GetLatestTag(tagsInfo) return latestTag, nil } -func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(repoName string) (RepoInfo, error) { - repo := RepoInfo{} +func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(repoName string) (common.RepoInfo, error) { + repo := common.RepoInfo{} repoBlob2Size := make(map[string]int64, 10) // made up of all manifests, configs and image layers repoSize := int64(0) - imageSummaries := make([]ImageSummary, 0) + imageSummaries := make([]common.ImageSummary, 0) manifestList, err := olu.GetImageManifests(repoName) if err != nil { olu.Log.Error().Err(err).Msg("error getting image manifests") - return RepoInfo{}, err + return common.RepoInfo{}, err } lastUpdatedTag, err := olu.GetRepoLastUpdated(repoName) if err != nil { olu.Log.Error().Err(err).Msgf("can't get last updated manifest for repo: %s", repoName) - return RepoInfo{}, err + return common.RepoInfo{}, err } repoVendorsSet := make(map[string]bool, len(manifestList)) - repoPlatformsSet := make(map[string]Platform, len(manifestList)) + repoPlatformsSet := make(map[string]common.Platform, len(manifestList)) - var lastUpdatedImageSummary ImageSummary + var lastUpdatedImageSummary common.ImageSummary for _, man := range manifestList { imageLayersSize := int64(0) @@ -379,7 +384,7 @@ func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(repoName string) (RepoInfo, er if err != nil { olu.Log.Error().Err(err).Msg("error getting image manifest blob") - return RepoInfo{}, err + return common.RepoInfo{}, err } isSigned := olu.CheckManifestSignature(repoName, man.Digest) @@ -399,7 +404,7 @@ func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(repoName string) (RepoInfo, er } opSys, arch := olu.GetImagePlatform(imageConfigInfo) - platform := Platform{ + platform := common.Platform{ Os: opSys, Arch: arch, } @@ -409,10 +414,10 @@ func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(repoName string) (RepoInfo, er repoPlatformsSet[platformString] = platform } - layers := make([]LayerSummary, 0) + layers := make([]common.LayerSummary, 0) for _, layer := range manifest.Layers { - layerInfo := LayerSummary{} + layerInfo := common.LayerSummary{} layerInfo.Digest = layer.Digest.String() @@ -428,20 +433,20 @@ func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(repoName string) (RepoInfo, er imageSize := imageLayersSize + manifestSize + configSize // get image info from manifest annotation, if not found get from image config labels. - annotations := GetAnnotations(manifest.Annotations, imageConfigInfo.Config.Labels) + annotations := common.GetAnnotations(manifest.Annotations, imageConfigInfo.Config.Labels) if annotations.Vendor != "" { repoVendorsSet[annotations.Vendor] = true } imageConfigHistory := imageConfigInfo.History - allHistory := []LayerHistory{} + allHistory := []common.LayerHistory{} if len(imageConfigHistory) == 0 { for _, layer := range layers { - allHistory = append(allHistory, LayerHistory{ + allHistory = append(allHistory, common.LayerHistory{ Layer: layer, - HistoryDescription: HistoryDescription{}, + HistoryDescription: common.HistoryDescription{}, }) } } else { @@ -449,8 +454,8 @@ func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(repoName string) (RepoInfo, er var layersIterator int // since we are appending pointers, it is important to iterate with an index over slice for i := range imageConfigHistory { - allHistory = append(allHistory, LayerHistory{ - HistoryDescription: HistoryDescription{ + allHistory = append(allHistory, common.LayerHistory{ + HistoryDescription: common.HistoryDescription{ Created: *imageConfigHistory[i].Created, CreatedBy: imageConfigHistory[i].CreatedBy, Author: imageConfigHistory[i].Author, @@ -464,7 +469,7 @@ func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(repoName string) (RepoInfo, er } if layersIterator+1 > len(layers) { - olu.Log.Error().Err(errors.ErrBadLayerCount). + olu.Log.Error().Err(zerr.ErrBadLayerCount). Msgf("error on creating layer history for image %s %s", repoName, man.Digest) break @@ -481,12 +486,12 @@ func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(repoName string) (RepoInfo, er size := strconv.Itoa(int(imageSize)) manifestDigest := man.Digest.String() configDigest := manifest.Config.Digest.String() - lastUpdated := GetImageLastUpdated(imageConfigInfo) + lastUpdated := common.GetImageLastUpdated(imageConfigInfo) - imageSummary := ImageSummary{ + imageSummary := common.ImageSummary{ RepoName: repoName, Tag: tag, - Manifests: []ManifestSummary{ + Manifests: []common.ManifestSummary{ { Digest: manifestDigest, ConfigDigest: configDigest, @@ -524,7 +529,7 @@ func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(repoName string) (RepoInfo, er size := strconv.FormatInt(repoSize, 10) - repoPlatforms := make([]Platform, 0, len(repoPlatformsSet)) + repoPlatforms := make([]common.Platform, 0, len(repoPlatformsSet)) for _, platform := range repoPlatformsSet { repoPlatforms = append(repoPlatforms, platform) @@ -537,7 +542,7 @@ func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(repoName string) (RepoInfo, er repoVendors = append(repoVendors, vendor) } - summary := RepoSummary{ + summary := common.RepoSummary{ Name: repoName, LastUpdated: lastUpdatedTag.Timestamp, Size: size, @@ -550,3 +555,27 @@ func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(repoName string) (RepoInfo, er return repo, nil } + +func (olu BaseOciLayoutUtils) ExtractImageDetails( + repo, tag string, + log log.Logger) ( + godigest.Digest, *ispec.Manifest, *ispec.Image, error, +) { + manifest, dig, err := olu.GetImageManifest(repo, tag) + if err != nil { + log.Error().Err(err).Msg("Could not retrieve image ispec manifest") + + return "", nil, nil, err + } + + digest := dig + + imageConfig, err := olu.GetImageConfigInfo(repo, digest) + if err != nil { + log.Error().Err(err).Msg("Could not retrieve image config") + + return "", nil, nil, err + } + + return digest, &manifest, &imageConfig, nil +} diff --git a/pkg/test/oci_layout_test.go b/pkg/test/oci_layout_test.go new file mode 100644 index 00000000..d585622f --- /dev/null +++ b/pkg/test/oci_layout_test.go @@ -0,0 +1,441 @@ +//go:build sync && scrub && metrics && search +// +build sync,scrub,metrics,search + +package test_test + +import ( + "encoding/json" + "fmt" + "os" + "path" + "testing" + + godigest "github.com/opencontainers/go-digest" + ispec "github.com/opencontainers/image-spec/specs-go/v1" + . "github.com/smartystreets/goconvey/convey" + + zerr "zotregistry.io/zot/errors" + "zotregistry.io/zot/pkg/api" + "zotregistry.io/zot/pkg/api/config" + extconf "zotregistry.io/zot/pkg/extensions/config" + "zotregistry.io/zot/pkg/extensions/monitoring" + "zotregistry.io/zot/pkg/log" + "zotregistry.io/zot/pkg/storage" + "zotregistry.io/zot/pkg/storage/local" + . "zotregistry.io/zot/pkg/test" + "zotregistry.io/zot/pkg/test/mocks" +) + +func TestBaseOciLayoutUtils(t *testing.T) { + manifestDigest := GetTestBlobDigest("zot-test", "config").String() + + Convey("GetImageManifestSize fail", t, func() { + mockStoreController := mocks.MockedImageStore{ + GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) { + return []byte{}, ErrTestError + }, + } + + storeController := storage.StoreController{DefaultStore: mockStoreController} + olu := NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) + + size := olu.GetImageManifestSize("", "") + So(size, ShouldBeZeroValue) + }) + + Convey("GetImageConfigSize: fail GetImageBlobManifest", t, func() { + mockStoreController := mocks.MockedImageStore{ + GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) { + return []byte{}, ErrTestError + }, + } + + storeController := storage.StoreController{DefaultStore: mockStoreController} + olu := NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) + + size := olu.GetImageConfigSize("", "") + So(size, ShouldBeZeroValue) + }) + + Convey("GetImageConfigSize: config GetBlobContent fail", t, func() { + mockStoreController := mocks.MockedImageStore{ + GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) { + if digest.String() == manifestDigest { + return []byte{}, ErrTestError + } + + return []byte( + ` + { + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": { + "mediaType": "application/vnd.oci.image.config.v1+json", + "digest": manifestDigest, + "size": 1476 + }, + "layers": [ + { + "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", + "digest": "` + GetTestBlobDigest("zot-test", "layer").String() + `", + "size": 76097157 + } + ] + }`), nil + }, + } + + storeController := storage.StoreController{DefaultStore: mockStoreController} + olu := NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) + + size := olu.GetImageConfigSize("", "") + So(size, ShouldBeZeroValue) + }) + + Convey("GetRepoLastUpdated: config GetBlobContent fail", t, func() { + mockStoreController := mocks.MockedImageStore{ + GetIndexContentFn: func(repo string) ([]byte, error) { + return []byte{}, ErrTestError + }, + } + + storeController := storage.StoreController{DefaultStore: mockStoreController} + olu := NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) + + _, err := olu.GetRepoLastUpdated("") + So(err, ShouldNotBeNil) + }) + + Convey("GetImageTagsWithTimestamp: GetImageBlobManifest fails", t, func() { + index := ispec.Index{ + Manifests: []ispec.Descriptor{ + {Annotations: map[string]string{ispec.AnnotationRefName: "w"}}, {}, + }, + } + + indexBlob, err := json.Marshal(index) + So(err, ShouldBeNil) + + mockStoreController := mocks.MockedImageStore{ + GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) { + return nil, ErrTestError + }, + GetIndexContentFn: func(repo string) ([]byte, error) { + return indexBlob, nil + }, + } + + storeController := storage.StoreController{DefaultStore: mockStoreController} + olu := NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) + + _, err = olu.GetImageTagsWithTimestamp("rep") + So(err, ShouldNotBeNil) + }) + + Convey("GetImageTagsWithTimestamp: GetImageInfo fails", t, func() { + index := ispec.Index{ + Manifests: []ispec.Descriptor{ + {Annotations: map[string]string{ispec.AnnotationRefName: "w"}}, {}, + }, + } + + indexBlob, err := json.Marshal(index) + So(err, ShouldBeNil) + + manifest := ispec.Manifest{ + Config: ispec.Descriptor{ + MediaType: "application/vnd.oci.image.config.v1+json", + Digest: "configDigest", + }, + Layers: []ispec.Descriptor{ + {}, + {}, + }, + } + + manifestBlob, err := json.Marshal(manifest) + So(err, ShouldBeNil) + + mockStoreController := mocks.MockedImageStore{ + GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) { + if digest.String() == "configDigest" { + return nil, ErrTestError + } + + return manifestBlob, nil + }, + GetIndexContentFn: func(repo string) ([]byte, error) { + return indexBlob, nil + }, + } + + storeController := storage.StoreController{DefaultStore: mockStoreController} + olu := NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) + + _, err = olu.GetImageTagsWithTimestamp("repo") + So(err, ShouldNotBeNil) + }) + + Convey("GetExpandedRepoInfo: fails", t, func() { + index := ispec.Index{ + Manifests: []ispec.Descriptor{ + {}, + { + Annotations: map[string]string{ + ispec.AnnotationRefName: "w", + ispec.AnnotationVendor: "vend", + }, + }, + }, + } + + indexBlob, err := json.Marshal(index) + So(err, ShouldBeNil) + + manifest := ispec.Manifest{ + Annotations: map[string]string{ + ispec.AnnotationRefName: "w", + ispec.AnnotationVendor: "vend", + }, + Layers: []ispec.Descriptor{ + {}, + {}, + }, + } + + manifestBlob, err := json.Marshal(manifest) + So(err, ShouldBeNil) + + mockStoreController := mocks.MockedImageStore{ + GetIndexContentFn: func(repo string) ([]byte, error) { + return nil, ErrTestError + }, + } + + storeController := storage.StoreController{DefaultStore: mockStoreController} + olu := NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) + + _, err = olu.GetExpandedRepoInfo("rep") + So(err, ShouldNotBeNil) + + // GetRepoLastUpdated fails + mockStoreController = mocks.MockedImageStore{ + GetIndexContentFn: func(repo string) ([]byte, error) { + return indexBlob, nil + }, + } + + storeController = storage.StoreController{DefaultStore: mockStoreController} + olu = NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) + + _, err = olu.GetExpandedRepoInfo("rep") + So(err, ShouldNotBeNil) + + // anotations + + mockStoreController = mocks.MockedImageStore{ + GetIndexContentFn: func(repo string) ([]byte, error) { + return indexBlob, nil + }, + GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) { + return manifestBlob, nil + }, + } + + storeController = storage.StoreController{DefaultStore: mockStoreController} + olu = NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) + + _, err = olu.GetExpandedRepoInfo("rep") + So(err, ShouldBeNil) + }) + + Convey("GetImageInfo fail", t, func() { + mockStoreController := mocks.MockedImageStore{ + GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) { + return []byte{}, ErrTestError + }, + } + + storeController := storage.StoreController{DefaultStore: mockStoreController} + olu := NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) + + _, err := olu.GetImageInfo("", "") + So(err, ShouldNotBeNil) + }) + + Convey("CheckManifestSignature: notation", t, func() { + // GetReferrers - fails => checkNotarySignature returns false + mockStoreController := mocks.MockedImageStore{ + GetImageManifestFn: func(name, reference string) ([]byte, godigest.Digest, string, error) { + return []byte{}, "", "", zerr.ErrRepoNotFound + }, + GetReferrersFn: func(name string, digest godigest.Digest, mediaTypes []string) (ispec.Index, error) { + return ispec.Index{}, ErrTestError + }, + } + + storeController := storage.StoreController{DefaultStore: mockStoreController} + olu := NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) + + check := olu.CheckManifestSignature("rep", godigest.FromString("")) + So(check, ShouldBeFalse) + + // checkNotarySignature -> true + dir := t.TempDir() + + port := GetFreePort() + baseURL := GetBaseURL(port) + conf := config.New() + conf.HTTP.Port = port + conf.Storage.RootDirectory = dir + defaultVal := true + conf.Extensions = &extconf.ExtensionConfig{ + Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, + } + + conf.Extensions.Search.CVE = nil + + ctlr := api.NewController(conf) + + ctlrManager := NewControllerManager(ctlr) + ctlrManager.StartAndWait(port) + defer ctlrManager.StopServer() + + // push test image to repo + config, layers, manifest, err := GetImageComponents(100) + So(err, ShouldBeNil) + + layersSize1 := 0 + for _, l := range layers { + layersSize1 += len(l) + } + + repo := "repo" + tag := "1.0.1" + err = UploadImage( + Image{ + Manifest: manifest, + Config: config, + Layers: layers, + Reference: tag, + }, + baseURL, + repo, + ) + So(err, ShouldBeNil) + + olu = NewBaseOciLayoutUtils(ctlr.StoreController, log.NewLogger("debug", "")) + manifestList, err := olu.GetImageManifests(repo) + So(err, ShouldBeNil) + So(len(manifestList), ShouldEqual, 1) + + isSigned := olu.CheckManifestSignature(repo, manifestList[0].Digest) + So(isSigned, ShouldBeFalse) + + err = SignImageUsingNotary(fmt.Sprintf("%s:%s", repo, tag), port) + So(err, ShouldBeNil) + + isSigned = olu.CheckManifestSignature(repo, manifestList[0].Digest) + So(isSigned, ShouldBeTrue) + }) +} + +func TestExtractImageDetails(t *testing.T) { + Convey("extractImageDetails good workflow", t, func() { + dir := t.TempDir() + testLogger := log.NewLogger("debug", "") + imageStore := local.NewImageStore(dir, false, 0, false, false, + testLogger, monitoring.NewMetricsServer(false, testLogger), nil, nil) + + storeController := storage.StoreController{ + DefaultStore: imageStore, + } + + num := 10 + config, layers, manifest, err := GetImageComponents(num) + So(err, ShouldBeNil) + + err = WriteImageToFileSystem( + Image{ + Manifest: manifest, + Layers: layers, + Config: config, + Reference: "latest", + }, "zot-test", storeController, + ) + So(err, ShouldBeNil) + + configBlob, err := json.Marshal(config) + So(err, ShouldBeNil) + configDigest := godigest.FromBytes(configBlob) + + olu := NewBaseOciLayoutUtils(storeController, testLogger) + resDigest, resManifest, resIspecImage, resErr := olu.ExtractImageDetails("zot-test", "latest", testLogger) + So(string(resDigest), ShouldContainSubstring, "sha256:c52f15d2d4") + So(resManifest.Config.Digest.String(), ShouldContainSubstring, configDigest.Encoded()) + + So(resIspecImage.Architecture, ShouldContainSubstring, "amd64") + So(resErr, ShouldBeNil) + }) + + Convey("extractImageDetails bad ispec.ImageManifest", t, func() { + dir := t.TempDir() + testLogger := log.NewLogger("debug", "") + imageStore := local.NewImageStore(dir, false, 0, false, false, + testLogger, monitoring.NewMetricsServer(false, testLogger), nil, nil) + + storeController := storage.StoreController{ + DefaultStore: imageStore, + } + + olu := NewBaseOciLayoutUtils(storeController, testLogger) + resDigest, resManifest, resIspecImage, resErr := olu.ExtractImageDetails("zot-test", + "latest", testLogger) + So(resErr, ShouldEqual, zerr.ErrRepoNotFound) + So(string(resDigest), ShouldEqual, "") + So(resManifest, ShouldBeNil) + + So(resIspecImage, ShouldBeNil) + }) + + Convey("extractImageDetails bad imageConfig", t, func() { + dir := t.TempDir() + testLogger := log.NewLogger("debug", "") + imageStore := local.NewImageStore(dir, false, 0, false, false, + testLogger, monitoring.NewMetricsServer(false, testLogger), nil, nil) + + storeController := storage.StoreController{ + DefaultStore: imageStore, + } + + num := 10 + config, layers, manifest, err := GetImageComponents(num) + So(err, ShouldBeNil) + + err = WriteImageToFileSystem( + Image{ + Manifest: manifest, + Layers: layers, + Config: config, + Reference: "latest", + }, "zot-test", storeController, + ) + So(err, ShouldBeNil) + + configBlob, err := json.Marshal(config) + So(err, ShouldBeNil) + configDigest := godigest.FromBytes(configBlob) + + err = os.Remove(path.Join(dir, "zot-test/blobs/sha256", configDigest.Encoded())) + if err != nil { + panic(err) + } + + olu := NewBaseOciLayoutUtils(storeController, testLogger) + resDigest, resManifest, resIspecImage, resErr := olu.ExtractImageDetails("zot-test", "latest", testLogger) + So(resErr, ShouldEqual, zerr.ErrBlobNotFound) + So(string(resDigest), ShouldEqual, "") + So(resManifest, ShouldBeNil) + So(resIspecImage, ShouldBeNil) + }) +}