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:
parent
ec38d39c06
commit
d0eb043be5
3 changed files with 92 additions and 16 deletions
|
@ -1,6 +1,8 @@
|
|||
package convert
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
|
@ -18,6 +20,7 @@ const (
|
|||
|
||||
type ImageAnnotations struct {
|
||||
Description string
|
||||
Created *time.Time
|
||||
Licenses string
|
||||
Title string
|
||||
Documentation string
|
||||
|
@ -45,6 +48,17 @@ func GetAnnotationValue(annotations map[string]string, annotationKey, labelKey s
|
|||
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 {
|
||||
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 {
|
||||
created := GetCreated(annotations)
|
||||
if created == nil {
|
||||
created = GetCreated(labels)
|
||||
}
|
||||
|
||||
description := GetDescription(annotations)
|
||||
if description == "" {
|
||||
description = GetDescription(labels)
|
||||
|
@ -123,6 +142,7 @@ func GetAnnotations(annotations, labels map[string]string) ImageAnnotations {
|
|||
}
|
||||
|
||||
return ImageAnnotations{
|
||||
Created: created,
|
||||
Description: description,
|
||||
Title: title,
|
||||
Documentation: documentation,
|
||||
|
@ -138,6 +158,11 @@ func GetIndexAnnotations(
|
|||
indexAnnotations map[string]string,
|
||||
annotationsFromManifest *ImageAnnotations,
|
||||
) ImageAnnotations {
|
||||
created := GetCreated(indexAnnotations)
|
||||
if created == nil {
|
||||
created = annotationsFromManifest.Created
|
||||
}
|
||||
|
||||
description := GetDescription(indexAnnotations)
|
||||
if description == "" {
|
||||
description = annotationsFromManifest.Description
|
||||
|
@ -179,6 +204,7 @@ func GetIndexAnnotations(
|
|||
}
|
||||
|
||||
return ImageAnnotations{
|
||||
Created: created,
|
||||
Description: description,
|
||||
Title: title,
|
||||
Documentation: documentation,
|
||||
|
|
|
@ -69,6 +69,9 @@ func TestLabels(t *testing.T) {
|
|||
// Test various labels
|
||||
labels := make(map[string]string)
|
||||
|
||||
created := convert.GetCreated(labels)
|
||||
So(created, ShouldBeNil)
|
||||
|
||||
desc := convert.GetDescription(labels)
|
||||
So(desc, ShouldEqual, "")
|
||||
|
||||
|
@ -81,11 +84,16 @@ func TestLabels(t *testing.T) {
|
|||
categories := convert.GetCategories(labels)
|
||||
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.AnnotationDescription] = "zot-desc"
|
||||
labels[ispec.AnnotationLicenses] = "zot-license"
|
||||
labels[convert.AnnotationLabels] = "zot-labels"
|
||||
|
||||
created = convert.GetCreated(labels)
|
||||
So(*created, ShouldEqual, expectedCreatedTime)
|
||||
|
||||
desc = convert.GetDescription(labels)
|
||||
So(desc, ShouldEqual, "zot-desc")
|
||||
|
||||
|
@ -101,10 +109,14 @@ func TestLabels(t *testing.T) {
|
|||
labels = make(map[string]string)
|
||||
|
||||
// Use diff key
|
||||
labels[convert.LabelAnnotationCreated] = expectedCreatedTime.Format(time.RFC3339)
|
||||
labels[convert.LabelAnnotationVendor] = "zot-vendor"
|
||||
labels[convert.LabelAnnotationDescription] = "zot-label-desc"
|
||||
labels[ispec.AnnotationLicenses] = "zot-label-license"
|
||||
|
||||
created = convert.GetCreated(labels)
|
||||
So(*created, ShouldEqual, expectedCreatedTime)
|
||||
|
||||
desc = convert.GetDescription(labels)
|
||||
So(desc, ShouldEqual, "zot-label-desc")
|
||||
|
||||
|
@ -113,6 +125,14 @@ func TestLabels(t *testing.T) {
|
|||
|
||||
vendor = convert.GetVendor(labels)
|
||||
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", ""))
|
||||
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{
|
||||
ispec.AnnotationCreated: configCreatedTime.Format(time.RFC3339),
|
||||
ispec.AnnotationDescription: "ConfigDescription",
|
||||
ispec.AnnotationLicenses: "ConfigLicenses",
|
||||
ispec.AnnotationVendor: "ConfigVendor",
|
||||
|
@ -579,6 +605,7 @@ func TestIndexAnnotations(t *testing.T) {
|
|||
}
|
||||
|
||||
manifestAnnotations := map[string]string{
|
||||
ispec.AnnotationCreated: manifestCreatedTime.Format(time.RFC3339),
|
||||
ispec.AnnotationDescription: "ManifestDescription",
|
||||
ispec.AnnotationLicenses: "ManifestLicenses",
|
||||
ispec.AnnotationVendor: "ManifestVendor",
|
||||
|
@ -589,6 +616,7 @@ func TestIndexAnnotations(t *testing.T) {
|
|||
}
|
||||
|
||||
indexAnnotations := map[string]string{
|
||||
ispec.AnnotationCreated: indexCreatedTime.Format(time.RFC3339),
|
||||
ispec.AnnotationDescription: "IndexDescription",
|
||||
ispec.AnnotationLicenses: "IndexLicenses",
|
||||
ispec.AnnotationVendor: "IndexVendor",
|
||||
|
@ -634,6 +662,7 @@ func TestIndexAnnotations(t *testing.T) {
|
|||
imageSummary, _, err := convert.ImageIndex2ImageSummary(ctx, convert.GetFullImageMeta("tag", repoMeta,
|
||||
imageMeta[indexWithAnnotations.DigestStr()]))
|
||||
So(err, ShouldBeNil)
|
||||
So(*imageSummary.LastUpdated, ShouldEqual, indexCreatedTime)
|
||||
So(*imageSummary.Description, ShouldResemble, "IndexDescription")
|
||||
So(*imageSummary.Licenses, ShouldResemble, "IndexLicenses")
|
||||
So(*imageSummary.Title, ShouldResemble, "IndexTitle")
|
||||
|
@ -667,6 +696,7 @@ func TestIndexAnnotations(t *testing.T) {
|
|||
imageSummary, _, err = convert.ImageIndex2ImageSummary(ctx, convert.GetFullImageMeta("tag", repoMeta,
|
||||
imageMeta[digest]))
|
||||
So(err, ShouldBeNil)
|
||||
So(*imageSummary.LastUpdated, ShouldEqual, manifestCreatedTime)
|
||||
So(*imageSummary.Description, ShouldResemble, "ManifestDescription")
|
||||
So(*imageSummary.Licenses, ShouldResemble, "ManifestLicenses")
|
||||
So(*imageSummary.Title, ShouldResemble, "ManifestTitle")
|
||||
|
@ -700,6 +730,7 @@ func TestIndexAnnotations(t *testing.T) {
|
|||
imageSummary, _, err = convert.ImageIndex2ImageSummary(ctx, convert.GetFullImageMeta("tag", repoMeta,
|
||||
imageMeta[digest]))
|
||||
So(err, ShouldBeNil)
|
||||
So(*imageSummary.LastUpdated, ShouldEqual, configCreatedTime)
|
||||
So(*imageSummary.Description, ShouldResemble, "ConfigDescription")
|
||||
So(*imageSummary.Licenses, ShouldResemble, "ConfigLicenses")
|
||||
So(*imageSummary.Title, ShouldResemble, "ConfigTitle")
|
||||
|
@ -715,6 +746,7 @@ func TestIndexAnnotations(t *testing.T) {
|
|||
indexWithMixAnnotations := CreateMultiarchWith().Images(
|
||||
[]Image{
|
||||
CreateImageWith().DefaultLayers().ImageConfig(ispec.Image{
|
||||
Created: &defaultCreatedTime,
|
||||
Config: ispec.ImageConfig{
|
||||
Labels: map[string]string{
|
||||
ispec.AnnotationDescription: "ConfigDescription",
|
||||
|
@ -730,6 +762,7 @@ func TestIndexAnnotations(t *testing.T) {
|
|||
},
|
||||
).Annotations(
|
||||
map[string]string{
|
||||
ispec.AnnotationCreated: indexCreatedTime.Format(time.RFC3339),
|
||||
ispec.AnnotationTitle: "IndexTitle",
|
||||
ispec.AnnotationDocumentation: "IndexDocumentation",
|
||||
ispec.AnnotationSource: "IndexSource",
|
||||
|
@ -754,6 +787,7 @@ func TestIndexAnnotations(t *testing.T) {
|
|||
imageSummary, _, err = convert.ImageIndex2ImageSummary(ctx, convert.GetFullImageMeta("tag", repoMeta,
|
||||
imageMeta[digest]))
|
||||
So(err, ShouldBeNil)
|
||||
So(*imageSummary.LastUpdated, ShouldEqual, indexCreatedTime)
|
||||
So(*imageSummary.Description, ShouldResemble, "ConfigDescription")
|
||||
So(*imageSummary.Licenses, ShouldResemble, "ConfigLicenses")
|
||||
So(*imageSummary.Vendor, ShouldResemble, "ManifestVendor")
|
||||
|
@ -765,7 +799,12 @@ func TestIndexAnnotations(t *testing.T) {
|
|||
err = metaDB.ResetDB()
|
||||
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{
|
||||
Name: "repo",
|
||||
|
@ -785,6 +824,7 @@ func TestIndexAnnotations(t *testing.T) {
|
|||
imageSummary, _, err = convert.ImageIndex2ImageSummary(ctx, convert.GetFullImageMeta("tag", repoMeta,
|
||||
imageMeta[digest]))
|
||||
So(err, ShouldBeNil)
|
||||
So(*imageSummary.LastUpdated, ShouldEqual, defaultCreatedTime)
|
||||
So(*imageSummary.Description, ShouldBeBlank)
|
||||
So(*imageSummary.Licenses, ShouldBeBlank)
|
||||
So(*imageSummary.Vendor, ShouldBeBlank)
|
||||
|
|
|
@ -463,13 +463,18 @@ func ImageIndex2ImageSummary(ctx context.Context, fullImageMeta mTypes.FullImage
|
|||
|
||||
annotations := GetIndexAnnotations(fullImageMeta.Index.Annotations, manifestAnnotations)
|
||||
|
||||
imageLastUpdated := annotations.Created
|
||||
if imageLastUpdated == nil {
|
||||
imageLastUpdated = &indexLastUpdated
|
||||
}
|
||||
|
||||
indexSummary := gql_generated.ImageSummary{
|
||||
RepoName: &repo,
|
||||
Tag: &tag,
|
||||
Digest: &indexDigestStr,
|
||||
MediaType: &indexMediaType,
|
||||
Manifests: manifestSummaries,
|
||||
LastUpdated: &indexLastUpdated,
|
||||
LastUpdated: imageLastUpdated,
|
||||
IsSigned: &isSigned,
|
||||
SignatureInfo: signaturesInfo,
|
||||
Size: ref(strconv.FormatInt(indexSize, 10)),
|
||||
|
@ -493,18 +498,17 @@ func ImageManifest2ImageSummary(ctx context.Context, fullImageMeta mTypes.FullIm
|
|||
manifest := fullImageMeta.Manifests[0]
|
||||
|
||||
var (
|
||||
repoName = fullImageMeta.Repo
|
||||
tag = fullImageMeta.Tag
|
||||
configDigest = manifest.Manifest.Config.Digest.String()
|
||||
configSize = manifest.Manifest.Config.Size
|
||||
manifestDigest = manifest.Digest.String()
|
||||
manifestSize = manifest.Size
|
||||
mediaType = manifest.Manifest.MediaType
|
||||
artifactType = zcommon.GetManifestArtifactType(fullImageMeta.Manifests[0].Manifest)
|
||||
platform = getPlatform(manifest.Config.Platform)
|
||||
imageLastUpdated = zcommon.GetImageLastUpdated(manifest.Config)
|
||||
downloadCount = fullImageMeta.Statistics.DownloadCount
|
||||
isSigned = isImageSigned(fullImageMeta.Signatures)
|
||||
repoName = fullImageMeta.Repo
|
||||
tag = fullImageMeta.Tag
|
||||
configDigest = manifest.Manifest.Config.Digest.String()
|
||||
configSize = manifest.Manifest.Config.Size
|
||||
manifestDigest = manifest.Digest.String()
|
||||
manifestSize = manifest.Size
|
||||
mediaType = manifest.Manifest.MediaType
|
||||
artifactType = zcommon.GetManifestArtifactType(fullImageMeta.Manifests[0].Manifest)
|
||||
platform = getPlatform(manifest.Config.Platform)
|
||||
downloadCount = fullImageMeta.Statistics.DownloadCount
|
||||
isSigned = isImageSigned(fullImageMeta.Signatures)
|
||||
)
|
||||
|
||||
imageSize, imageBlobsMap := getImageBlobsInfo(manifestDigest, manifestSize, configDigest, configSize,
|
||||
|
@ -517,6 +521,12 @@ func ImageManifest2ImageSummary(ctx context.Context, fullImageMeta mTypes.FullIm
|
|||
authors = manifest.Config.Author
|
||||
}
|
||||
|
||||
imageLastUpdated := annotations.Created
|
||||
if imageLastUpdated == nil {
|
||||
configCreated := zcommon.GetImageLastUpdated(manifest.Config)
|
||||
imageLastUpdated = &configCreated
|
||||
}
|
||||
|
||||
historyEntries, err := getAllHistory(manifest.Manifest, manifest.Config)
|
||||
if err != nil {
|
||||
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{
|
||||
Digest: &manifestDigest,
|
||||
ConfigDigest: &configDigest,
|
||||
LastUpdated: &imageLastUpdated,
|
||||
LastUpdated: imageLastUpdated,
|
||||
Size: &imageSizeStr,
|
||||
IsSigned: &isSigned,
|
||||
SignatureInfo: signaturesInfo,
|
||||
|
@ -546,7 +556,7 @@ func ImageManifest2ImageSummary(ctx context.Context, fullImageMeta mTypes.FullIm
|
|||
Digest: &manifestDigest,
|
||||
MediaType: &mediaType,
|
||||
Manifests: []*gql_generated.ManifestSummary{&manifestSummary},
|
||||
LastUpdated: &imageLastUpdated,
|
||||
LastUpdated: imageLastUpdated,
|
||||
IsSigned: &isSigned,
|
||||
SignatureInfo: signaturesInfo,
|
||||
Size: &imageSizeStr,
|
||||
|
|
Loading…
Reference in a new issue