Skip to content

Commit a12b6e8

Browse files
authored
Merge pull request #1239 from fluxcd/oci-created
oci: set created timestamp in the config
2 parents 39efde3 + c635f88 commit a12b6e8

2 files changed

Lines changed: 89 additions & 1 deletion

File tree

oci/push.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,29 @@ func (c *Client) Push(ctx context.Context, url, sourcePath string, opts ...PushO
117117
o.meta.Created = ct.Format(time.RFC3339)
118118
}
119119

120+
annotations := o.meta.ToAnnotations()
121+
createdValue := annotations[CreatedAnnotation]
122+
if createdValue == "" {
123+
createdValue = o.meta.Created
124+
annotations[CreatedAnnotation] = createdValue
125+
}
126+
created, err := time.Parse(time.RFC3339, createdValue)
127+
if err != nil {
128+
return "", fmt.Errorf("invalid created timestamp %q: %w", createdValue, err)
129+
}
130+
120131
img := mutate.MediaType(empty.Image, types.OCIManifestSchema1)
121132
img = mutate.ConfigMediaType(img, CanonicalConfigMediaType)
122-
img = mutate.Annotations(img, o.meta.ToAnnotations()).(gcrv1.Image)
133+
configFile, err := img.ConfigFile()
134+
if err != nil {
135+
return "", fmt.Errorf("reading artifact config failed: %w", err)
136+
}
137+
configFile.Created = gcrv1.Time{Time: created}
138+
img, err = mutate.ConfigFile(img, configFile)
139+
if err != nil {
140+
return "", fmt.Errorf("setting artifact config failed: %w", err)
141+
}
142+
img = mutate.Annotations(img, annotations).(gcrv1.Image)
123143

124144
img, err = mutate.Append(img, mutate.Addendum{Layer: layer})
125145
if err != nil {

oci/push_pull_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,10 @@ func Test_Push_Pull(t *testing.T) {
253253
g.Expect(manifest.Annotations[CreatedAnnotation]).To(BeEquivalentTo(created))
254254
g.Expect(manifest.Annotations[SourceAnnotation]).To(BeEquivalentTo(source))
255255
g.Expect(manifest.Annotations[RevisionAnnotation]).To(BeEquivalentTo(revision))
256+
257+
configFile, err := image.ConfigFile()
258+
g.Expect(err).ToNot(HaveOccurred())
259+
g.Expect(configFile.Created.Time.UTC().Format(time.RFC3339)).To(BeEquivalentTo(created))
256260
}
257261

258262
// Verify media types
@@ -322,6 +326,70 @@ func Test_Push_Pull(t *testing.T) {
322326
}
323327
}
324328

329+
func Test_PushCreatedAnnotationOverridesConfigCreated(t *testing.T) {
330+
g := NewWithT(t)
331+
ctx := context.Background()
332+
c := NewClient(DefaultOptions())
333+
repo := "test-push-created" + randStringRunes(5)
334+
url := fmt.Sprintf("%s/%s:%s", dockerReg, repo, "v0.0.1")
335+
created := time.Date(2026, 6, 10, 12, 0, 0, 0, time.UTC).Format(time.RFC3339)
336+
337+
metadata := Metadata{
338+
Source: "github.com/fluxcd/flux2",
339+
Revision: "rev",
340+
Annotations: map[string]string{
341+
CreatedAnnotation: created,
342+
},
343+
}
344+
345+
_, err := c.Push(ctx, url, "testdata/artifact", WithPushMetadata(metadata))
346+
g.Expect(err).ToNot(HaveOccurred())
347+
348+
image, err := crane.Pull(url)
349+
g.Expect(err).ToNot(HaveOccurred())
350+
351+
manifest, err := image.Manifest()
352+
g.Expect(err).ToNot(HaveOccurred())
353+
g.Expect(manifest.Annotations[CreatedAnnotation]).To(BeEquivalentTo(created))
354+
355+
configFile, err := image.ConfigFile()
356+
g.Expect(err).ToNot(HaveOccurred())
357+
g.Expect(configFile.Created.Time.UTC().Format(time.RFC3339)).To(BeEquivalentTo(created))
358+
}
359+
360+
func Test_PushEmptyCreatedAnnotationUsesDefaultCreated(t *testing.T) {
361+
g := NewWithT(t)
362+
ctx := context.Background()
363+
c := NewClient(DefaultOptions())
364+
repo := "test-push-empty-created" + randStringRunes(5)
365+
url := fmt.Sprintf("%s/%s:%s", dockerReg, repo, "v0.0.1")
366+
367+
metadata := Metadata{
368+
Source: "github.com/fluxcd/flux2",
369+
Revision: "rev",
370+
Annotations: map[string]string{
371+
CreatedAnnotation: "",
372+
},
373+
}
374+
375+
_, err := c.Push(ctx, url, "testdata/artifact", WithPushMetadata(metadata))
376+
g.Expect(err).ToNot(HaveOccurred())
377+
378+
image, err := crane.Pull(url)
379+
g.Expect(err).ToNot(HaveOccurred())
380+
381+
manifest, err := image.Manifest()
382+
g.Expect(err).ToNot(HaveOccurred())
383+
created := manifest.Annotations[CreatedAnnotation]
384+
g.Expect(created).ToNot(BeEmpty())
385+
_, err = time.Parse(time.RFC3339, created)
386+
g.Expect(err).ToNot(HaveOccurred())
387+
388+
configFile, err := image.ConfigFile()
389+
g.Expect(err).ToNot(HaveOccurred())
390+
g.Expect(configFile.Created.Time.UTC().Format(time.RFC3339)).To(BeEquivalentTo(created))
391+
}
392+
325393
func Test_getLayerMediaType(t *testing.T) {
326394
tests := []struct {
327395
name string

0 commit comments

Comments
 (0)