Skip to content

Commit

Permalink
containerimage: support SOURCE_DATE_EPOCH for CreatedAt
Browse files Browse the repository at this point in the history
Fix issue 3167

Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
  • Loading branch information
AkihiroSuda committed Jun 29, 2023
1 parent 62bdf96 commit 601dd5b
Show file tree
Hide file tree
Showing 17 changed files with 2,035 additions and 5 deletions.
61 changes: 61 additions & 0 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ func TestIntegration(t *testing.T) {
testSourceDateEpochReset,
testSourceDateEpochLocalExporter,
testSourceDateEpochTarExporter,
testSourceDateEpochImageExporter,
testAttestationBundle,
testSBOMScan,
testSBOMScanSingleRef,
Expand Down Expand Up @@ -2923,6 +2924,66 @@ func testSourceDateEpochTarExporter(t *testing.T, sb integration.Sandbox) {

checkAllReleasable(t, c, sb, true)
}

func testSourceDateEpochImageExporter(t *testing.T, sb integration.Sandbox) {
cdAddress := sb.ContainerdAddress()
if cdAddress == "" {
t.SkipNow()
}
// https://github.com/containerd/containerd/commit/133ddce7cf18a1db175150e7a69470dea1bb3132
integration.CheckContainerdVersion(t, cdAddress, ">= 1.7.0-beta.1")

integration.CheckFeatureCompat(t, sb, integration.FeatureSourceDateEpoch)
requiresLinux(t)
c, err := New(sb.Context(), sb.Address())
require.NoError(t, err)

busybox := llb.Image("busybox:latest")
st := llb.Scratch()

run := func(cmd string) {
st = busybox.Run(llb.Shlex(cmd), llb.Dir("/wd")).AddMount("/wd", st)
}

run(`sh -c "echo -n first > foo"`)
run(`sh -c "echo -n second > bar"`)

def, err := st.Marshal(sb.Context())
require.NoError(t, err)

name := strings.ToLower(path.Base(t.Name()))
tm := time.Date(2015, time.October, 21, 7, 28, 0, 0, time.UTC)

_, err = c.Solve(sb.Context(), def, SolveOpt{
FrontendAttrs: map[string]string{
"build-arg:SOURCE_DATE_EPOCH": fmt.Sprintf("%d", tm.Unix()),
},
Exports: []ExportEntry{
{
Type: ExporterImage,
Attrs: map[string]string{
"name": name,
},
},
},
}, nil)
require.NoError(t, err)

ctx := namespaces.WithNamespace(sb.Context(), "buildkit")
client, err := newContainerd(cdAddress)
require.NoError(t, err)
defer client.Close()

img, err := client.GetImage(ctx, name)
require.NoError(t, err)
require.Equal(t, tm, img.Metadata().CreatedAt)

err = client.ImageService().Delete(ctx, name, images.SynchronousDelete())
require.NoError(t, err)

checkAllReleasable(t, c, sb, true)
}

func testFrontendMetadataReturn(t *testing.T, sb integration.Sandbox) {
requiresLinux(t)
c, err := New(sb.Context(), sb.Address())
Expand Down
22 changes: 17 additions & 5 deletions exporter/containerimage/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import (
"fmt"
"strconv"
"strings"
"time"

"github.com/containerd/containerd/content"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/leases"
"github.com/containerd/containerd/pkg/epoch"
"github.com/containerd/containerd/platforms"
"github.com/containerd/containerd/remotes/docker"
"github.com/containerd/containerd/rootfs"
Expand Down Expand Up @@ -236,22 +236,34 @@ func (e *imageExporterInstance) Export(ctx context.Context, src *exporter.Source
for _, targetName := range targetNames {
if e.opt.Images != nil && e.store {
tagDone := progress.OneOff(ctx, "naming to "+targetName)

// imageClientCtx is used for propagating the epoch to e.opt.Images.Update() and e.opt.Images.Create().
//
// Ideally, we should be able to propagate the epoch via images.Image.CreatedAt.
// However, due to a bug of containerd, we are temporarily stuck with this workaround.
// https://github.com/containerd/containerd/issues/8322
imageClientCtx := ctx
if e.opts.Epoch != nil {
imageClientCtx = epoch.WithSourceDateEpoch(imageClientCtx, e.opts.Epoch)
}
img := images.Image{
Target: *desc,
CreatedAt: time.Now(),
Target: *desc,
// CreatedAt in images.Images is ignored due to a bug of containerd.
// See the comment lines for imageClientCtx.
}

sfx := []string{""}
if nameCanonical {
sfx = append(sfx, "@"+desc.Digest.String())
}
for _, sfx := range sfx {
img.Name = targetName + sfx
if _, err := e.opt.Images.Update(ctx, img); err != nil {
if _, err := e.opt.Images.Update(imageClientCtx, img); err != nil {
if !errors.Is(err, errdefs.ErrNotFound) {
return nil, nil, tagDone(err)
}

if _, err := e.opt.Images.Create(ctx, img); err != nil {
if _, err := e.opt.Images.Create(imageClientCtx, img); err != nil {
return nil, nil, tagDone(err)
}
}
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.20
require (
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.4.1
github.com/Masterminds/semver/v3 v3.1.0
github.com/Microsoft/go-winio v0.6.1
github.com/Microsoft/hcsshim v0.10.0-rc.8
github.com/agext/levenshtein v1.2.3
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20191009163259-e802c2cb94ae
github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk=
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
Expand Down
30 changes: 30 additions & 0 deletions util/testutil/integration/sandbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
"testing"
"time"

"github.com/Masterminds/semver/v3"
containerdpkg "github.com/containerd/containerd"
"github.com/google/shlex"
"github.com/moby/buildkit/util/bklog"
"github.com/pkg/errors"
Expand Down Expand Up @@ -367,3 +369,31 @@ func CheckFeatureCompat(t *testing.T, sb Sandbox, reason ...string) {
t.Skipf("%s worker can not currently run this test due to missing features (%s)", sb.Name(), strings.Join(ereasons, ", "))
}
}

func CheckContainerdVersion(t *testing.T, cdAddress, constraint string) {
t.Helper()
constraintSemVer, err := semver.NewConstraint(constraint)
if err != nil {
t.Fatal(err)
}

cdClient, err := containerdpkg.New(cdAddress, containerdpkg.WithTimeout(60*time.Second))
if err != nil {
t.Fatal(err)
}
defer cdClient.Close()
ctx := context.TODO()
cdVersion, err := cdClient.Version(ctx)
if err != nil {
t.Fatal(err)
}

cdVersionSemVer, err := semver.NewVersion(cdVersion.Version)
if err != nil {
t.Fatal(err)
}

if !constraintSemVer.Check(cdVersionSemVer) {
t.Skipf("containerd version %q does not satisfy the constraint %q", cdVersion.Version, constraint)
}
}
1 change: 1 addition & 0 deletions vendor/github.com/Masterminds/semver/v3/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions vendor/github.com/Masterminds/semver/v3/.golangci.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 601dd5b

Please sign in to comment.