From 9ca5fa10297973c8479fbd60cbdfa9f91a621308 Mon Sep 17 00:00:00 2001 From: Catalin Hofnar Date: Fri, 29 Jul 2022 18:33:34 +0300 Subject: [PATCH] Implement RepoListWithNewestImage to return [RepoSummary] Removed access by index in repoListWithNewestImage Signed-off-by: Catalin Hofnar --- pkg/extensions/search/common/common_test.go | 170 ++++++++++++++++-- .../search/gql_generated/generated.go | 146 ++++++++++----- pkg/extensions/search/resolver.go | 149 +++++++++------ pkg/extensions/search/schema.graphql | 2 +- pkg/extensions/search/schema.resolvers.go | 26 +-- 5 files changed, 361 insertions(+), 132 deletions(-) diff --git a/pkg/extensions/search/common/common_test.go b/pkg/extensions/search/common/common_test.go index 5cf07bb3..326e36f2 100644 --- a/pkg/extensions/search/common/common_test.go +++ b/pkg/extensions/search/common/common_test.go @@ -24,6 +24,7 @@ import ( "github.com/sigstore/cosign/cmd/cosign/cli/sign" . "github.com/smartystreets/goconvey/convey" "gopkg.in/resty.v1" + zerr "zotregistry.io/zot/errors" "zotregistry.io/zot/pkg/api" "zotregistry.io/zot/pkg/api/config" "zotregistry.io/zot/pkg/api/constants" @@ -51,9 +52,9 @@ var ( subRootDir string ) -type ImgResponsWithLatestTag struct { - ImgListWithLatestTag ImgListWithLatestTag `json:"data"` - Errors []ErrorGQL `json:"errors"` +type RepoWithNewestImageResponse struct { + RepoListWithNewestImage RepoListWithNewestImage `json:"data"` + Errors []ErrorGQL `json:"errors"` } type ExpandedRepoInfoResp struct { @@ -112,8 +113,8 @@ type ExpandedRepoInfo struct { } //nolint:tagliatelle // graphQL schema -type ImgListWithLatestTag struct { - Images []ImageInfo `json:"ImageListWithLatestTag"` +type RepoListWithNewestImage struct { + Repos []RepoSummary `json:"RepoListWithNewestImage"` } type ErrorGQL struct { @@ -318,8 +319,132 @@ func TestImageFormat(t *testing.T) { }) } -func TestLatestTagSearchHTTP(t *testing.T) { - Convey("Test latest image search by timestamp", t, func() { +func TestRepoListWithNewestImage(t *testing.T) { + Convey("Test repoListWithNewestImage AddError", t, func() { + subpath := "/a" + err := testSetup(t, subpath) + if err != nil { + panic(err) + } + + err = os.RemoveAll(path.Join(rootDir, "zot-cve-test")) + if err != nil { + panic(err) + } + + err = os.RemoveAll(path.Join(rootDir, subpath)) + if err != nil { + panic(err) + } + + 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{Enable: &defaultVal}, + } + + conf.Extensions.Search.CVE = nil + + ctlr := api.NewController(conf) + + go func() { + // this blocks + if err := ctlr.Run(context.Background()); err != nil { + return + } + }() + + // wait till ready + for { + _, err := resty.R().Get(baseURL) + if err == nil { + break + } + time.Sleep(100 * time.Millisecond) + } + + // shut down server + defer func() { + ctx := context.Background() + _ = ctlr.Server.Shutdown(ctx) + }() + + resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + + "?query={RepoListWithNewestImage{Name%20NewestImage{Tag}}}") + So(resp, ShouldNotBeNil) + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, 200) + + err = os.Remove(path.Join(rootDir, + "zot-test/blobs/sha256/2bacca16b9df395fc855c14ccf50b12b58d35d468b8e7f25758aff90f89bf396")) + if err != nil { + panic(err) + } + + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + + "?query={RepoListWithNewestImage{Name%20NewestImage{Tag}}}") + So(resp, ShouldNotBeNil) + So(err, ShouldBeNil) + errmsg := fmt.Sprint(zerr.ErrBlobNotFound) + body := string(resp.Body()) + So(body, ShouldContainSubstring, errmsg) + So(resp.StatusCode(), ShouldEqual, 200) + + err = CopyFiles("../../../../test/data/zot-test", path.Join(rootDir, "zot-test")) + if err != nil { + panic(err) + } + + err = os.Remove(path.Join(rootDir, + "zot-test/blobs/sha256/adf3bb6cc81f8bd6a9d5233be5f0c1a4f1e3ed1cf5bbdfad7708cc8d4099b741")) + if err != nil { + panic(err) + } + + err = os.Remove(path.Join(rootDir, + "zot-test/blobs/sha256/2d473b07cdd5f0912cd6f1a703352c82b512407db6b05b43f2553732b55df3bc")) + if err != nil { + panic(err) + } + + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + + "?query={RepoListWithNewestImage{Name%20NewestImage{Tag}}}") + So(resp, ShouldNotBeNil) + So(err, ShouldBeNil) + errmsg = fmt.Sprint(zerr.ErrBlobNotFound) + body = string(resp.Body()) + So(body, ShouldContainSubstring, errmsg) + So(resp.StatusCode(), ShouldEqual, 200) + + err = CopyFiles("../../../../test/data/zot-test", path.Join(rootDir, "zot-test")) + if err != nil { + panic(err) + } + + err = os.Remove(path.Join(rootDir, "zot-test/index.json")) + if err != nil { + panic(err) + } + //nolint: lll + manifestNoAnnotations := "{\"schemaVersion\":2,\"manifests\":[{\"mediaType\":\"application/vnd.oci.image.manifest.v1+json\",\"digest\":\"sha256:2bacca16b9df395fc855c14ccf50b12b58d35d468b8e7f25758aff90f89bf396\",\"size\":350}]}" + err = os.WriteFile(path.Join(rootDir, "zot-test/index.json"), []byte(manifestNoAnnotations), 0o600) + if err != nil { + panic(err) + } + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + + "?query={RepoListWithNewestImage{Name%20NewestImage{Tag}}}") + So(resp, ShouldNotBeNil) + So(err, ShouldBeNil) + body = string(resp.Body()) + So(body, ShouldContainSubstring, "reference not found for this manifest") + So(resp.StatusCode(), ShouldEqual, 200) + }) + + Convey("Test repoListWithNewestImage by tag with HTTP", t, func() { subpath := "/a" err := testSetup(t, subpath) if err != nil { @@ -373,20 +498,22 @@ func TestLatestTagSearchHTTP(t *testing.T) { So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 422) - resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){RepoName%20Tag}}") + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + + "?query={RepoListWithNewestImage{Name%20NewestImage{Tag}}}") So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) - var responseStruct ImgResponsWithLatestTag + var responseStruct RepoWithNewestImageResponse err = json.Unmarshal(resp.Body(), &responseStruct) So(err, ShouldBeNil) - So(len(responseStruct.ImgListWithLatestTag.Images), ShouldEqual, 4) + So(len(responseStruct.RepoListWithNewestImage.Repos), ShouldEqual, 4) - images := responseStruct.ImgListWithLatestTag.Images - So(images[0].Tag, ShouldEqual, "0.0.1") + images := responseStruct.RepoListWithNewestImage.Repos + So(images[0].NewestImage.Tag, ShouldEqual, "0.0.1") - resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){RepoName%20Tag}}") + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + + "?query={RepoListWithNewestImage{Name%20NewestImage{Tag}}}") So(resp, ShouldNotBeNil) So(err, ShouldBeNil) @@ -395,14 +522,15 @@ func TestLatestTagSearchHTTP(t *testing.T) { panic(err) } - resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){RepoName%20Tag}}") + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + + "?query={RepoListWithNewestImage{Name%20NewestImage{Tag}}}") So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) err = json.Unmarshal(resp.Body(), &responseStruct) So(err, ShouldBeNil) - So(len(responseStruct.ImgListWithLatestTag.Images), ShouldEqual, 0) + So(responseStruct.Errors, ShouldNotBeNil) err = os.Chmod(rootDir, 0o755) if err != nil { @@ -419,7 +547,8 @@ func TestLatestTagSearchHTTP(t *testing.T) { panic(err) } - resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){RepoName%20Tag}}") + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + + "?query={RepoListWithNewestImage{Name%20NewestImage{Tag}}}") So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -430,7 +559,8 @@ func TestLatestTagSearchHTTP(t *testing.T) { panic(err) } - resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){RepoName%20Tag}}") + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + + "?query={RepoListWithNewestImage{Name%20NewestImage{Tag}}}") So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -440,7 +570,8 @@ func TestLatestTagSearchHTTP(t *testing.T) { panic(err) } - resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){RepoName%20Tag}}") + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + + "?query={RepoListWithNewestImage{Name%20NewestImage{Tag}}}") So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -451,7 +582,8 @@ func TestLatestTagSearchHTTP(t *testing.T) { panic(err) } - resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){RepoName%20Tag}}") + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + + "?query={RepoListWithNewestImage{Name%20NewestImage{Tag}}}") So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) diff --git a/pkg/extensions/search/gql_generated/generated.go b/pkg/extensions/search/gql_generated/generated.go index 2b7d4d1b..59fa49df 100644 --- a/pkg/extensions/search/gql_generated/generated.go +++ b/pkg/extensions/search/gql_generated/generated.go @@ -98,14 +98,14 @@ type ComplexityRoot struct { } Query struct { - CVEListForImage func(childComplexity int, image string) int - ExpandedRepoInfo func(childComplexity int, repo string) int - GlobalSearch func(childComplexity int, query string) int - ImageList func(childComplexity int, repo string) int - ImageListForCve func(childComplexity int, id string) int - ImageListForDigest func(childComplexity int, id string) int - ImageListWithCVEFixed func(childComplexity int, id string, image string) int - ImageListWithLatestTag func(childComplexity int) int + CVEListForImage func(childComplexity int, image string) int + ExpandedRepoInfo func(childComplexity int, repo string) int + GlobalSearch func(childComplexity int, query string) int + ImageList func(childComplexity int, repo string) int + ImageListForCve func(childComplexity int, id string) int + ImageListForDigest func(childComplexity int, id string) int + ImageListWithCVEFixed func(childComplexity int, id string, image string) int + RepoListWithNewestImage func(childComplexity int) int } RepoInfo struct { @@ -132,7 +132,7 @@ type QueryResolver interface { ImageListForCve(ctx context.Context, id string) ([]*ImageSummary, error) ImageListWithCVEFixed(ctx context.Context, id string, image string) ([]*ImageSummary, error) ImageListForDigest(ctx context.Context, id string) ([]*ImageSummary, error) - ImageListWithLatestTag(ctx context.Context) ([]*ImageSummary, error) + RepoListWithNewestImage(ctx context.Context) ([]*RepoSummary, error) ImageList(ctx context.Context, repo string) ([]*ImageSummary, error) ExpandedRepoInfo(ctx context.Context, repo string) (*RepoInfo, error) GlobalSearch(ctx context.Context, query string) (*GlobalSearchResult, error) @@ -468,12 +468,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.ImageListWithCVEFixed(childComplexity, args["id"].(string), args["image"].(string)), true - case "Query.ImageListWithLatestTag": - if e.complexity.Query.ImageListWithLatestTag == nil { + case "Query.RepoListWithNewestImage": + if e.complexity.Query.RepoListWithNewestImage == nil { break } - return e.complexity.Query.ImageListWithLatestTag(childComplexity), true + return e.complexity.Query.RepoListWithNewestImage(childComplexity), true case "RepoInfo.Images": if e.complexity.RepoInfo.Images == nil { @@ -697,7 +697,7 @@ type Query { ImageListForCVE(id: String!): [ImageSummary!] ImageListWithCVEFixed(id: String!, image: String!): [ImageSummary!] ImageListForDigest(id: String!): [ImageSummary!] - ImageListWithLatestTag: [ImageSummary!] + RepoListWithNewestImage: [RepoSummary!]! # Newest based on created timestamp ImageList(repo: String!): [ImageSummary!] ExpandedRepoInfo(repo: String!): RepoInfo! GlobalSearch(query: String!): GlobalSearchResult! @@ -2639,8 +2639,8 @@ func (ec *executionContext) fieldContext_Query_ImageListForDigest(ctx context.Co return fc, nil } -func (ec *executionContext) _Query_ImageListWithLatestTag(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_Query_ImageListWithLatestTag(ctx, field) +func (ec *executionContext) _Query_RepoListWithNewestImage(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_RepoListWithNewestImage(ctx, field) if err != nil { return graphql.Null } @@ -2653,21 +2653,24 @@ func (ec *executionContext) _Query_ImageListWithLatestTag(ctx context.Context, f }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().ImageListWithLatestTag(rctx) + return ec.resolvers.Query().RepoListWithNewestImage(rctx) }) if err != nil { ec.Error(ctx, err) return graphql.Null } if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } return graphql.Null } - res := resTmp.([]*ImageSummary) + res := resTmp.([]*RepoSummary) fc.Result = res - return ec.marshalOImageSummary2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageSummaryᚄ(ctx, field.Selections, res) + return ec.marshalNRepoSummary2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐRepoSummaryᚄ(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_Query_ImageListWithLatestTag(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Query_RepoListWithNewestImage(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "Query", Field: field, @@ -2675,38 +2678,28 @@ func (ec *executionContext) fieldContext_Query_ImageListWithLatestTag(ctx contex IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { - case "RepoName": - return ec.fieldContext_ImageSummary_RepoName(ctx, field) - case "Tag": - return ec.fieldContext_ImageSummary_Tag(ctx, field) - case "Digest": - return ec.fieldContext_ImageSummary_Digest(ctx, field) - case "ConfigDigest": - return ec.fieldContext_ImageSummary_ConfigDigest(ctx, field) + case "Name": + return ec.fieldContext_RepoSummary_Name(ctx, field) case "LastUpdated": - return ec.fieldContext_ImageSummary_LastUpdated(ctx, field) - case "IsSigned": - return ec.fieldContext_ImageSummary_IsSigned(ctx, field) + return ec.fieldContext_RepoSummary_LastUpdated(ctx, field) case "Size": - return ec.fieldContext_ImageSummary_Size(ctx, field) - case "Platform": - return ec.fieldContext_ImageSummary_Platform(ctx, field) - case "Vendor": - return ec.fieldContext_ImageSummary_Vendor(ctx, field) + return ec.fieldContext_RepoSummary_Size(ctx, field) + case "Platforms": + return ec.fieldContext_RepoSummary_Platforms(ctx, field) + case "Vendors": + return ec.fieldContext_RepoSummary_Vendors(ctx, field) case "Score": - return ec.fieldContext_ImageSummary_Score(ctx, field) + return ec.fieldContext_RepoSummary_Score(ctx, field) + case "NewestImage": + return ec.fieldContext_RepoSummary_NewestImage(ctx, field) case "DownloadCount": - return ec.fieldContext_ImageSummary_DownloadCount(ctx, field) - case "Layers": - return ec.fieldContext_ImageSummary_Layers(ctx, field) - case "Description": - return ec.fieldContext_ImageSummary_Description(ctx, field) - case "Licenses": - return ec.fieldContext_ImageSummary_Licenses(ctx, field) - case "Labels": - return ec.fieldContext_ImageSummary_Labels(ctx, field) + return ec.fieldContext_RepoSummary_DownloadCount(ctx, field) + case "StarCount": + return ec.fieldContext_RepoSummary_StarCount(ctx, field) + case "IsBookmarked": + return ec.fieldContext_RepoSummary_IsBookmarked(ctx, field) } - return nil, fmt.Errorf("no field named %q was found under type ImageSummary", field.Name) + return nil, fmt.Errorf("no field named %q was found under type RepoSummary", field.Name) }, } return fc, nil @@ -5795,7 +5788,7 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr out.Concurrently(i, func() graphql.Marshaler { return rrm(innerCtx) }) - case "ImageListWithLatestTag": + case "RepoListWithNewestImage": field := field innerFunc := func(ctx context.Context) (res graphql.Marshaler) { @@ -5804,7 +5797,10 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr ec.Error(ctx, ec.Recover(ctx, r)) } }() - res = ec._Query_ImageListWithLatestTag(ctx, field) + res = ec._Query_RepoListWithNewestImage(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } return res } @@ -6379,6 +6375,60 @@ func (ec *executionContext) marshalNRepoInfo2ᚖzotregistryᚗioᚋzotᚋpkgᚋe return ec._RepoInfo(ctx, sel, v) } +func (ec *executionContext) marshalNRepoSummary2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐRepoSummaryᚄ(ctx context.Context, sel ast.SelectionSet, v []*RepoSummary) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNRepoSummary2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐRepoSummary(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalNRepoSummary2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐRepoSummary(ctx context.Context, sel ast.SelectionSet, v *RepoSummary) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._RepoSummary(ctx, sel, v) +} + func (ec *executionContext) unmarshalNString2string(ctx context.Context, v interface{}) (string, error) { res, err := graphql.UnmarshalString(v) return res, graphql.ErrorOnPath(ctx, err) diff --git a/pkg/extensions/search/resolver.go b/pkg/extensions/search/resolver.go index 157f2ebf..b5df3f1b 100644 --- a/pkg/extensions/search/resolver.go +++ b/pkg/extensions/search/resolver.go @@ -11,10 +11,12 @@ import ( "strconv" "strings" - glob "github.com/bmatcuk/doublestar/v4" - v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/99designs/gqlgen/graphql" + glob "github.com/bmatcuk/doublestar/v4" // nolint:gci + v1 "github.com/google/go-containerregistry/pkg/v1" // nolint:gci godigest "github.com/opencontainers/go-digest" ispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/vektah/gqlparser/v2/gqlerror" "zotregistry.io/zot/pkg/extensions/search/common" cveinfo "zotregistry.io/zot/pkg/extensions/search/cve" digestinfo "zotregistry.io/zot/pkg/extensions/search/digest" @@ -114,80 +116,125 @@ func (r *queryResolver) getImageListForDigest(repoList []string, digest string) return imgResultForDigest, errResult } -func (r *queryResolver) getImageListWithLatestTag(store storage.ImageStore) ([]*gql_generated.ImageSummary, error) { - results := make([]*gql_generated.ImageSummary, 0) +// nolint:lll +func (r *queryResolver) repoListWithNewestImage(ctx context.Context, store storage.ImageStore) ([]*gql_generated.RepoSummary, error) { + repos := []*gql_generated.RepoSummary{} + olu := common.NewBaseOciLayoutUtils(r.storeController, r.log) - repoList, err := store.GetRepositories() + repoNames, err := store.GetRepositories() if err != nil { - r.log.Error().Err(err).Msg("extension api: error extracting repositories list") - - return results, err + return nil, err } - if len(repoList) == 0 { - r.log.Info().Msg("no repositories found") - } - - layoutUtils := common.NewBaseOciLayoutUtils(r.storeController, r.log) - - for _, repo := range repoList { - tagsInfo, err := layoutUtils.GetImageTagsWithTimestamp(repo) + for _, repo := range repoNames { + lastUpdatedTag, err := olu.GetRepoLastUpdated(repo) if err != nil { - r.log.Error().Err(err).Msg("extension api: error getting tag timestamp info") - - return results, err - } - - if len(tagsInfo) == 0 { - r.log.Info().Str("no tagsinfo found for repo", repo).Msg(" continuing traversing") + graphql.AddError(ctx, err) continue } - latestTag := common.GetLatestTag(tagsInfo) + repoSize := int64(0) + repoBlob2Size := make(map[string]int64, 10) + tagsInfo, _ := olu.GetImageTagsWithTimestamp(repo) - digest := godigest.Digest(latestTag.Digest) - - manifest, err := layoutUtils.GetImageBlobManifest(repo, digest) + manifests, err := olu.GetImageManifests(repo) if err != nil { - r.log.Error().Err(err).Msg("extension api: error reading manifest") + graphql.AddError(ctx, err) - return results, err + continue } - size := strconv.FormatInt(manifest.Config.Size, 10) + repoPlatforms := make([]*gql_generated.OsArch, 0, len(tagsInfo)) + repoVendors := make([]*string, 0, len(manifests)) + repoName := repo - name := repo + var lastUpdatedImageSummary gql_generated.ImageSummary - imageConfig, err := layoutUtils.GetImageInfo(repo, manifest.Config.Digest) - if err != nil { - r.log.Error().Err(err).Msg("extension api: error reading image config") + var brokenManifest bool - return results, err + for i, manifest := range manifests { + imageLayersSize := int64(0) + manifestSize := olu.GetImageManifestSize(repo, manifests[i].Digest) + + imageBlobManifest, _ := olu.GetImageBlobManifest(repo, manifests[i].Digest) + + configSize := imageBlobManifest.Config.Size + repoBlob2Size[manifests[i].Digest.String()] = manifestSize + repoBlob2Size[imageBlobManifest.Config.Digest.Hex] = configSize + + for _, layer := range imageBlobManifest.Layers { + repoBlob2Size[layer.Digest.String()] = layer.Size + imageLayersSize += layer.Size + } + + imageSize := imageLayersSize + manifestSize + configSize + + imageConfigInfo, _ := olu.GetImageConfigInfo(repo, manifests[i].Digest) + + os, arch := olu.GetImagePlatform(imageConfigInfo) + osArch := &gql_generated.OsArch{ + Os: &os, + Arch: &arch, + } + repoPlatforms = append(repoPlatforms, osArch) + + vendor := olu.GetImageVendor(imageConfigInfo) + repoVendors = append(repoVendors, &vendor) + + manifestTag, ok := manifest.Annotations[ispec.AnnotationRefName] + if !ok { + graphql.AddError(ctx, gqlerror.Errorf("reference not found for this manifest")) + brokenManifest = true + + break + } + + tag := manifestTag + size := strconv.Itoa(int(imageSize)) + isSigned := olu.CheckManifestSignature(repo, manifests[i].Digest) + lastUpdated := olu.GetImageLastUpdated(imageConfigInfo) + score := 0 + + imageSummary := gql_generated.ImageSummary{ + RepoName: &repoName, + Tag: &tag, + LastUpdated: &lastUpdated, + IsSigned: &isSigned, + Size: &size, + Platform: osArch, + Vendor: &vendor, + Score: &score, + } + + if tagsInfo[i].Digest == lastUpdatedTag.Digest { + lastUpdatedImageSummary = imageSummary + } } - labels := imageConfig.Config.Labels + if brokenManifest { + continue + } - // Read Description - desc := common.GetDescription(labels) + for blob := range repoBlob2Size { + repoSize += repoBlob2Size[blob] + } - // Read licenses - license := common.GetLicense(labels) + repoSizeStr := strconv.FormatInt(repoSize, 10) + index := 0 - // Read vendor - vendor := common.GetVendor(labels) - - // Read categories - categories := common.GetCategories(labels) - - results = append(results, &gql_generated.ImageSummary{ - RepoName: &name, Tag: &latestTag.Name, - Description: &desc, Licenses: &license, Vendor: &vendor, - Labels: &categories, Size: &size, LastUpdated: &latestTag.Timestamp, + repos = append(repos, &gql_generated.RepoSummary{ + Name: &repoName, + LastUpdated: &lastUpdatedTag.Timestamp, + Size: &repoSizeStr, + Platforms: repoPlatforms, + Vendors: repoVendors, + Score: &index, + NewestImage: &lastUpdatedImageSummary, }) } - return results, nil + return repos, nil } func cleanQuerry(query string) string { diff --git a/pkg/extensions/search/schema.graphql b/pkg/extensions/search/schema.graphql index 91ed1c71..562a4771 100644 --- a/pkg/extensions/search/schema.graphql +++ b/pkg/extensions/search/schema.graphql @@ -84,7 +84,7 @@ type Query { ImageListForCVE(id: String!): [ImageSummary!] ImageListWithCVEFixed(id: String!, image: String!): [ImageSummary!] ImageListForDigest(id: String!): [ImageSummary!] - ImageListWithLatestTag: [ImageSummary!] + RepoListWithNewestImage: [RepoSummary!]! # Newest based on created timestamp ImageList(repo: String!): [ImageSummary!] ExpandedRepoInfo(repo: String!): RepoInfo! GlobalSearch(query: String!): GlobalSearchResult! diff --git a/pkg/extensions/search/schema.resolvers.go b/pkg/extensions/search/schema.resolvers.go index 1c641a1f..216e08be 100644 --- a/pkg/extensions/search/schema.resolvers.go +++ b/pkg/extensions/search/schema.resolvers.go @@ -287,41 +287,41 @@ func (r *queryResolver) ImageListForDigest(ctx context.Context, id string) ([]*g return imgResultForDigest, nil } -// ImageListWithLatestTag is the resolver for the ImageListWithLatestTag field. -func (r *queryResolver) ImageListWithLatestTag(ctx context.Context) ([]*gql_generated.ImageSummary, error) { +// RepoListWithNewestImage is the resolver for the RepoListWithNewestImage field. +func (r *queryResolver) RepoListWithNewestImage(ctx context.Context) ([]*gql_generated.RepoSummary, error) { r.log.Info().Msg("extension api: finding image list") - imageList := make([]*gql_generated.ImageSummary, 0) + repoList := make([]*gql_generated.RepoSummary, 0) defaultStore := r.storeController.DefaultStore - dsImageList, err := r.getImageListWithLatestTag(defaultStore) + dsRepoList, err := r.repoListWithNewestImage(ctx, defaultStore) if err != nil { r.log.Error().Err(err).Msg("extension api: error extracting default store image list") - return imageList, err + return repoList, err } - if len(dsImageList) != 0 { - imageList = append(imageList, dsImageList...) + if len(dsRepoList) != 0 { + repoList = append(repoList, dsRepoList...) } subStore := r.storeController.SubStore for _, store := range subStore { - ssImageList, err := r.getImageListWithLatestTag(store) + ssRepoList, err := r.repoListWithNewestImage(ctx, store) if err != nil { - r.log.Error().Err(err).Msg("extension api: error extracting default store image list") + r.log.Error().Err(err).Msg("extension api: error extracting substore image list") - return imageList, err + return repoList, err } - if len(ssImageList) != 0 { - imageList = append(imageList, ssImageList...) + if len(ssRepoList) != 0 { + repoList = append(repoList, ssRepoList...) } } - return imageList, nil + return repoList, nil } // ImageList is the resolver for the ImageList field.