From 3667c6a05b1a2c2c6b680e2323fbcc9ed76947a8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 11 Feb 2021 20:32:47 -0800 Subject: [PATCH] pull: resolve the image config and set it on stored image (#326) Previously `pull` would not properly retain the image configuration which contains the entrypoint, working directory, user, and more. This resulted in broken images if they were pulled then subsequently pushed. This commit fetches the image config, which must be done separately, and then ensures that the configuration is set on the exported image into the local img state store. --- client/pull.go | 27 ++++++++++++++++++++++++++- pull_test.go | 19 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/client/pull.go b/client/pull.go index 49d1ffe17..bc85f985c 100644 --- a/client/pull.go +++ b/client/pull.go @@ -9,8 +9,11 @@ import ( "github.com/moby/buildkit/cache" "github.com/moby/buildkit/exporter" imageexporter "github.com/moby/buildkit/exporter/containerimage" + "github.com/moby/buildkit/exporter/containerimage/exptypes" "github.com/moby/buildkit/source" "github.com/moby/buildkit/source/containerimage" + "github.com/moby/buildkit/util/imageutil" + "github.com/moby/buildkit/util/pull" ) // Pull retrieves an image from a remote registry. @@ -66,6 +69,22 @@ func (c *Client) Pull(ctx context.Context, image string) (*ListedImage, error) { if err != nil { return nil, err } + + // Fetch the configuration for the image. src.Resolve below fetches + // all the data layers but does NOT fetch the configuration. We need this + // to set it for export later. + _, configBytes, err := imageutil.Config( + ctx, + image, + pull.NewResolver(ctx, opt.RegistryHosts, sm, opt.ImageStore, source.ResolveModeDefault, image), + opt.ContentStore, + opt.LeaseManager, + nil, + ) + if err != nil { + return nil, err + } + s, err := src.Resolve(ctx, identifier, sm) if err != nil { return nil, err @@ -99,7 +118,13 @@ func (c *Client) Pull(ctx context.Context, image string) (*ListedImage, error) { if err != nil { return nil, err } - if _, err := e.Export(ctx, exporter.Source{Ref: ref}); err != nil { + if _, err := e.Export(ctx, exporter.Source{ + Ref: ref, + Metadata: map[string][]byte{ + // This sets the image config to preserve entrypoint, workingdir, etc. + exptypes.ExporterImageConfigKey: configBytes, + }, + }); err != nil { return nil, err } diff --git a/pull_test.go b/pull_test.go index c6bfc9139..051db8bd8 100644 --- a/pull_test.go +++ b/pull_test.go @@ -1,8 +1,11 @@ package main import ( + "encoding/json" "strings" "testing" + + ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) func TestPullFromDefaultRegistry(t *testing.T) { @@ -29,3 +32,19 @@ func TestPullIsInListOutput(t *testing.T) { t.Fatalf("expected busybox:latest in ls output, got: %s", out) } } + +func TestPullRetainsConfig(t *testing.T) { + // Test an official image, + run(t, "pull", "alpine") + + out := run(t, "inspect", "alpine") + + var image ocispec.Image + if err := json.Unmarshal([]byte(out), &image); err != nil { + t.Fatalf("error decoding JSON: %s", err) + } + + if len(image.Config.Cmd) == 0 { + t.Fatal("image config should be populated") + } +}