From 0c70ae8a4ef77e3cc45a80b82f8c135abe6989cb Mon Sep 17 00:00:00 2001 From: Alex Stan Date: Fri, 29 Jul 2022 17:51:10 +0300 Subject: [PATCH] RepoInfo structure now includes new field representing RepoSummary ExpandedRepoInfo currently returns RepoInfo that is a list of Manifests. To comply with the newest UI requirements, a new field called Summary, referring to RepoSummary structure, was added. Signed-off-by: Alex Stan --- pkg/extensions/search/common/common_test.go | 17 ++++- pkg/extensions/search/common/oci_layout.go | 76 +++++++++++++++++++ .../search/gql_generated/generated.go | 72 ++++++++++++++++++ .../search/gql_generated/models_gen.go | 1 + pkg/extensions/search/schema.graphql | 1 + pkg/extensions/search/schema.resolvers.go | 26 +++++++ 6 files changed, 192 insertions(+), 1 deletion(-) diff --git a/pkg/extensions/search/common/common_test.go b/pkg/extensions/search/common/common_test.go index e26d865d..d1f3c194 100644 --- a/pkg/extensions/search/common/common_test.go +++ b/pkg/extensions/search/common/common_test.go @@ -517,7 +517,7 @@ func TestExpandedRepoInfo(t *testing.T) { So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 422) - query := "{ExpandedRepoInfo(repo:\"zot-cve-test\"){Manifests%20{Digest%20IsSigned%20Tag%20Layers%20{Size%20Digest}}}}" + query := "{ExpandedRepoInfo(repo:\"zot-cve-test\"){Summary%20{Name%20LastUpdated%20Size%20Platforms%20{Os%20Arch}%20Vendors%20Score}}}" // nolint: lll resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + query) So(resp, ShouldNotBeNil) @@ -526,6 +526,21 @@ func TestExpandedRepoInfo(t *testing.T) { responseStruct := &ExpandedRepoInfoResp{} + err = json.Unmarshal(resp.Body(), responseStruct) + So(err, ShouldBeNil) + So(responseStruct.ExpandedRepoInfo.RepoInfo.Summary, ShouldNotBeEmpty) + So(responseStruct.ExpandedRepoInfo.RepoInfo.Summary.Name, ShouldEqual, "zot-cve-test") + So(responseStruct.ExpandedRepoInfo.RepoInfo.Summary.Score, ShouldEqual, -1) + + query = "{ExpandedRepoInfo(repo:\"zot-cve-test\"){Manifests%20{Digest%20IsSigned%20Tag%20Layers%20{Size%20Digest}}}}" + + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + query) + So(resp, ShouldNotBeNil) + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, 200) + + responseStruct = &ExpandedRepoInfoResp{} + err = json.Unmarshal(resp.Body(), responseStruct) So(err, ShouldBeNil) So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests), ShouldNotEqual, 0) diff --git a/pkg/extensions/search/common/oci_layout.go b/pkg/extensions/search/common/oci_layout.go index a9223b3c..2e11d135 100644 --- a/pkg/extensions/search/common/oci_layout.go +++ b/pkg/extensions/search/common/oci_layout.go @@ -43,6 +43,7 @@ type BaseOciLayoutUtils struct { type RepoInfo struct { Manifests []Manifest `json:"manifests"` + Summary RepoSummary } type Manifest struct { @@ -52,6 +53,20 @@ type Manifest struct { Layers []Layer `json:"layers"` } +type RepoSummary struct { + Name string `json:"name"` + LastUpdated time.Time `json:"lastUpdated"` + Size string `json:"size"` + Platforms []OsArch `json:"platforms"` + Vendors []string `json:"vendors"` + Score int `json:"score"` +} + +type OsArch struct { + Os string `json:"os"` + Arch string `json:"arch"` +} + type Layer struct { Size string `json:"size"` Digest string `json:"digest"` @@ -337,8 +352,18 @@ func (olu BaseOciLayoutUtils) GetRepoLastUpdated(repo string) (TagInfo, error) { func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(name string) (RepoInfo, error) { repo := RepoInfo{} + repoBlob2Size := make(map[string]int64, 10) + + // made up of all manifests, configs and image layers + repoSize := int64(0) + manifests := make([]Manifest, 0) + tagsInfo, err := olu.GetImageTagsWithTimestamp(name) + if err != nil { + olu.Log.Error().Err(err).Msgf("can't get tags info for repo: %s", name) + } + manifestList, err := olu.GetImageManifests(name) if err != nil { olu.Log.Error().Err(err).Msg("error getting image manifests") @@ -346,6 +371,9 @@ func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(name string) (RepoInfo, error) return RepoInfo{}, err } + repoPlatforms := make([]OsArch, 0, len(tagsInfo)) + repoVendors := make([]string, 0, len(manifestList)) + for _, man := range manifestList { manifestInfo := Manifest{} @@ -369,6 +397,30 @@ func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(name string) (RepoInfo, error) manifestInfo.IsSigned = olu.checkManifestSignature(name, man.Digest) + manifestSize := olu.GetImageManifestSize(name, man.Digest) + olu.Log.Debug().Msg(fmt.Sprintf("%v", man.Digest)) + configSize := manifest.Config.Size + + repoBlob2Size[man.Digest.String()] = manifestSize + repoBlob2Size[manifest.Config.Digest.Hex] = configSize + + imageConfigInfo, err := olu.GetImageConfigInfo(name, man.Digest) + if err != nil { + olu.Log.Error().Err(err).Msgf("can't retrieve config info for the image %s %s", name, man.Digest) + + continue + } + + vendor := olu.GetImageVendor(imageConfigInfo) + os, arch := olu.GetImagePlatform(imageConfigInfo) + osArch := OsArch{ + Os: os, + Arch: arch, + } + + repoPlatforms = append(repoPlatforms, osArch) + repoVendors = append(repoVendors, vendor) + layers := make([]Layer, 0) for _, layer := range manifest.Layers { @@ -376,6 +428,8 @@ func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(name string) (RepoInfo, error) layerInfo.Digest = layer.Digest.Hex + repoBlob2Size[layerInfo.Digest] = layer.Size + layerInfo.Size = strconv.FormatInt(layer.Size, 10) layers = append(layers, layerInfo) @@ -388,6 +442,28 @@ func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(name string) (RepoInfo, error) repo.Manifests = manifests + lastUpdate, err := olu.GetRepoLastUpdated(name) + if err != nil { + olu.Log.Error().Err(err).Msgf("can't find latest update timestamp for repo: %s", name) + } + + for blob := range repoBlob2Size { + repoSize += repoBlob2Size[blob] + } + + size := strconv.FormatInt(repoSize, 10) + + summary := RepoSummary{ + Name: name, + LastUpdated: lastUpdate.Timestamp, + Size: size, + Platforms: repoPlatforms, + Vendors: repoVendors, + Score: -1, + } + + repo.Summary = summary + return repo, nil } diff --git a/pkg/extensions/search/gql_generated/generated.go b/pkg/extensions/search/gql_generated/generated.go index 40916282..69d1c6d4 100644 --- a/pkg/extensions/search/gql_generated/generated.go +++ b/pkg/extensions/search/gql_generated/generated.go @@ -138,6 +138,7 @@ type ComplexityRoot struct { RepoInfo struct { Manifests func(childComplexity int) int + Summary func(childComplexity int) int } RepoSummary struct { @@ -583,6 +584,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.RepoInfo.Manifests(childComplexity), true + case "RepoInfo.Summary": + if e.complexity.RepoInfo.Summary == nil { + break + } + + return e.complexity.RepoInfo.Summary(childComplexity), true + case "RepoSummary.LastUpdated": if e.complexity.RepoSummary.LastUpdated == nil { break @@ -759,6 +767,7 @@ type ImageInfo { type RepoInfo { Manifests: [ManifestInfo] + Summary: RepoSummary } type ManifestInfo { @@ -3241,6 +3250,8 @@ func (ec *executionContext) fieldContext_Query_ExpandedRepoInfo(ctx context.Cont switch field.Name { case "Manifests": return ec.fieldContext_RepoInfo_Manifests(ctx, field) + case "Summary": + return ec.fieldContext_RepoInfo_Summary(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type RepoInfo", field.Name) }, @@ -3499,6 +3510,63 @@ func (ec *executionContext) fieldContext_RepoInfo_Manifests(ctx context.Context, return fc, nil } +func (ec *executionContext) _RepoInfo_Summary(ctx context.Context, field graphql.CollectedField, obj *RepoInfo) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_RepoInfo_Summary(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Summary, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*RepoSummary) + fc.Result = res + return ec.marshalORepoSummary2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐRepoSummary(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_RepoInfo_Summary(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "RepoInfo", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "Name": + return ec.fieldContext_RepoSummary_Name(ctx, field) + case "LastUpdated": + return ec.fieldContext_RepoSummary_LastUpdated(ctx, field) + case "Size": + 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_RepoSummary_Score(ctx, field) + case "NewestTag": + return ec.fieldContext_RepoSummary_NewestTag(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type RepoSummary", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) _RepoSummary_Name(ctx context.Context, field graphql.CollectedField, obj *RepoSummary) (ret graphql.Marshaler) { fc, err := ec.fieldContext_RepoSummary_Name(ctx, field) if err != nil { @@ -6363,6 +6431,10 @@ func (ec *executionContext) _RepoInfo(ctx context.Context, sel ast.SelectionSet, out.Values[i] = ec._RepoInfo_Manifests(ctx, field, obj) + case "Summary": + + out.Values[i] = ec._RepoInfo_Summary(ctx, field, obj) + default: panic("unknown field " + strconv.Quote(field.Name)) } diff --git a/pkg/extensions/search/gql_generated/models_gen.go b/pkg/extensions/search/gql_generated/models_gen.go index dd80da46..d0a4ab35 100644 --- a/pkg/extensions/search/gql_generated/models_gen.go +++ b/pkg/extensions/search/gql_generated/models_gen.go @@ -92,6 +92,7 @@ type PackageInfo struct { type RepoInfo struct { Manifests []*ManifestInfo `json:"Manifests"` + Summary *RepoSummary `json:"Summary"` } type RepoSummary struct { diff --git a/pkg/extensions/search/schema.graphql b/pkg/extensions/search/schema.graphql index 4a223b44..8657535c 100644 --- a/pkg/extensions/search/schema.graphql +++ b/pkg/extensions/search/schema.graphql @@ -52,6 +52,7 @@ type ImageInfo { type RepoInfo { Manifests: [ManifestInfo] + Summary: RepoSummary } type ManifestInfo { diff --git a/pkg/extensions/search/schema.resolvers.go b/pkg/extensions/search/schema.resolvers.go index aa299b0b..9cd4c8ce 100644 --- a/pkg/extensions/search/schema.resolvers.go +++ b/pkg/extensions/search/schema.resolvers.go @@ -333,6 +333,31 @@ func (r *queryResolver) ExpandedRepoInfo(ctx context.Context, repo string) (*gql manifests := make([]*gql_generated.ManifestInfo, 0) + summary := &gql_generated.RepoSummary{} + + summary.LastUpdated = &origRepoInfo.Summary.LastUpdated + summary.Name = &origRepoInfo.Summary.Name + summary.Platforms = []*gql_generated.OsArch{} + + for _, platform := range origRepoInfo.Summary.Platforms { + platform := platform + + summary.Platforms = append(summary.Platforms, &gql_generated.OsArch{ + Os: &platform.Os, + Arch: &platform.Arch, + }) + } + + summary.Size = &origRepoInfo.Summary.Size + + for _, vendor := range origRepoInfo.Summary.Vendors { + vendor := vendor + summary.Vendors = append(summary.Vendors, &vendor) + } + + score := -1 // score not relevant for this query + summary.Score = &score + for _, manifest := range origRepoInfo.Manifests { tag := manifest.Tag @@ -359,6 +384,7 @@ func (r *queryResolver) ExpandedRepoInfo(ctx context.Context, repo string) (*gql manifests = append(manifests, manifestInfo) } + repoInfo.Summary = summary repoInfo.Manifests = manifests return repoInfo, nil