mirror of
https://github.com/project-zot/zot.git
synced 2026-06-17 21:17:58 +08:00
fix(search): expose LastPullTimestamp and PushedBy on index ImageSummary (#3865)
ImageIndex2ImageSummary was missing LastPullTimestamp assignment, causing multi-arch image queries to always return null for this field. Also adds the PushedBy field (already stored in MetaDB) to the GraphQL schema and both conversion paths (manifest and index). Signed-off-by: cainydev <wajo432@gmail.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user