Skip to content

Commit ddef55c

Browse files
committed
Output correct image ID when using Docker with the containerd-snapshotter.
Prior to this change, the following command emits the wrong image ID when buildx uses the "docker-container" driver and Docker is configured with the containerd-snapshotter. $ docker buildx build --load --iidfile=img.txt $ docker run --rm "$(cat img.txt)" echo hello docker: Error response from daemon: No such image: sha256:4ac37e81e00f242010e42f3251094e47de6100e01d25e9bd0feac6b8906976df. See 'docker run --help'. The problem is that buildx is outputing the incorrect image ID in this scenario (it's outputing the container image config digest, instead of the container image digest used by the containerd-snapshotter). This commit fixes this. See moby/moby#45458. Signed-off-by: Cesar Talledo <[email protected]>
1 parent bad5063 commit ddef55c

File tree

2 files changed

+59
-4
lines changed

2 files changed

+59
-4
lines changed

commands/build.go

+51-3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"github.com/docker/buildx/util/cobrautil"
3434
"github.com/docker/buildx/util/confutil"
3535
"github.com/docker/buildx/util/desktop"
36+
"github.com/docker/buildx/util/dockerutil"
3637
"github.com/docker/buildx/util/ioset"
3738
"github.com/docker/buildx/util/metricutil"
3839
"github.com/docker/buildx/util/osutil"
@@ -321,12 +322,30 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
321322
if err != nil {
322323
return err
323324
}
325+
324326
_, err = b.LoadNodes(ctx)
325327
if err != nil {
326328
return err
327329
}
328330
driverType := b.Driver
329331

332+
// Check if the image will be loaded (i.e., --load or --output=type=docker is set)
333+
loadImage := opts.ExportLoad
334+
for _, e := range opts.Exports {
335+
if e.Type == "docker" {
336+
loadImage = true
337+
}
338+
}
339+
340+
preferImageDigest := false
341+
if loadImage {
342+
// If loading the image and the local Docker instance is using the
343+
// containerd snapshotter, output the image digest (rather than the image
344+
// config digest), so that "docker run <digest>" will work. See
345+
// https://github.com/moby/moby/issues/45458.
346+
preferImageDigest = isDockerUsingContainerdSnapshotter(ctx, dockerCli, opts.Exports)
347+
}
348+
330349
var term bool
331350
if _, err := console.ConsoleFromFile(os.Stderr); err == nil {
332351
term = true
@@ -377,12 +396,12 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
377396
case progressui.RawJSONMode:
378397
// no additional display
379398
case progressui.QuietMode:
380-
fmt.Println(getImageID(resp.ExporterResponse))
399+
fmt.Println(getImageID(resp.ExporterResponse, preferImageDigest))
381400
default:
382401
desktop.PrintBuildDetails(os.Stderr, printer.BuildRefs(), term)
383402
}
384403
if options.imageIDFile != "" {
385-
if err := os.WriteFile(options.imageIDFile, []byte(getImageID(resp.ExporterResponse)), 0644); err != nil {
404+
if err := os.WriteFile(options.imageIDFile, []byte(getImageID(resp.ExporterResponse, preferImageDigest)), 0644); err != nil {
386405
return errors.Wrap(err, "writing image ID file")
387406
}
388407
}
@@ -412,14 +431,43 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
412431
}
413432

414433
// getImageID returns the image ID - the digest of the image config
415-
func getImageID(resp map[string]string) string {
434+
func getImageID(resp map[string]string, preferImageDigest bool) string {
416435
dgst := resp[exptypes.ExporterImageDigestKey]
436+
if preferImageDigest {
437+
return dgst
438+
}
417439
if v, ok := resp[exptypes.ExporterImageConfigDigestKey]; ok {
418440
dgst = v
419441
}
420442
return dgst
421443
}
422444

445+
func isDockerUsingContainerdSnapshotter(ctx context.Context, dockerCli command.Cli, exports []*controllerapi.ExportEntry) bool {
446+
usingContainerdSnapshotter := false
447+
448+
// If the build command has "-o type=docker,context=<mycontext>", then pass that
449+
// context to docker.Features() below, so we can find out if the associated
450+
// Docker engine is using the containerd snapshotter.
451+
dockerCtx := ""
452+
for _, e := range exports {
453+
if e.Type == "docker" {
454+
for k, v := range e.Attrs {
455+
if k == "context" {
456+
dockerCtx = v
457+
}
458+
}
459+
}
460+
}
461+
462+
docker := dockerutil.NewClient(dockerCli)
463+
features := docker.Features(ctx, dockerCtx)
464+
if features[dockerutil.OCIImporter] {
465+
usingContainerdSnapshotter = true
466+
}
467+
468+
return usingContainerdSnapshotter
469+
}
470+
423471
func runBasicBuild(ctx context.Context, dockerCli command.Cli, opts *controllerapi.BuildOptions, printer *progress.Printer) (*client.SolveResponse, *build.Inputs, error) {
424472
resp, res, dfmap, err := cbuild.RunBuild(ctx, dockerCli, opts, dockerCli.In(), printer, false)
425473
if res != nil {

tests/build.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -399,18 +399,25 @@ func testImageIDOutput(t *testing.T, sb integration.Sandbox) {
399399

400400
require.Equal(t, dgst.String(), strings.TrimSpace(stdout.String()))
401401

402+
// read the md.json file
402403
dt, err = os.ReadFile(filepath.Join(targetDir, "md.json"))
403404
require.NoError(t, err)
404405

405406
type mdT struct {
407+
Digest string `json:"containerimage.digest"`
406408
ConfigDigest string `json:"containerimage.config.digest"`
407409
}
410+
408411
var md mdT
409412
err = json.Unmarshal(dt, &md)
410413
require.NoError(t, err)
411414

412415
require.NotEmpty(t, md.ConfigDigest)
413-
require.Equal(t, dgst, digest.Digest(md.ConfigDigest))
416+
require.NotEmpty(t, md.Digest)
417+
418+
// verify the image ID output is correct
419+
// XXX: improve this by checking that it's one of the two expected digests depending on the scenario.
420+
require.Contains(t, []digest.Digest{digest.Digest(md.ConfigDigest), digest.Digest(md.Digest)}, dgst)
414421
}
415422

416423
func testBuildMobyFromLocalImage(t *testing.T, sb integration.Sandbox) {

0 commit comments

Comments
 (0)