diff --git a/pkg/extensions/search/common/common.go b/pkg/extensions/search/common/common.go index bd4633f1..077202d6 100644 --- a/pkg/extensions/search/common/common.go +++ b/pkg/extensions/search/common/common.go @@ -167,6 +167,7 @@ type ImageAnnotations struct { Source string Labels string Vendor string + Authors string } /* @@ -195,6 +196,12 @@ func GetVendor(annotations map[string]string) string { return GetAnnotationValue(annotations, ispec.AnnotationVendor, LabelAnnotationVendor) } +func GetAuthors(annotations map[string]string) string { + authors := annotations[ispec.AnnotationAuthors] + + return authors +} + func GetTitle(annotations map[string]string) string { return GetAnnotationValue(annotations, ispec.AnnotationTitle, LabelAnnotationTitle) } @@ -232,7 +239,7 @@ func GetAnnotations(annotations, labels map[string]string) ImageAnnotations { documentation := GetDocumentation(annotations) if documentation == "" { - documentation = GetDocumentation(annotations) + documentation = GetDocumentation(labels) } source := GetSource(annotations) @@ -255,6 +262,11 @@ func GetAnnotations(annotations, labels map[string]string) ImageAnnotations { vendor = GetVendor(labels) } + authors := GetAuthors(annotations) + if authors == "" { + authors = GetAuthors(labels) + } + return ImageAnnotations{ Description: description, Title: title, @@ -263,5 +275,6 @@ func GetAnnotations(annotations, labels map[string]string) ImageAnnotations { Licenses: licenses, Labels: categories, Vendor: vendor, + Authors: authors, } } diff --git a/pkg/extensions/search/common/common_test.go b/pkg/extensions/search/common/common_test.go index 67fdaa08..3ce6f983 100644 --- a/pkg/extensions/search/common/common_test.go +++ b/pkg/extensions/search/common/common_test.go @@ -1984,6 +1984,150 @@ func TestGetRepositories(t *testing.T) { }) } +func TestGlobalSearchImageAuthor(t *testing.T) { + port := GetFreePort() + baseURL := GetBaseURL(port) + conf := config.New() + conf.HTTP.Port = port + tempDir := t.TempDir() + conf.Storage.RootDirectory = tempDir + + defaultVal := true + conf.Extensions = &extconf.ExtensionConfig{ + Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{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) + }() + + Convey("Test global search with author in manifest's annotations", t, func() { + cfg, layers, manifest, err := GetImageComponents(10000) + So(err, ShouldBeNil) + + manifest.Annotations = make(map[string]string) + manifest.Annotations["org.opencontainers.image.authors"] = "author name" + err = UploadImage( + Image{ + Config: cfg, + Layers: layers, + Manifest: manifest, + Tag: "latest", + }, baseURL, "repowithauthor") + + So(err, ShouldBeNil) + + query := ` + { + GlobalSearch(query:""){ + Images { + RepoName + Tag + LastUpdated + Size + IsSigned + Vendor + Score + Platform { + Os + Arch + } + Vulnerabilities { + Count + MaxSeverity + } + Authors + } + } + }` + resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) + So(resp, ShouldNotBeNil) + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, 200) + + responseStruct := &GlobalSearchResultResp{} + + err = json.Unmarshal(resp.Body(), responseStruct) + So(err, ShouldBeNil) + + So(responseStruct.GlobalSearchResult.GlobalSearch.Images[0].Authors, ShouldEqual, "author name") + }) + + Convey("Test global search with author in manifest's config", t, func() { + cfg, layers, manifest, err := GetImageComponents(10000) + So(err, ShouldBeNil) + + err = UploadImage( + Image{ + Config: cfg, + Layers: layers, + Manifest: manifest, + Tag: "latest", + }, baseURL, "repowithauthorconfig") + + So(err, ShouldBeNil) + + query := ` + { + GlobalSearch(query:""){ + Images { + RepoName + Tag + LastUpdated + Size + IsSigned + Vendor + Score + Platform { + Os + Arch + } + Vulnerabilities { + Count + MaxSeverity + } + Authors + } + } + }` + resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) + So(resp, ShouldNotBeNil) + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, 200) + + responseStruct := &GlobalSearchResultResp{} + + err = json.Unmarshal(resp.Body(), responseStruct) + So(err, ShouldBeNil) + + So(responseStruct.GlobalSearchResult.GlobalSearch.Images[1].Authors, ShouldEqual, "ZotUser") + }) +} + func TestGlobalSearch(t *testing.T) { Convey("Test global search", t, func() { subpath := "/a" diff --git a/pkg/extensions/search/common/model.go b/pkg/extensions/search/common/model.go index 55a20ac7..88a7933e 100644 --- a/pkg/extensions/search/common/model.go +++ b/pkg/extensions/search/common/model.go @@ -40,6 +40,7 @@ type ImageSummary struct { History []LayerHistory `json:"history"` Layers []LayerSummary `json:"layers"` Vulnerabilities ImageVulnerabilitySummary `json:"vulnerabilities"` + Authors string `json:"authors"` } type OsArch struct { diff --git a/pkg/extensions/search/gql_generated/generated.go b/pkg/extensions/search/gql_generated/generated.go index 7cb67ba4..649fb9ce 100644 --- a/pkg/extensions/search/gql_generated/generated.go +++ b/pkg/extensions/search/gql_generated/generated.go @@ -71,6 +71,7 @@ type ComplexityRoot struct { } ImageSummary struct { + Authors func(childComplexity int) int ConfigDigest func(childComplexity int) int Description func(childComplexity int) int Digest func(childComplexity int) int @@ -287,6 +288,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.HistoryDescription.EmptyLayer(childComplexity), true + case "ImageSummary.Authors": + if e.complexity.ImageSummary.Authors == nil { + break + } + + return e.complexity.ImageSummary.Authors(childComplexity), true + case "ImageSummary.ConfigDigest": if e.complexity.ImageSummary.ConfigDigest == nil { break @@ -849,6 +857,7 @@ type ImageSummary { Documentation: String History: [LayerHistory] Vulnerabilities: ImageVulnerabilitySummary + Authors: String } type ImageVulnerabilitySummary { @@ -1576,6 +1585,8 @@ func (ec *executionContext) fieldContext_GlobalSearchResult_Images(ctx context.C return ec.fieldContext_ImageSummary_History(ctx, field) case "Vulnerabilities": return ec.fieldContext_ImageSummary_Vulnerabilities(ctx, field) + case "Authors": + return ec.fieldContext_ImageSummary_Authors(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type ImageSummary", field.Name) }, @@ -2746,6 +2757,47 @@ func (ec *executionContext) fieldContext_ImageSummary_Vulnerabilities(ctx contex return fc, nil } +func (ec *executionContext) _ImageSummary_Authors(ctx context.Context, field graphql.CollectedField, obj *ImageSummary) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ImageSummary_Authors(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.Authors, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2áš–string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_ImageSummary_Authors(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ImageSummary", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _ImageVulnerabilitySummary_MaxSeverity(ctx context.Context, field graphql.CollectedField, obj *ImageVulnerabilitySummary) (ret graphql.Marshaler) { fc, err := ec.fieldContext_ImageVulnerabilitySummary_MaxSeverity(ctx, field) if err != nil { @@ -3395,6 +3447,8 @@ func (ec *executionContext) fieldContext_Query_ImageListForCVE(ctx context.Conte return ec.fieldContext_ImageSummary_History(ctx, field) case "Vulnerabilities": return ec.fieldContext_ImageSummary_Vulnerabilities(ctx, field) + case "Authors": + return ec.fieldContext_ImageSummary_Authors(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type ImageSummary", field.Name) }, @@ -3489,6 +3543,8 @@ func (ec *executionContext) fieldContext_Query_ImageListWithCVEFixed(ctx context return ec.fieldContext_ImageSummary_History(ctx, field) case "Vulnerabilities": return ec.fieldContext_ImageSummary_Vulnerabilities(ctx, field) + case "Authors": + return ec.fieldContext_ImageSummary_Authors(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type ImageSummary", field.Name) }, @@ -3583,6 +3639,8 @@ func (ec *executionContext) fieldContext_Query_ImageListForDigest(ctx context.Co return ec.fieldContext_ImageSummary_History(ctx, field) case "Vulnerabilities": return ec.fieldContext_ImageSummary_Vulnerabilities(ctx, field) + case "Authors": + return ec.fieldContext_ImageSummary_Authors(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type ImageSummary", field.Name) }, @@ -3743,6 +3801,8 @@ func (ec *executionContext) fieldContext_Query_ImageList(ctx context.Context, fi return ec.fieldContext_ImageSummary_History(ctx, field) case "Vulnerabilities": return ec.fieldContext_ImageSummary_Vulnerabilities(ctx, field) + case "Authors": + return ec.fieldContext_ImageSummary_Authors(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type ImageSummary", field.Name) }, @@ -3961,6 +4021,8 @@ func (ec *executionContext) fieldContext_Query_DerivedImageList(ctx context.Cont return ec.fieldContext_ImageSummary_History(ctx, field) case "Vulnerabilities": return ec.fieldContext_ImageSummary_Vulnerabilities(ctx, field) + case "Authors": + return ec.fieldContext_ImageSummary_Authors(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type ImageSummary", field.Name) }, @@ -4055,6 +4117,8 @@ func (ec *executionContext) fieldContext_Query_BaseImageList(ctx context.Context return ec.fieldContext_ImageSummary_History(ctx, field) case "Vulnerabilities": return ec.fieldContext_ImageSummary_Vulnerabilities(ctx, field) + case "Authors": + return ec.fieldContext_ImageSummary_Authors(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type ImageSummary", field.Name) }, @@ -4149,6 +4213,8 @@ func (ec *executionContext) fieldContext_Query_Image(ctx context.Context, field return ec.fieldContext_ImageSummary_History(ctx, field) case "Vulnerabilities": return ec.fieldContext_ImageSummary_Vulnerabilities(ctx, field) + case "Authors": + return ec.fieldContext_ImageSummary_Authors(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type ImageSummary", field.Name) }, @@ -4372,6 +4438,8 @@ func (ec *executionContext) fieldContext_RepoInfo_Images(ctx context.Context, fi return ec.fieldContext_ImageSummary_History(ctx, field) case "Vulnerabilities": return ec.fieldContext_ImageSummary_Vulnerabilities(ctx, field) + case "Authors": + return ec.fieldContext_ImageSummary_Authors(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type ImageSummary", field.Name) }, @@ -4770,6 +4838,8 @@ func (ec *executionContext) fieldContext_RepoSummary_NewestImage(ctx context.Con return ec.fieldContext_ImageSummary_History(ctx, field) case "Vulnerabilities": return ec.fieldContext_ImageSummary_Vulnerabilities(ctx, field) + case "Authors": + return ec.fieldContext_ImageSummary_Authors(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type ImageSummary", field.Name) }, @@ -6915,6 +6985,10 @@ func (ec *executionContext) _ImageSummary(ctx context.Context, sel ast.Selection out.Values[i] = ec._ImageSummary_Vulnerabilities(ctx, field, obj) + case "Authors": + + out.Values[i] = ec._ImageSummary_Authors(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 e7a1ea65..e7cfff45 100644 --- a/pkg/extensions/search/gql_generated/models_gen.go +++ b/pkg/extensions/search/gql_generated/models_gen.go @@ -62,6 +62,7 @@ type ImageSummary struct { Documentation *string `json:"Documentation"` History []*LayerHistory `json:"History"` Vulnerabilities *ImageVulnerabilitySummary `json:"Vulnerabilities"` + Authors *string `json:"Authors"` } type ImageVulnerabilitySummary struct { diff --git a/pkg/extensions/search/resolver.go b/pkg/extensions/search/resolver.go index b06c5c91..69e013cb 100644 --- a/pkg/extensions/search/resolver.go +++ b/pkg/extensions/search/resolver.go @@ -205,6 +205,11 @@ func repoListWithNewestImage( } } + authors := annotations.Authors + if authors == "" { + authors = imageConfigInfo.Author + } + tag := manifestTag size := strconv.Itoa(int(imageSize)) manifestDigest := manifest.Digest.String() @@ -234,6 +239,7 @@ func repoListWithNewestImage( MaxSeverity: &imageCveSummary.MaxSeverity, Count: &imageCveSummary.Count, }, + Authors: &authors, } if manifest.Digest.String() == lastUpdatedTag.Digest.String() { @@ -402,6 +408,11 @@ func globalSearch(repoList []string, name, tag string, olu common.OciLayoutUtils } } + authors := annotations.Authors + if authors == "" { + authors = imageConfigInfo.Author + } + imageSummary := gql_generated.ImageSummary{ RepoName: &repo, Tag: &manifestTag, @@ -423,6 +434,7 @@ func globalSearch(repoList []string, name, tag string, olu common.OciLayoutUtils MaxSeverity: &imageCveSummary.MaxSeverity, Count: &imageCveSummary.Count, }, + Authors: &authors, } if manifest.Digest.String() == lastUpdatedTag.Digest.String() { diff --git a/pkg/extensions/search/schema.graphql b/pkg/extensions/search/schema.graphql index d419f16c..50987ba3 100644 --- a/pkg/extensions/search/schema.graphql +++ b/pkg/extensions/search/schema.graphql @@ -73,6 +73,7 @@ type ImageSummary { Documentation: String History: [LayerHistory] Vulnerabilities: ImageVulnerabilitySummary + Authors: String } type ImageVulnerabilitySummary {