diff --git a/pkg/common/model.go b/pkg/common/model.go index c31ed397..b245d85f 100644 --- a/pkg/common/model.go +++ b/pkg/common/model.go @@ -53,6 +53,8 @@ type ManifestSummary struct { Layers []LayerSummary `json:"layers"` History []LayerHistory `json:"history"` Vulnerabilities ImageVulnerabilitySummary `json:"vulnerabilities"` + Referrers []Referrer `json:"referrers"` + ArtifactType string `json:"artifactType"` } type Platform struct { diff --git a/pkg/extensions/search/convert/repodb.go b/pkg/extensions/search/convert/repodb.go index bc4f34ef..80fd74cf 100644 --- a/pkg/extensions/search/convert/repodb.go +++ b/pkg/extensions/search/convert/repodb.go @@ -301,6 +301,7 @@ func ImageManifest2ImageSummary(ctx context.Context, repo, tag string, digest go repoName = repo configDigest = manifestContent.Config.Digest.String() configSize = manifestContent.Config.Size + artifactType = common.GetManifestArtifactType(manifestContent) imageLastUpdated = common.GetImageLastUpdated(configContent) downloadCount = repoMeta.Statistics[digest.String()].DownloadCount isSigned = false @@ -373,6 +374,8 @@ func ImageManifest2ImageSummary(ctx context.Context, repo, tag string, digest go MaxSeverity: &imageCveSummary.MaxSeverity, Count: &imageCveSummary.Count, }, + Referrers: getReferrers(repoMeta.Referrers[manifestDigest]), + ArtifactType: &artifactType, }, }, LastUpdated: &imageLastUpdated, @@ -437,8 +440,7 @@ func ImageManifest2ManifestSummary(ctx context.Context, repo, tag string, descri ) (*gql_generated.ManifestSummary, map[string]int64, error) { var ( manifestContent ispec.Manifest - - digest = descriptor.Digest + digest = descriptor.Digest ) err := json.Unmarshal(manifestMeta.ManifestBlob, &manifestContent) @@ -463,6 +465,7 @@ func ImageManifest2ManifestSummary(ctx context.Context, repo, tag string, descri manifestDigestStr = digest.String() configDigest = manifestContent.Config.Digest.String() configSize = manifestContent.Config.Size + artifactType = common.GetManifestArtifactType(manifestContent) imageLastUpdated = common.GetImageLastUpdated(configContent) downloadCount = manifestMeta.DownloadCount isSigned = false @@ -522,7 +525,8 @@ func ImageManifest2ManifestSummary(ctx context.Context, repo, tag string, descri MaxSeverity: &imageCveSummary.MaxSeverity, Count: &imageCveSummary.Count, }, - Referrers: getReferrers(referrersInfo), + Referrers: getReferrers(referrersInfo), + ArtifactType: &artifactType, } return &manifestSummary, imageBlobsMap, nil diff --git a/pkg/extensions/search/gql_generated/generated.go b/pkg/extensions/search/gql_generated/generated.go index 1adfbb3c..09466419 100644 --- a/pkg/extensions/search/gql_generated/generated.go +++ b/pkg/extensions/search/gql_generated/generated.go @@ -115,6 +115,7 @@ type ComplexityRoot struct { } ManifestSummary struct { + ArtifactType func(childComplexity int) int ConfigDigest func(childComplexity int) int Digest func(childComplexity int) int DownloadCount func(childComplexity int) int @@ -538,6 +539,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.LayerSummary.Size(childComplexity), true + case "ManifestSummary.ArtifactType": + if e.complexity.ManifestSummary.ArtifactType == nil { + break + } + + return e.complexity.ManifestSummary.ArtifactType(childComplexity), true + case "ManifestSummary.ConfigDigest": if e.complexity.ManifestSummary.ConfigDigest == nil { break @@ -1278,6 +1286,10 @@ type ManifestSummary { Information about objects that reference this image """ Referrers: [Referrer] + """ + Value of the artifactType field if present else the value of the config media type + """ + ArtifactType: String } """ @@ -3248,6 +3260,8 @@ func (ec *executionContext) fieldContext_ImageSummary_Manifests(ctx context.Cont return ec.fieldContext_ManifestSummary_Vulnerabilities(ctx, field) case "Referrers": return ec.fieldContext_ManifestSummary_Referrers(ctx, field) + case "ArtifactType": + return ec.fieldContext_ManifestSummary_ArtifactType(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type ManifestSummary", field.Name) }, @@ -4598,6 +4612,47 @@ func (ec *executionContext) fieldContext_ManifestSummary_Referrers(ctx context.C return fc, nil } +func (ec *executionContext) _ManifestSummary_ArtifactType(ctx context.Context, field graphql.CollectedField, obj *ManifestSummary) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ManifestSummary_ArtifactType(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.ArtifactType, 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_ManifestSummary_ArtifactType(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ManifestSummary", + 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) _PackageInfo_Name(ctx context.Context, field graphql.CollectedField, obj *PackageInfo) (ret graphql.Marshaler) { fc, err := ec.fieldContext_PackageInfo_Name(ctx, field) if err != nil { @@ -9282,6 +9337,10 @@ func (ec *executionContext) _ManifestSummary(ctx context.Context, sel ast.Select out.Values[i] = ec._ManifestSummary_Referrers(ctx, field, obj) + case "ArtifactType": + + out.Values[i] = ec._ManifestSummary_ArtifactType(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 8ca876f7..2ee26739 100644 --- a/pkg/extensions/search/gql_generated/models_gen.go +++ b/pkg/extensions/search/gql_generated/models_gen.go @@ -181,6 +181,8 @@ type ManifestSummary struct { Vulnerabilities *ImageVulnerabilitySummary `json:"Vulnerabilities,omitempty"` // Information about objects that reference this image Referrers []*Referrer `json:"Referrers,omitempty"` + // Value of the artifactType field if present else the value of the config media type + ArtifactType *string `json:"ArtifactType,omitempty"` } // Contains the name of the package, the current installed version and the version where the CVE was fixed diff --git a/pkg/extensions/search/schema.graphql b/pkg/extensions/search/schema.graphql index b8e6eb70..e6cd3936 100644 --- a/pkg/extensions/search/schema.graphql +++ b/pkg/extensions/search/schema.graphql @@ -242,6 +242,10 @@ type ManifestSummary { Information about objects that reference this image """ Referrers: [Referrer] + """ + Value of the artifactType field if present else the value of the config media type + """ + ArtifactType: String } """ diff --git a/pkg/extensions/search/search_test.go b/pkg/extensions/search/search_test.go index f9cb5952..20a4455a 100644 --- a/pkg/extensions/search/search_test.go +++ b/pkg/extensions/search/search_test.go @@ -6456,4 +6456,132 @@ func TestImageSummary(t *testing.T) { // There are 0 vulnerabilities this data used in tests So(imgSummary.Vulnerabilities.MaxSeverity, ShouldEqual, "CRITICAL") }) + + Convey("GraphQL query for Artifact Type", t, func() { + port := GetFreePort() + baseURL := GetBaseURL(port) + conf := config.New() + conf.HTTP.Port = port + conf.Storage.RootDirectory = t.TempDir() + conf.Storage.GC = false + + defaultVal := true + conf.Extensions = &extconf.ExtensionConfig{ + Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, + } + + conf.Extensions.Search.CVE = nil + + ctlr := api.NewController(conf) + + query := ` + { + Image(image:"repo:art%d"){ + RepoName + Tag + Manifests { + Digest + ArtifactType + } + Size + } + }` + + queryImg1 := fmt.Sprintf(query, 1) + queryImg2 := fmt.Sprintf(query, 2) + + var imgSummaryResponse ImageSummaryResult + + ctlrManager := NewControllerManager(ctlr) + ctlrManager.StartAndWait(port) + defer ctlrManager.StopServer() + + // upload the images + artType1 := "application/test.signature.v1" + artType2 := "application/test.signature.v2" + + img1, err := GetRandomImage("art1") + So(err, ShouldBeNil) + img1.Manifest.Config = ispec.ScratchDescriptor + img1.Manifest.ArtifactType = artType1 + digest1, err := img1.Digest() + So(err, ShouldBeNil) + + err = UploadImage(img1, baseURL, "repo") + So(err, ShouldBeNil) + + img2, err := GetRandomImage("art2") + So(err, ShouldBeNil) + img2.Manifest.Config.MediaType = artType2 + digest2, err := img2.Digest() + So(err, ShouldBeNil) + + err = UploadImage(img2, baseURL, "repo") + So(err, ShouldBeNil) + + // GET image 1 + resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(queryImg1)) + So(resp, ShouldNotBeNil) + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, 200) + So(resp.Body(), ShouldNotBeNil) + + err = json.Unmarshal(resp.Body(), &imgSummaryResponse) + So(err, ShouldBeNil) + + imgSum := imgSummaryResponse.SingleImageSummary.ImageSummary + So(len(imgSum.Manifests), ShouldEqual, 1) + So(imgSum.Manifests[0].ArtifactType, ShouldResemble, artType1) + + // GET image 2 + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(queryImg2)) + So(resp, ShouldNotBeNil) + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, 200) + So(resp.Body(), ShouldNotBeNil) + + err = json.Unmarshal(resp.Body(), &imgSummaryResponse) + So(err, ShouldBeNil) + + imgSum = imgSummaryResponse.SingleImageSummary.ImageSummary + So(len(imgSum.Manifests), ShouldEqual, 1) + So(imgSum.Manifests[0].ArtifactType, ShouldResemble, artType2) + + // Expanded repo info test + + queryExpRepoInfo := `{ + ExpandedRepoInfo(repo:"test1"){ + Images { + Tag + Manifests { + Digest + ArtifactType + } + } + } + }` + + var expandedRepoInfoResp ExpandedRepoInfoResp + + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + + url.QueryEscape(queryExpRepoInfo)) + So(resp, ShouldNotBeNil) + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, 200) + So(resp.Body(), ShouldNotBeNil) + + err = json.Unmarshal(resp.Body(), &expandedRepoInfoResp) + So(err, ShouldBeNil) + + imgSums := expandedRepoInfoResp.ExpandedRepoInfo.RepoInfo.ImageSummaries + + for _, imgSum := range imgSums { + switch imgSum.Digest { + case digest1.String(): + So(imgSum.Manifests[0].ArtifactType, ShouldResemble, artType1) + case digest2.String(): + So(imgSum.Manifests[0].ArtifactType, ShouldResemble, artType2) + } + } + }) }