Skip to content

Commit 601dd5b

Browse files
committed
containerimage: support SOURCE_DATE_EPOCH for CreatedAt
Fix issue 3167 Signed-off-by: Akihiro Suda <[email protected]>
1 parent 62bdf96 commit 601dd5b

File tree

17 files changed

+2035
-5
lines changed

17 files changed

+2035
-5
lines changed

client/client_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ func TestIntegration(t *testing.T) {
188188
testSourceDateEpochReset,
189189
testSourceDateEpochLocalExporter,
190190
testSourceDateEpochTarExporter,
191+
testSourceDateEpochImageExporter,
191192
testAttestationBundle,
192193
testSBOMScan,
193194
testSBOMScanSingleRef,
@@ -2923,6 +2924,66 @@ func testSourceDateEpochTarExporter(t *testing.T, sb integration.Sandbox) {
29232924

29242925
checkAllReleasable(t, c, sb, true)
29252926
}
2927+
2928+
func testSourceDateEpochImageExporter(t *testing.T, sb integration.Sandbox) {
2929+
cdAddress := sb.ContainerdAddress()
2930+
if cdAddress == "" {
2931+
t.SkipNow()
2932+
}
2933+
// https://github.com/containerd/containerd/commit/133ddce7cf18a1db175150e7a69470dea1bb3132
2934+
integration.CheckContainerdVersion(t, cdAddress, ">= 1.7.0-beta.1")
2935+
2936+
integration.CheckFeatureCompat(t, sb, integration.FeatureSourceDateEpoch)
2937+
requiresLinux(t)
2938+
c, err := New(sb.Context(), sb.Address())
2939+
require.NoError(t, err)
2940+
2941+
busybox := llb.Image("busybox:latest")
2942+
st := llb.Scratch()
2943+
2944+
run := func(cmd string) {
2945+
st = busybox.Run(llb.Shlex(cmd), llb.Dir("/wd")).AddMount("/wd", st)
2946+
}
2947+
2948+
run(`sh -c "echo -n first > foo"`)
2949+
run(`sh -c "echo -n second > bar"`)
2950+
2951+
def, err := st.Marshal(sb.Context())
2952+
require.NoError(t, err)
2953+
2954+
name := strings.ToLower(path.Base(t.Name()))
2955+
tm := time.Date(2015, time.October, 21, 7, 28, 0, 0, time.UTC)
2956+
2957+
_, err = c.Solve(sb.Context(), def, SolveOpt{
2958+
FrontendAttrs: map[string]string{
2959+
"build-arg:SOURCE_DATE_EPOCH": fmt.Sprintf("%d", tm.Unix()),
2960+
},
2961+
Exports: []ExportEntry{
2962+
{
2963+
Type: ExporterImage,
2964+
Attrs: map[string]string{
2965+
"name": name,
2966+
},
2967+
},
2968+
},
2969+
}, nil)
2970+
require.NoError(t, err)
2971+
2972+
ctx := namespaces.WithNamespace(sb.Context(), "buildkit")
2973+
client, err := newContainerd(cdAddress)
2974+
require.NoError(t, err)
2975+
defer client.Close()
2976+
2977+
img, err := client.GetImage(ctx, name)
2978+
require.NoError(t, err)
2979+
require.Equal(t, tm, img.Metadata().CreatedAt)
2980+
2981+
err = client.ImageService().Delete(ctx, name, images.SynchronousDelete())
2982+
require.NoError(t, err)
2983+
2984+
checkAllReleasable(t, c, sb, true)
2985+
}
2986+
29262987
func testFrontendMetadataReturn(t *testing.T, sb integration.Sandbox) {
29272988
requiresLinux(t)
29282989
c, err := New(sb.Context(), sb.Address())

exporter/containerimage/export.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ import (
77
"fmt"
88
"strconv"
99
"strings"
10-
"time"
1110

1211
"github.com/containerd/containerd/content"
1312
"github.com/containerd/containerd/errdefs"
1413
"github.com/containerd/containerd/images"
1514
"github.com/containerd/containerd/leases"
15+
"github.com/containerd/containerd/pkg/epoch"
1616
"github.com/containerd/containerd/platforms"
1717
"github.com/containerd/containerd/remotes/docker"
1818
"github.com/containerd/containerd/rootfs"
@@ -236,22 +236,34 @@ func (e *imageExporterInstance) Export(ctx context.Context, src *exporter.Source
236236
for _, targetName := range targetNames {
237237
if e.opt.Images != nil && e.store {
238238
tagDone := progress.OneOff(ctx, "naming to "+targetName)
239+
240+
// imageClientCtx is used for propagating the epoch to e.opt.Images.Update() and e.opt.Images.Create().
241+
//
242+
// Ideally, we should be able to propagate the epoch via images.Image.CreatedAt.
243+
// However, due to a bug of containerd, we are temporarily stuck with this workaround.
244+
// https://github.com/containerd/containerd/issues/8322
245+
imageClientCtx := ctx
246+
if e.opts.Epoch != nil {
247+
imageClientCtx = epoch.WithSourceDateEpoch(imageClientCtx, e.opts.Epoch)
248+
}
239249
img := images.Image{
240-
Target: *desc,
241-
CreatedAt: time.Now(),
250+
Target: *desc,
251+
// CreatedAt in images.Images is ignored due to a bug of containerd.
252+
// See the comment lines for imageClientCtx.
242253
}
254+
243255
sfx := []string{""}
244256
if nameCanonical {
245257
sfx = append(sfx, "@"+desc.Digest.String())
246258
}
247259
for _, sfx := range sfx {
248260
img.Name = targetName + sfx
249-
if _, err := e.opt.Images.Update(ctx, img); err != nil {
261+
if _, err := e.opt.Images.Update(imageClientCtx, img); err != nil {
250262
if !errors.Is(err, errdefs.ErrNotFound) {
251263
return nil, nil, tagDone(err)
252264
}
253265

254-
if _, err := e.opt.Images.Create(ctx, img); err != nil {
266+
if _, err := e.opt.Images.Create(imageClientCtx, img); err != nil {
255267
return nil, nil, tagDone(err)
256268
}
257269
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.20
55
require (
66
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0
77
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.4.1
8+
github.com/Masterminds/semver/v3 v3.1.0
89
github.com/Microsoft/go-winio v0.6.1
910
github.com/Microsoft/hcsshim v0.10.0-rc.8
1011
github.com/agext/levenshtein v1.2.3

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20191009163259-e802c2cb94ae
124124
github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14=
125125
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
126126
github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
127+
github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk=
127128
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
128129
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
129130
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=

util/testutil/integration/sandbox.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313
"testing"
1414
"time"
1515

16+
"github.com/Masterminds/semver/v3"
17+
containerdpkg "github.com/containerd/containerd"
1618
"github.com/google/shlex"
1719
"github.com/moby/buildkit/util/bklog"
1820
"github.com/pkg/errors"
@@ -367,3 +369,31 @@ func CheckFeatureCompat(t *testing.T, sb Sandbox, reason ...string) {
367369
t.Skipf("%s worker can not currently run this test due to missing features (%s)", sb.Name(), strings.Join(ereasons, ", "))
368370
}
369371
}
372+
373+
func CheckContainerdVersion(t *testing.T, cdAddress, constraint string) {
374+
t.Helper()
375+
constraintSemVer, err := semver.NewConstraint(constraint)
376+
if err != nil {
377+
t.Fatal(err)
378+
}
379+
380+
cdClient, err := containerdpkg.New(cdAddress, containerdpkg.WithTimeout(60*time.Second))
381+
if err != nil {
382+
t.Fatal(err)
383+
}
384+
defer cdClient.Close()
385+
ctx := context.TODO()
386+
cdVersion, err := cdClient.Version(ctx)
387+
if err != nil {
388+
t.Fatal(err)
389+
}
390+
391+
cdVersionSemVer, err := semver.NewVersion(cdVersion.Version)
392+
if err != nil {
393+
t.Fatal(err)
394+
}
395+
396+
if !constraintSemVer.Check(cdVersionSemVer) {
397+
t.Skipf("containerd version %q does not satisfy the constraint %q", cdVersion.Version, constraint)
398+
}
399+
}

vendor/github.com/Masterminds/semver/v3/.gitignore

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/Masterminds/semver/v3/.golangci.yml

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)