0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-16 21:56:37 -05:00

feat: Get the image LastUpdated timestamp from annotations (#2240)

Fallback to Created field and the History entries in the image config
only if the annotation "org.opencontainers.image.created" is not available

closes #2210

Signed-off-by: Andrei Aaron <aaaron@luxoft.com>
This commit is contained in:
Andrei Aaron 2024-02-14 19:14:24 +02:00 committed by GitHub
parent ec38d39c06
commit d0eb043be5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 92 additions and 16 deletions

View file

@ -1,6 +1,8 @@
package convert package convert
import ( import (
"time"
ispec "github.com/opencontainers/image-spec/specs-go/v1" ispec "github.com/opencontainers/image-spec/specs-go/v1"
) )
@ -18,6 +20,7 @@ const (
type ImageAnnotations struct { type ImageAnnotations struct {
Description string Description string
Created *time.Time
Licenses string Licenses string
Title string Title string
Documentation string Documentation string
@ -45,6 +48,17 @@ func GetAnnotationValue(annotations map[string]string, annotationKey, labelKey s
return value return value
} }
func GetCreated(annotations map[string]string) *time.Time {
createdStr := GetAnnotationValue(annotations, ispec.AnnotationCreated, LabelAnnotationCreated)
created, err := time.Parse(time.RFC3339, createdStr)
if err != nil {
return nil
}
return &created
}
func GetDescription(annotations map[string]string) string { func GetDescription(annotations map[string]string) string {
return GetAnnotationValue(annotations, ispec.AnnotationDescription, LabelAnnotationDescription) return GetAnnotationValue(annotations, ispec.AnnotationDescription, LabelAnnotationDescription)
} }
@ -82,6 +96,11 @@ func GetCategories(labels map[string]string) string {
} }
func GetAnnotations(annotations, labels map[string]string) ImageAnnotations { func GetAnnotations(annotations, labels map[string]string) ImageAnnotations {
created := GetCreated(annotations)
if created == nil {
created = GetCreated(labels)
}
description := GetDescription(annotations) description := GetDescription(annotations)
if description == "" { if description == "" {
description = GetDescription(labels) description = GetDescription(labels)
@ -123,6 +142,7 @@ func GetAnnotations(annotations, labels map[string]string) ImageAnnotations {
} }
return ImageAnnotations{ return ImageAnnotations{
Created: created,
Description: description, Description: description,
Title: title, Title: title,
Documentation: documentation, Documentation: documentation,
@ -138,6 +158,11 @@ func GetIndexAnnotations(
indexAnnotations map[string]string, indexAnnotations map[string]string,
annotationsFromManifest *ImageAnnotations, annotationsFromManifest *ImageAnnotations,
) ImageAnnotations { ) ImageAnnotations {
created := GetCreated(indexAnnotations)
if created == nil {
created = annotationsFromManifest.Created
}
description := GetDescription(indexAnnotations) description := GetDescription(indexAnnotations)
if description == "" { if description == "" {
description = annotationsFromManifest.Description description = annotationsFromManifest.Description
@ -179,6 +204,7 @@ func GetIndexAnnotations(
} }
return ImageAnnotations{ return ImageAnnotations{
Created: created,
Description: description, Description: description,
Title: title, Title: title,
Documentation: documentation, Documentation: documentation,

View file

@ -69,6 +69,9 @@ func TestLabels(t *testing.T) {
// Test various labels // Test various labels
labels := make(map[string]string) labels := make(map[string]string)
created := convert.GetCreated(labels)
So(created, ShouldBeNil)
desc := convert.GetDescription(labels) desc := convert.GetDescription(labels)
So(desc, ShouldEqual, "") So(desc, ShouldEqual, "")
@ -81,11 +84,16 @@ func TestLabels(t *testing.T) {
categories := convert.GetCategories(labels) categories := convert.GetCategories(labels)
So(categories, ShouldEqual, "") So(categories, ShouldEqual, "")
expectedCreatedTime := time.Date(2010, 1, 1, 12, 0, 0, 0, time.UTC)
labels[ispec.AnnotationCreated] = expectedCreatedTime.Format(time.RFC3339)
labels[ispec.AnnotationVendor] = "zot" labels[ispec.AnnotationVendor] = "zot"
labels[ispec.AnnotationDescription] = "zot-desc" labels[ispec.AnnotationDescription] = "zot-desc"
labels[ispec.AnnotationLicenses] = "zot-license" labels[ispec.AnnotationLicenses] = "zot-license"
labels[convert.AnnotationLabels] = "zot-labels" labels[convert.AnnotationLabels] = "zot-labels"
created = convert.GetCreated(labels)
So(*created, ShouldEqual, expectedCreatedTime)
desc = convert.GetDescription(labels) desc = convert.GetDescription(labels)
So(desc, ShouldEqual, "zot-desc") So(desc, ShouldEqual, "zot-desc")
@ -101,10 +109,14 @@ func TestLabels(t *testing.T) {
labels = make(map[string]string) labels = make(map[string]string)
// Use diff key // Use diff key
labels[convert.LabelAnnotationCreated] = expectedCreatedTime.Format(time.RFC3339)
labels[convert.LabelAnnotationVendor] = "zot-vendor" labels[convert.LabelAnnotationVendor] = "zot-vendor"
labels[convert.LabelAnnotationDescription] = "zot-label-desc" labels[convert.LabelAnnotationDescription] = "zot-label-desc"
labels[ispec.AnnotationLicenses] = "zot-label-license" labels[ispec.AnnotationLicenses] = "zot-label-license"
created = convert.GetCreated(labels)
So(*created, ShouldEqual, expectedCreatedTime)
desc = convert.GetDescription(labels) desc = convert.GetDescription(labels)
So(desc, ShouldEqual, "zot-label-desc") So(desc, ShouldEqual, "zot-label-desc")
@ -113,6 +125,14 @@ func TestLabels(t *testing.T) {
vendor = convert.GetVendor(labels) vendor = convert.GetVendor(labels)
So(vendor, ShouldEqual, "zot-vendor") So(vendor, ShouldEqual, "zot-vendor")
labels = make(map[string]string)
// Handle conversion errors
labels[ispec.AnnotationCreated] = "asd"
created = convert.GetCreated(labels)
So(created, ShouldBeNil)
}) })
} }
@ -568,7 +588,13 @@ func TestIndexAnnotations(t *testing.T) {
metaDB, err := boltdb.New(driver, log.NewLogger("debug", "")) metaDB, err := boltdb.New(driver, log.NewLogger("debug", ""))
So(err, ShouldBeNil) So(err, ShouldBeNil)
defaultCreatedTime := *DefaultTimeRef()
configCreatedTime := time.Date(2009, 1, 1, 12, 0, 0, 0, time.UTC)
manifestCreatedTime := time.Date(2010, 1, 1, 12, 0, 0, 0, time.UTC)
indexCreatedTime := time.Date(2011, 1, 1, 12, 0, 0, 0, time.UTC)
configLabels := map[string]string{ configLabels := map[string]string{
ispec.AnnotationCreated: configCreatedTime.Format(time.RFC3339),
ispec.AnnotationDescription: "ConfigDescription", ispec.AnnotationDescription: "ConfigDescription",
ispec.AnnotationLicenses: "ConfigLicenses", ispec.AnnotationLicenses: "ConfigLicenses",
ispec.AnnotationVendor: "ConfigVendor", ispec.AnnotationVendor: "ConfigVendor",
@ -579,6 +605,7 @@ func TestIndexAnnotations(t *testing.T) {
} }
manifestAnnotations := map[string]string{ manifestAnnotations := map[string]string{
ispec.AnnotationCreated: manifestCreatedTime.Format(time.RFC3339),
ispec.AnnotationDescription: "ManifestDescription", ispec.AnnotationDescription: "ManifestDescription",
ispec.AnnotationLicenses: "ManifestLicenses", ispec.AnnotationLicenses: "ManifestLicenses",
ispec.AnnotationVendor: "ManifestVendor", ispec.AnnotationVendor: "ManifestVendor",
@ -589,6 +616,7 @@ func TestIndexAnnotations(t *testing.T) {
} }
indexAnnotations := map[string]string{ indexAnnotations := map[string]string{
ispec.AnnotationCreated: indexCreatedTime.Format(time.RFC3339),
ispec.AnnotationDescription: "IndexDescription", ispec.AnnotationDescription: "IndexDescription",
ispec.AnnotationLicenses: "IndexLicenses", ispec.AnnotationLicenses: "IndexLicenses",
ispec.AnnotationVendor: "IndexVendor", ispec.AnnotationVendor: "IndexVendor",
@ -634,6 +662,7 @@ func TestIndexAnnotations(t *testing.T) {
imageSummary, _, err := convert.ImageIndex2ImageSummary(ctx, convert.GetFullImageMeta("tag", repoMeta, imageSummary, _, err := convert.ImageIndex2ImageSummary(ctx, convert.GetFullImageMeta("tag", repoMeta,
imageMeta[indexWithAnnotations.DigestStr()])) imageMeta[indexWithAnnotations.DigestStr()]))
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(*imageSummary.LastUpdated, ShouldEqual, indexCreatedTime)
So(*imageSummary.Description, ShouldResemble, "IndexDescription") So(*imageSummary.Description, ShouldResemble, "IndexDescription")
So(*imageSummary.Licenses, ShouldResemble, "IndexLicenses") So(*imageSummary.Licenses, ShouldResemble, "IndexLicenses")
So(*imageSummary.Title, ShouldResemble, "IndexTitle") So(*imageSummary.Title, ShouldResemble, "IndexTitle")
@ -667,6 +696,7 @@ func TestIndexAnnotations(t *testing.T) {
imageSummary, _, err = convert.ImageIndex2ImageSummary(ctx, convert.GetFullImageMeta("tag", repoMeta, imageSummary, _, err = convert.ImageIndex2ImageSummary(ctx, convert.GetFullImageMeta("tag", repoMeta,
imageMeta[digest])) imageMeta[digest]))
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(*imageSummary.LastUpdated, ShouldEqual, manifestCreatedTime)
So(*imageSummary.Description, ShouldResemble, "ManifestDescription") So(*imageSummary.Description, ShouldResemble, "ManifestDescription")
So(*imageSummary.Licenses, ShouldResemble, "ManifestLicenses") So(*imageSummary.Licenses, ShouldResemble, "ManifestLicenses")
So(*imageSummary.Title, ShouldResemble, "ManifestTitle") So(*imageSummary.Title, ShouldResemble, "ManifestTitle")
@ -700,6 +730,7 @@ func TestIndexAnnotations(t *testing.T) {
imageSummary, _, err = convert.ImageIndex2ImageSummary(ctx, convert.GetFullImageMeta("tag", repoMeta, imageSummary, _, err = convert.ImageIndex2ImageSummary(ctx, convert.GetFullImageMeta("tag", repoMeta,
imageMeta[digest])) imageMeta[digest]))
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(*imageSummary.LastUpdated, ShouldEqual, configCreatedTime)
So(*imageSummary.Description, ShouldResemble, "ConfigDescription") So(*imageSummary.Description, ShouldResemble, "ConfigDescription")
So(*imageSummary.Licenses, ShouldResemble, "ConfigLicenses") So(*imageSummary.Licenses, ShouldResemble, "ConfigLicenses")
So(*imageSummary.Title, ShouldResemble, "ConfigTitle") So(*imageSummary.Title, ShouldResemble, "ConfigTitle")
@ -715,6 +746,7 @@ func TestIndexAnnotations(t *testing.T) {
indexWithMixAnnotations := CreateMultiarchWith().Images( indexWithMixAnnotations := CreateMultiarchWith().Images(
[]Image{ []Image{
CreateImageWith().DefaultLayers().ImageConfig(ispec.Image{ CreateImageWith().DefaultLayers().ImageConfig(ispec.Image{
Created: &defaultCreatedTime,
Config: ispec.ImageConfig{ Config: ispec.ImageConfig{
Labels: map[string]string{ Labels: map[string]string{
ispec.AnnotationDescription: "ConfigDescription", ispec.AnnotationDescription: "ConfigDescription",
@ -730,6 +762,7 @@ func TestIndexAnnotations(t *testing.T) {
}, },
).Annotations( ).Annotations(
map[string]string{ map[string]string{
ispec.AnnotationCreated: indexCreatedTime.Format(time.RFC3339),
ispec.AnnotationTitle: "IndexTitle", ispec.AnnotationTitle: "IndexTitle",
ispec.AnnotationDocumentation: "IndexDocumentation", ispec.AnnotationDocumentation: "IndexDocumentation",
ispec.AnnotationSource: "IndexSource", ispec.AnnotationSource: "IndexSource",
@ -754,6 +787,7 @@ func TestIndexAnnotations(t *testing.T) {
imageSummary, _, err = convert.ImageIndex2ImageSummary(ctx, convert.GetFullImageMeta("tag", repoMeta, imageSummary, _, err = convert.ImageIndex2ImageSummary(ctx, convert.GetFullImageMeta("tag", repoMeta,
imageMeta[digest])) imageMeta[digest]))
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(*imageSummary.LastUpdated, ShouldEqual, indexCreatedTime)
So(*imageSummary.Description, ShouldResemble, "ConfigDescription") So(*imageSummary.Description, ShouldResemble, "ConfigDescription")
So(*imageSummary.Licenses, ShouldResemble, "ConfigLicenses") So(*imageSummary.Licenses, ShouldResemble, "ConfigLicenses")
So(*imageSummary.Vendor, ShouldResemble, "ManifestVendor") So(*imageSummary.Vendor, ShouldResemble, "ManifestVendor")
@ -765,7 +799,12 @@ func TestIndexAnnotations(t *testing.T) {
err = metaDB.ResetDB() err = metaDB.ResetDB()
So(err, ShouldBeNil) So(err, ShouldBeNil)
//-------------------------------------------------------- //--------------------------------------------------------
indexWithNoAnnotations := CreateRandomMultiarch() indexWithNoAnnotations := CreateMultiarchWith().Images(
[]Image{
CreateImageWith().RandomLayers(1, 10).DefaultConfig().Build(),
CreateImageWith().RandomLayers(1, 10).DefaultConfig().Build(),
},
).Build()
ctx, err = ociutils.InitializeTestMetaDB(ctx, metaDB, ociutils.Repo{ ctx, err = ociutils.InitializeTestMetaDB(ctx, metaDB, ociutils.Repo{
Name: "repo", Name: "repo",
@ -785,6 +824,7 @@ func TestIndexAnnotations(t *testing.T) {
imageSummary, _, err = convert.ImageIndex2ImageSummary(ctx, convert.GetFullImageMeta("tag", repoMeta, imageSummary, _, err = convert.ImageIndex2ImageSummary(ctx, convert.GetFullImageMeta("tag", repoMeta,
imageMeta[digest])) imageMeta[digest]))
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(*imageSummary.LastUpdated, ShouldEqual, defaultCreatedTime)
So(*imageSummary.Description, ShouldBeBlank) So(*imageSummary.Description, ShouldBeBlank)
So(*imageSummary.Licenses, ShouldBeBlank) So(*imageSummary.Licenses, ShouldBeBlank)
So(*imageSummary.Vendor, ShouldBeBlank) So(*imageSummary.Vendor, ShouldBeBlank)

View file

@ -463,13 +463,18 @@ func ImageIndex2ImageSummary(ctx context.Context, fullImageMeta mTypes.FullImage
annotations := GetIndexAnnotations(fullImageMeta.Index.Annotations, manifestAnnotations) annotations := GetIndexAnnotations(fullImageMeta.Index.Annotations, manifestAnnotations)
imageLastUpdated := annotations.Created
if imageLastUpdated == nil {
imageLastUpdated = &indexLastUpdated
}
indexSummary := gql_generated.ImageSummary{ indexSummary := gql_generated.ImageSummary{
RepoName: &repo, RepoName: &repo,
Tag: &tag, Tag: &tag,
Digest: &indexDigestStr, Digest: &indexDigestStr,
MediaType: &indexMediaType, MediaType: &indexMediaType,
Manifests: manifestSummaries, Manifests: manifestSummaries,
LastUpdated: &indexLastUpdated, LastUpdated: imageLastUpdated,
IsSigned: &isSigned, IsSigned: &isSigned,
SignatureInfo: signaturesInfo, SignatureInfo: signaturesInfo,
Size: ref(strconv.FormatInt(indexSize, 10)), Size: ref(strconv.FormatInt(indexSize, 10)),
@ -493,18 +498,17 @@ func ImageManifest2ImageSummary(ctx context.Context, fullImageMeta mTypes.FullIm
manifest := fullImageMeta.Manifests[0] manifest := fullImageMeta.Manifests[0]
var ( var (
repoName = fullImageMeta.Repo repoName = fullImageMeta.Repo
tag = fullImageMeta.Tag tag = fullImageMeta.Tag
configDigest = manifest.Manifest.Config.Digest.String() configDigest = manifest.Manifest.Config.Digest.String()
configSize = manifest.Manifest.Config.Size configSize = manifest.Manifest.Config.Size
manifestDigest = manifest.Digest.String() manifestDigest = manifest.Digest.String()
manifestSize = manifest.Size manifestSize = manifest.Size
mediaType = manifest.Manifest.MediaType mediaType = manifest.Manifest.MediaType
artifactType = zcommon.GetManifestArtifactType(fullImageMeta.Manifests[0].Manifest) artifactType = zcommon.GetManifestArtifactType(fullImageMeta.Manifests[0].Manifest)
platform = getPlatform(manifest.Config.Platform) platform = getPlatform(manifest.Config.Platform)
imageLastUpdated = zcommon.GetImageLastUpdated(manifest.Config) downloadCount = fullImageMeta.Statistics.DownloadCount
downloadCount = fullImageMeta.Statistics.DownloadCount isSigned = isImageSigned(fullImageMeta.Signatures)
isSigned = isImageSigned(fullImageMeta.Signatures)
) )
imageSize, imageBlobsMap := getImageBlobsInfo(manifestDigest, manifestSize, configDigest, configSize, imageSize, imageBlobsMap := getImageBlobsInfo(manifestDigest, manifestSize, configDigest, configSize,
@ -517,6 +521,12 @@ func ImageManifest2ImageSummary(ctx context.Context, fullImageMeta mTypes.FullIm
authors = manifest.Config.Author authors = manifest.Config.Author
} }
imageLastUpdated := annotations.Created
if imageLastUpdated == nil {
configCreated := zcommon.GetImageLastUpdated(manifest.Config)
imageLastUpdated = &configCreated
}
historyEntries, err := getAllHistory(manifest.Manifest, manifest.Config) historyEntries, err := getAllHistory(manifest.Manifest, manifest.Config)
if err != nil { if err != nil {
graphql.AddError(ctx, gqlerror.Errorf("error generating history on tag %s in repo %s: "+ graphql.AddError(ctx, gqlerror.Errorf("error generating history on tag %s in repo %s: "+
@ -528,7 +538,7 @@ func ImageManifest2ImageSummary(ctx context.Context, fullImageMeta mTypes.FullIm
manifestSummary := gql_generated.ManifestSummary{ manifestSummary := gql_generated.ManifestSummary{
Digest: &manifestDigest, Digest: &manifestDigest,
ConfigDigest: &configDigest, ConfigDigest: &configDigest,
LastUpdated: &imageLastUpdated, LastUpdated: imageLastUpdated,
Size: &imageSizeStr, Size: &imageSizeStr,
IsSigned: &isSigned, IsSigned: &isSigned,
SignatureInfo: signaturesInfo, SignatureInfo: signaturesInfo,
@ -546,7 +556,7 @@ func ImageManifest2ImageSummary(ctx context.Context, fullImageMeta mTypes.FullIm
Digest: &manifestDigest, Digest: &manifestDigest,
MediaType: &mediaType, MediaType: &mediaType,
Manifests: []*gql_generated.ManifestSummary{&manifestSummary}, Manifests: []*gql_generated.ManifestSummary{&manifestSummary},
LastUpdated: &imageLastUpdated, LastUpdated: imageLastUpdated,
IsSigned: &isSigned, IsSigned: &isSigned,
SignatureInfo: signaturesInfo, SignatureInfo: signaturesInfo,
Size: &imageSizeStr, Size: &imageSizeStr,