diff --git a/pkg/extensions/search/convert/convert_test.go b/pkg/extensions/search/convert/convert_test.go index d217b129..6d4eeb31 100644 --- a/pkg/extensions/search/convert/convert_test.go +++ b/pkg/extensions/search/convert/convert_test.go @@ -279,19 +279,21 @@ func TestTaggedTimestamp(t *testing.T) { Convey("TaggedTimestamp is populated from tag descriptor", func() { taggedTime := time.Date(2024, time.January, 15, 10, 30, 0, 0, time.UTC) pushTime := time.Date(2024, time.January, 10, 8, 0, 0, 0, time.UTC) + digestStr := godigest.FromString("sha256:abc123").String() repoMeta := mTypes.RepoMeta{ Name: "repo", Tags: map[string]mTypes.Descriptor{ "tag1": { - Digest: "sha256:abc123", + Digest: digestStr, MediaType: ispec.MediaTypeImageManifest, TaggedTimestamp: taggedTime, }, }, Statistics: map[string]mTypes.DescriptorStatistics{ - "sha256:abc123": { + digestStr: { PushTimestamp: pushTime, + PushedBy: "testuser", }, }, } @@ -319,6 +321,8 @@ func TestTaggedTimestamp(t *testing.T) { So(err, ShouldBeNil) So(imageSummary.TaggedTimestamp, ShouldNotBeNil) So(*imageSummary.TaggedTimestamp, ShouldEqual, taggedTime) + So(imageSummary.PushedBy, ShouldNotBeNil) + So(*imageSummary.PushedBy, ShouldEqual, "testuser") }) Convey("TaggedTimestamp falls back to PushTimestamp when zero", func() { @@ -372,6 +376,7 @@ func TestTaggedTimestamp(t *testing.T) { Convey("TaggedTimestamp is propagated to nested manifests in ImageIndex", func() { taggedTime := time.Date(2024, time.January, 15, 10, 30, 0, 0, time.UTC) pushTime := time.Date(2024, time.January, 10, 8, 0, 0, 0, time.UTC) + lastPullTime := time.Date(2024, time.February, 1, 12, 0, 0, 0, time.UTC) // Create a multiarch image multiarchImage := CreateMultiarchWith().Images([]Image{ @@ -392,7 +397,9 @@ func TestTaggedTimestamp(t *testing.T) { }, Statistics: map[string]mTypes.DescriptorStatistics{ indexDigestStr: { - PushTimestamp: pushTime, + PushTimestamp: pushTime, + LastPullTimestamp: lastPullTime, + PushedBy: "indexuser", }, }, } @@ -405,6 +412,10 @@ func TestTaggedTimestamp(t *testing.T) { So(err, ShouldBeNil) So(imageSummary.TaggedTimestamp, ShouldNotBeNil) So(*imageSummary.TaggedTimestamp, ShouldEqual, taggedTime) + So(imageSummary.LastPullTimestamp, ShouldNotBeNil) + So(*imageSummary.LastPullTimestamp, ShouldEqual, lastPullTime) + So(imageSummary.PushedBy, ShouldNotBeNil) + So(*imageSummary.PushedBy, ShouldEqual, "indexuser") // Verify that nested manifests also have the correct TaggedTimestamp So(len(imageSummary.Manifests), ShouldBeGreaterThan, 0) diff --git a/pkg/extensions/search/convert/metadb.go b/pkg/extensions/search/convert/metadb.go index 5b29b354..0124e4a1 100644 --- a/pkg/extensions/search/convert/metadb.go +++ b/pkg/extensions/search/convert/metadb.go @@ -427,10 +427,12 @@ func ImageIndex2ImageSummary(ctx context.Context, fullImageMeta mTypes.FullImage manifestSummaries = make([]*gql_generated.ManifestSummary, 0, len(fullImageMeta.Manifests)) indexBlobs = map[string]int64{} - indexDigestStr = fullImageMeta.Digest.String() - indexMediaType = ispec.MediaTypeImageIndex - pushTimestamp = fullImageMeta.Statistics.PushTimestamp - taggedTimestamp = fullImageMeta.TaggedTimestamp + indexDigestStr = fullImageMeta.Digest.String() + indexMediaType = ispec.MediaTypeImageIndex + lastPullTimestamp = fullImageMeta.Statistics.LastPullTimestamp + pushTimestamp = fullImageMeta.Statistics.PushTimestamp + pushedBy = fullImageMeta.Statistics.PushedBy + taggedTimestamp = fullImageMeta.TaggedTimestamp ) // Fallback to PushTimestamp if TaggedTimestamp is not available @@ -490,27 +492,29 @@ func ImageIndex2ImageSummary(ctx context.Context, fullImageMeta mTypes.FullImage } indexSummary := gql_generated.ImageSummary{ - RepoName: &repo, - Tag: &tag, - Digest: &indexDigestStr, - MediaType: &indexMediaType, - Manifests: manifestSummaries, - LastUpdated: imageLastUpdated, - IsSigned: &isSigned, - SignatureInfo: signaturesInfo, - Size: ref(strconv.FormatInt(indexSize, 10)), - DownloadCount: ref(fullImageMeta.Statistics.DownloadCount), - PushTimestamp: &pushTimestamp, - TaggedTimestamp: &taggedTimestamp, - Description: &annotations.Description, - Title: &annotations.Title, - Documentation: &annotations.Documentation, - Licenses: &annotations.Licenses, - Labels: &annotations.Labels, - Source: &annotations.Source, - Vendor: &annotations.Vendor, - Authors: &annotations.Authors, - Referrers: getReferrers(fullImageMeta.Referrers), + RepoName: &repo, + Tag: &tag, + Digest: &indexDigestStr, + MediaType: &indexMediaType, + Manifests: manifestSummaries, + LastUpdated: imageLastUpdated, + IsSigned: &isSigned, + SignatureInfo: signaturesInfo, + Size: ref(strconv.FormatInt(indexSize, 10)), + DownloadCount: ref(fullImageMeta.Statistics.DownloadCount), + LastPullTimestamp: &lastPullTimestamp, + PushTimestamp: &pushTimestamp, + PushedBy: &pushedBy, + TaggedTimestamp: &taggedTimestamp, + Description: &annotations.Description, + Title: &annotations.Title, + Documentation: &annotations.Documentation, + Licenses: &annotations.Licenses, + Labels: &annotations.Labels, + Source: &annotations.Source, + Vendor: &annotations.Vendor, + Authors: &annotations.Authors, + Referrers: getReferrers(fullImageMeta.Referrers), } return &indexSummary, indexBlobs, nil @@ -534,6 +538,7 @@ func ImageManifest2ImageSummary(ctx context.Context, fullImageMeta mTypes.FullIm isSigned = isImageSigned(fullImageMeta.Signatures) lastPullTimestamp = fullImageMeta.Statistics.LastPullTimestamp pushTimestamp = fullImageMeta.Statistics.PushTimestamp + pushedBy = fullImageMeta.Statistics.PushedBy taggedTimestamp = fullImageMeta.TaggedTimestamp ) @@ -594,6 +599,7 @@ func ImageManifest2ImageSummary(ctx context.Context, fullImageMeta mTypes.FullIm DownloadCount: &downloadCount, LastPullTimestamp: &lastPullTimestamp, PushTimestamp: &pushTimestamp, + PushedBy: &pushedBy, TaggedTimestamp: &taggedTimestamp, Description: &annotations.Description, Title: &annotations.Title, diff --git a/pkg/extensions/search/gql_generated/generated.go b/pkg/extensions/search/gql_generated/generated.go index 02fdd1a7..b93bcedb 100644 --- a/pkg/extensions/search/gql_generated/generated.go +++ b/pkg/extensions/search/gql_generated/generated.go @@ -100,6 +100,7 @@ type ComplexityRoot struct { Manifests func(childComplexity int) int MediaType func(childComplexity int) int PushTimestamp func(childComplexity int) int + PushedBy func(childComplexity int) int Referrers func(childComplexity int) int RepoName func(childComplexity int) int SignatureInfo func(childComplexity int) int @@ -530,6 +531,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin } return e.ComplexityRoot.ImageSummary.PushTimestamp(childComplexity), true + case "ImageSummary.PushedBy": + if e.ComplexityRoot.ImageSummary.PushedBy == nil { + break + } + + return e.ComplexityRoot.ImageSummary.PushedBy(childComplexity), true case "ImageSummary.Referrers": if e.ComplexityRoot.ImageSummary.Referrers == nil { break @@ -1400,6 +1407,10 @@ type ImageSummary { """ PushTimestamp: Time """ + The user who pushed the image to the registry + """ + PushedBy: String + """ Timestamp when the image manifest was tagged (if the data is unavailable it falls back to when the image was pushed to the registry) """ TaggedTimestamp: Time @@ -3106,6 +3117,8 @@ func (ec *executionContext) fieldContext_GlobalSearchResult_Images(_ context.Con return ec.fieldContext_ImageSummary_LastPullTimestamp(ctx, field) case "PushTimestamp": return ec.fieldContext_ImageSummary_PushTimestamp(ctx, field) + case "PushedBy": + return ec.fieldContext_ImageSummary_PushedBy(ctx, field) case "TaggedTimestamp": return ec.fieldContext_ImageSummary_TaggedTimestamp(ctx, field) case "LastUpdated": @@ -3787,6 +3800,35 @@ func (ec *executionContext) fieldContext_ImageSummary_PushTimestamp(_ context.Co return fc, nil } +func (ec *executionContext) _ImageSummary_PushedBy(ctx context.Context, field graphql.CollectedField, obj *ImageSummary) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_ImageSummary_PushedBy, + func(ctx context.Context) (any, error) { + return obj.PushedBy, nil + }, + nil, + ec.marshalOString2áš–string, + true, + false, + ) +} + +func (ec *executionContext) fieldContext_ImageSummary_PushedBy(_ 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) _ImageSummary_TaggedTimestamp(ctx context.Context, field graphql.CollectedField, obj *ImageSummary) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -5277,6 +5319,8 @@ func (ec *executionContext) fieldContext_PaginatedImagesResult_Results(_ context return ec.fieldContext_ImageSummary_LastPullTimestamp(ctx, field) case "PushTimestamp": return ec.fieldContext_ImageSummary_PushTimestamp(ctx, field) + case "PushedBy": + return ec.fieldContext_ImageSummary_PushedBy(ctx, field) case "TaggedTimestamp": return ec.fieldContext_ImageSummary_TaggedTimestamp(ctx, field) case "LastUpdated": @@ -6034,6 +6078,8 @@ func (ec *executionContext) fieldContext_Query_Image(ctx context.Context, field return ec.fieldContext_ImageSummary_LastPullTimestamp(ctx, field) case "PushTimestamp": return ec.fieldContext_ImageSummary_PushTimestamp(ctx, field) + case "PushedBy": + return ec.fieldContext_ImageSummary_PushedBy(ctx, field) case "TaggedTimestamp": return ec.fieldContext_ImageSummary_TaggedTimestamp(ctx, field) case "LastUpdated": @@ -6530,6 +6576,8 @@ func (ec *executionContext) fieldContext_RepoInfo_Images(_ context.Context, fiel return ec.fieldContext_ImageSummary_LastPullTimestamp(ctx, field) case "PushTimestamp": return ec.fieldContext_ImageSummary_PushTimestamp(ctx, field) + case "PushedBy": + return ec.fieldContext_ImageSummary_PushedBy(ctx, field) case "TaggedTimestamp": return ec.fieldContext_ImageSummary_TaggedTimestamp(ctx, field) case "LastUpdated": @@ -6813,6 +6861,8 @@ func (ec *executionContext) fieldContext_RepoSummary_NewestImage(_ context.Conte return ec.fieldContext_ImageSummary_LastPullTimestamp(ctx, field) case "PushTimestamp": return ec.fieldContext_ImageSummary_PushTimestamp(ctx, field) + case "PushedBy": + return ec.fieldContext_ImageSummary_PushedBy(ctx, field) case "TaggedTimestamp": return ec.fieldContext_ImageSummary_TaggedTimestamp(ctx, field) case "LastUpdated": @@ -9065,6 +9115,8 @@ func (ec *executionContext) _ImageSummary(ctx context.Context, sel ast.Selection out.Values[i] = ec._ImageSummary_LastPullTimestamp(ctx, field, obj) case "PushTimestamp": out.Values[i] = ec._ImageSummary_PushTimestamp(ctx, field, obj) + case "PushedBy": + out.Values[i] = ec._ImageSummary_PushedBy(ctx, field, obj) case "TaggedTimestamp": out.Values[i] = ec._ImageSummary_TaggedTimestamp(ctx, field, obj) case "LastUpdated": diff --git a/pkg/extensions/search/gql_generated/models_gen.go b/pkg/extensions/search/gql_generated/models_gen.go index 91b024a5..ccc64afe 100644 --- a/pkg/extensions/search/gql_generated/models_gen.go +++ b/pkg/extensions/search/gql_generated/models_gen.go @@ -152,6 +152,8 @@ type ImageSummary struct { LastPullTimestamp *time.Time `json:"LastPullTimestamp,omitempty"` // Timestamp when the image was pushed to the registry PushTimestamp *time.Time `json:"PushTimestamp,omitempty"` + // The user who pushed the image to the registry + PushedBy *string `json:"PushedBy,omitempty"` // Timestamp when the image manifest was tagged (if the data is unavailable it falls back to when the image was pushed to the registry) TaggedTimestamp *time.Time `json:"TaggedTimestamp,omitempty"` // Timestamp of the last modification done to the image (from config or the last updated layer) diff --git a/pkg/extensions/search/schema.graphql b/pkg/extensions/search/schema.graphql index 88820c23..7c760956 100644 --- a/pkg/extensions/search/schema.graphql +++ b/pkg/extensions/search/schema.graphql @@ -213,6 +213,10 @@ type ImageSummary { """ PushTimestamp: Time """ + The user who pushed the image to the registry + """ + PushedBy: String + """ Timestamp when the image manifest was tagged (if the data is unavailable it falls back to when the image was pushed to the registry) """ TaggedTimestamp: Time