diff --git a/pkg/extensions/search/common/common_test.go b/pkg/extensions/search/common/common_test.go index f35a0e18..aede184e 100644 --- a/pkg/extensions/search/common/common_test.go +++ b/pkg/extensions/search/common/common_test.go @@ -62,6 +62,16 @@ type RepoWithNewestImageResponse struct { Errors []ErrorGQL `json:"errors"` } +type DerivedImageListResponse struct { + DerivedImageList DerivedImageList `json:"data"` + Errors []ErrorGQL `json:"errors"` +} + +type BaseImageListResponse struct { + BaseImageList BaseImageList `json:"data"` + Errors []ErrorGQL `json:"errors"` +} + type ImageListResponse struct { ImageList ImageList `json:"data"` Errors []ErrorGQL `json:"errors"` @@ -71,6 +81,13 @@ type ImageList struct { SummaryList []common.ImageSummary `json:"imageList"` } +type DerivedImageList struct { + DerivedList []common.ImageSummary `json:"derivedImageList"` +} +type BaseImageList struct { + BaseList []common.ImageSummary `json:"baseImageList"` +} + type ExpandedRepoInfoResp struct { ExpandedRepoInfo ExpandedRepoInfo `json:"data"` Errors []ErrorGQL `json:"errors"` @@ -1454,7 +1471,7 @@ func TestDerivedImageList(t *testing.T) { query := ` { - DerivedImageList(image:"test-repo"){ + DerivedImageList(image:"test-repo:latest"){ RepoName, Tag, Digest, @@ -1477,7 +1494,7 @@ func TestDerivedImageList(t *testing.T) { Convey("Inexistent repository", t, func() { query := ` { - DerivedImageList(image:"inexistent-image"){ + DerivedImageList(image:"inexistent-image:latest"){ RepoName, Tag, Digest, @@ -1493,13 +1510,43 @@ func TestDerivedImageList(t *testing.T) { So(err, ShouldBeNil) }) + Convey("Invalid query, no reference provided", t, func() { + query := ` + { + DerivedImageList(image:"inexistent-image"){ + RepoName, + Tag, + Digest, + ConfigDigest, + LastUpdated, + IsSigned, + Size + } + }` + + responseStruct := &DerivedImageListResponse{} + contains := false + resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) + So(err, ShouldBeNil) + + err = json.Unmarshal(resp.Body(), responseStruct) + So(err, ShouldBeNil) + for _, err := range responseStruct.Errors { + result := strings.Contains(err.Message, "no reference provided") + if result { + contains = result + } + } + So(contains, ShouldBeTrue) + }) + Convey("Failed to get manifest", t, func() { err := os.Mkdir(path.Join(rootDir, "fail-image"), 0o000) So(err, ShouldBeNil) query := ` { - DerivedImageList(image:"fail-image"){ + DerivedImageList(image:"fail-image:latest"){ RepoName, Tag, Digest, @@ -1560,7 +1607,7 @@ func TestDerivedImageListNoRepos(t *testing.T) { query := ` { - DerivedImageList(image:"test-image"){ + DerivedImageList(image:"test-image:latest"){ RepoName, Tag, Digest, @@ -1968,7 +2015,7 @@ func TestBaseImageList(t *testing.T) { query := ` { - BaseImageList(image:"test-repo"){ + BaseImageList(image:"test-repo:latest"){ RepoName, Tag, Digest, @@ -1986,7 +2033,7 @@ func TestBaseImageList(t *testing.T) { So(strings.Contains(string(resp.Body()), "less-layers-false"), ShouldBeFalse) So(strings.Contains(string(resp.Body()), "more-layers"), ShouldBeFalse) So(strings.Contains(string(resp.Body()), "diff-layers"), ShouldBeFalse) - So(strings.Contains(string(resp.Body()), "test-repo"), ShouldBeTrue) //nolint:goconst // should not list given image + So(strings.Contains(string(resp.Body()), "test-repo"), ShouldBeFalse) //nolint:goconst // should not list given image So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) }) @@ -1994,7 +2041,7 @@ func TestBaseImageList(t *testing.T) { Convey("Nonexistent repository", t, func() { query := ` { - BaseImageList(image:"nonexistent-image"){ + BaseImageList(image:"nonexistent-image:latest"){ RepoName, Tag, Digest, @@ -2010,13 +2057,43 @@ func TestBaseImageList(t *testing.T) { So(err, ShouldBeNil) }) + Convey("Invalid query, no reference provided", t, func() { + query := ` + { + BaseImageList(image:"nonexistent-image"){ + RepoName, + Tag, + Digest, + ConfigDigest, + LastUpdated, + IsSigned, + Size + } + }` + + responseStruct := &BaseImageListResponse{} + contains := false + resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) + So(err, ShouldBeNil) + + err = json.Unmarshal(resp.Body(), responseStruct) + So(err, ShouldBeNil) + for _, err := range responseStruct.Errors { + result := strings.Contains(err.Message, "no reference provided") + if result { + contains = result + } + } + So(contains, ShouldBeTrue) + }) + Convey("Failed to get manifest", t, func() { err := os.Mkdir(path.Join(rootDir, "fail-image"), 0o000) So(err, ShouldBeNil) query := ` { - BaseImageList(image:"fail-image"){ + BaseImageList(image:"fail-image:latest"){ RepoName, Tag, Digest, @@ -2077,7 +2154,7 @@ func TestBaseImageListNoRepos(t *testing.T) { query := ` { - BaseImageList(image:"test-image"){ + BaseImageList(image:"test-image:latest"){ RepoName, Tag, Digest, @@ -3216,6 +3293,20 @@ func TestImageSummary(t *testing.T) { } }` + noTagQuery := ` + { + Image(image:"%s"){ + RepoName, + Tag, + Digest, + ConfigDigest, + LastUpdated, + IsSigned, + Size + Layers { Digest Size } + } + }` + gqlEndpoint := fmt.Sprintf("%s%s?query=", baseURL, graphqlQueryPrefix) config, layers, manifest, err := GetImageComponents(100) So(err, ShouldBeNil) @@ -3252,6 +3343,27 @@ func TestImageSummary(t *testing.T) { resp *resty.Response ) + t.Log("starting test to retrieve image without reference") + strQuery = fmt.Sprintf(noTagQuery, repoName) + targetURL = fmt.Sprintf("%s%s", gqlEndpoint, url.QueryEscape(strQuery)) + contains := false + + resp, err = resty.R().Get(targetURL) + So(resp, ShouldNotBeNil) + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, 200) + So(resp.Body(), ShouldNotBeNil) + + err = json.Unmarshal(resp.Body(), &imgSummaryResponse) + So(err, ShouldBeNil) + for _, err := range imgSummaryResponse.Errors { + result := strings.Contains(err.Message, "no reference provided") + if result { + contains = result + } + } + So(contains, ShouldBeTrue) + t.Log("starting Test retrieve image based on image identifier") // gql is parametrized with the repo. strQuery = fmt.Sprintf(gqlQuery, repoName, tagTarget) diff --git a/pkg/extensions/search/cve/cve_test.go b/pkg/extensions/search/cve/cve_test.go index c5423904..96e5eaff 100644 --- a/pkg/extensions/search/cve/cve_test.go +++ b/pkg/extensions/search/cve/cve_test.go @@ -12,6 +12,7 @@ import ( "net/http" "os" "path" + "strings" "testing" "time" @@ -51,7 +52,8 @@ const ( ) type CveResult struct { - ImgList ImgList `json:"data"` + ImgList ImgList `json:"data"` + Errors []ErrorGQL `json:"errors"` } //nolint:tagliatelle // graphQL schema @@ -69,6 +71,11 @@ type ImgList struct { CVEResultForImage CVEResultForImage `json:"CVEListForImage"` } +type ErrorGQL struct { + Message string `json:"message"` + Path []string `json:"path"` +} + //nolint:tagliatelle // graphQL schema type CVEResultForImage struct { Tag string `json:"Tag"` @@ -490,11 +497,23 @@ func TestCVESearch(t *testing.T) { So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 422) + var cveResult CveResult + contains := false + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-test\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}") + err = json.Unmarshal(resp.Body(), &cveResult) + So(err, ShouldBeNil) + for _, err := range cveResult.Errors { + result := strings.Contains(err.Message, "no reference provided") + if result { + contains = result + } + } + So(contains, ShouldBeTrue) + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-test:0.0.1\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - var cveResult CveResult err = json.Unmarshal(resp.Body(), &cveResult) So(err, ShouldBeNil) So(len(cveResult.ImgList.CVEResultForImage.CVEList), ShouldNotBeZeroValue) diff --git a/pkg/extensions/search/schema.resolvers.go b/pkg/extensions/search/schema.resolvers.go index b033881d..29c0bfb2 100644 --- a/pkg/extensions/search/schema.resolvers.go +++ b/pkg/extensions/search/schema.resolvers.go @@ -8,6 +8,7 @@ import ( "context" "fmt" + "github.com/vektah/gqlparser/v2/gqlerror" "zotregistry.io/zot/pkg/extensions/search/common" "zotregistry.io/zot/pkg/extensions/search/gql_generated" ) @@ -21,6 +22,10 @@ func (r *queryResolver) CVEListForImage(ctx context.Context, image string) (*gql _, copyImgTag := common.GetImageDirAndTag(image) + if copyImgTag == "" { + return &gql_generated.CVEResultForImage{}, gqlerror.Errorf("no reference provided") + } + cveids := []*gql_generated.Cve{} for id, cveDetail := range cveidMap { @@ -421,6 +426,10 @@ func (r *queryResolver) DerivedImageList(ctx context.Context, image string) ([]* imageDir, imageTag := common.GetImageDirAndTag(image) + if imageTag == "" { + return []*gql_generated.ImageSummary{}, gqlerror.Errorf("no reference provided") + } + imageManifest, _, err := layoutUtils.GetImageManifest(imageDir, imageTag) if err != nil { r.log.Info().Str("image", image).Msg("image not found") @@ -489,6 +498,10 @@ func (r *queryResolver) BaseImageList(ctx context.Context, image string) ([]*gql imageDir, imageTag := common.GetImageDirAndTag(image) + if imageTag == "" { + return []*gql_generated.ImageSummary{}, gqlerror.Errorf("no reference provided") + } + imageManifest, _, err := layoutUtils.GetImageManifest(imageDir, imageTag) if err != nil { r.log.Info().Str("image", image).Msg("image not found") @@ -552,6 +565,10 @@ func (r *queryResolver) Image(ctx context.Context, image string) (*gql_generated repo, tag := common.GetImageDirAndTag(image) layoutUtils := common.NewBaseOciLayoutUtils(r.storeController, r.log) + if tag == "" { + return &gql_generated.ImageSummary{}, gqlerror.Errorf("no reference provided") + } + digest, manifest, imageConfig, err := extractImageDetails(ctx, layoutUtils, repo, tag, r.log) if err != nil { r.log.Error().Err(err).Msg("unable to get image details")