From ccd9d633bb7a4fe7271f2c9e7f83dc66b2d994e9 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Wed, 27 May 2020 11:32:22 -0700 Subject: [PATCH] Set platform on container create API. Previously we only set the platform when performing a pull, which is only initiated if pull always is set, or if the image reference does not exist in the daemon. The daemon now supports specifying which platform you wanted on container create so it can validate the image reference is the platform you thought you were getting. Signed-off-by: Brian Goff --- cli/command/container/client_test.go | 5 ++++- cli/command/container/create.go | 20 ++++++++++++++++++-- cli/command/container/create_test.go | 5 +++++ cli/command/container/run_test.go | 4 +++- 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/cli/command/container/client_test.go b/cli/command/container/client_test.go index b883ad1db1..67ae04904f 100644 --- a/cli/command/container/client_test.go +++ b/cli/command/container/client_test.go @@ -8,6 +8,7 @@ import ( "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" "github.com/docker/docker/client" + specs "github.com/opencontainers/image-spec/specs-go/v1" ) type fakeClient struct { @@ -18,6 +19,7 @@ type fakeClient struct { createContainerFunc func(config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, + platform *specs.Platform, containerName string) (container.ContainerCreateCreatedBody, error) containerStartFunc func(container string, options types.ContainerStartOptions) error imageCreateFunc func(parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error) @@ -69,10 +71,11 @@ func (f *fakeClient) ContainerCreate( config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, + platform *specs.Platform, containerName string, ) (container.ContainerCreateCreatedBody, error) { if f.createContainerFunc != nil { - return f.createContainerFunc(config, hostConfig, networkingConfig, containerName) + return f.createContainerFunc(config, hostConfig, networkingConfig, platform, containerName) } return container.ContainerCreateCreatedBody{}, nil } diff --git a/cli/command/container/create.go b/cli/command/container/create.go index 0141775b5a..743878be18 100644 --- a/cli/command/container/create.go +++ b/cli/command/container/create.go @@ -7,6 +7,7 @@ import ( "os" "regexp" + "github.com/containerd/containerd/platforms" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/image" @@ -14,9 +15,11 @@ import ( "github.com/docker/distribution/reference" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/versions" apiclient "github.com/docker/docker/client" "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/registry" + specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -233,13 +236,26 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig return nil } + var platform *specs.Platform + // Engine API version 1.41 first introduced the option to specify platform on + // create. It will produce an error if you try to set a platform on older API + // versions, so check the API version here to maintain backwards + // compatibility for CLI users. + if opts.platform != "" && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.41") { + p, err := platforms.Parse(opts.platform) + if err != nil { + return nil, errors.Wrap(err, "error parsing specified platform") + } + platform = &p + } + if opts.pull == PullImageAlways { if err := pullAndTagImage(); err != nil { return nil, err } } - response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, opts.name) + response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, opts.name) if err != nil { // Pull image if it does not exist locally and we have the PullImageMissing option. Default behavior. if apiclient.IsErrNotFound(err) && namedRef != nil && opts.pull == PullImageMissing { @@ -250,7 +266,7 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig } var retryErr error - response, retryErr = dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, opts.name) + response, retryErr = dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, opts.name) if retryErr != nil { return nil, retryErr } diff --git a/cli/command/container/create_test.go b/cli/command/container/create_test.go index 4e0bfa07ca..15a9c41749 100644 --- a/cli/command/container/create_test.go +++ b/cli/command/container/create_test.go @@ -18,6 +18,7 @@ import ( "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" "github.com/google/go-cmp/cmp" + specs "github.com/opencontainers/image-spec/specs-go/v1" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" "gotest.tools/v3/fs" @@ -116,6 +117,7 @@ func TestCreateContainerImagePullPolicy(t *testing.T) { config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, + platform *specs.Platform, containerName string, ) (container.ContainerCreateCreatedBody, error) { defer func() { c.ResponseCounter++ }() @@ -184,6 +186,7 @@ func TestNewCreateCommandWithContentTrustErrors(t *testing.T) { createContainerFunc: func(config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, + platform *specs.Platform, containerName string, ) (container.ContainerCreateCreatedBody, error) { return container.ContainerCreateCreatedBody{}, fmt.Errorf("shouldn't try to pull image") @@ -244,6 +247,7 @@ func TestNewCreateCommandWithWarnings(t *testing.T) { createContainerFunc: func(config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, + platform *specs.Platform, containerName string, ) (container.ContainerCreateCreatedBody, error) { return container.ContainerCreateCreatedBody{}, nil @@ -280,6 +284,7 @@ func TestCreateContainerWithProxyConfig(t *testing.T) { createContainerFunc: func(config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, + platform *specs.Platform, containerName string, ) (container.ContainerCreateCreatedBody, error) { sort.Strings(config.Env) diff --git a/cli/command/container/run_test.go b/cli/command/container/run_test.go index 8c5f3c3d85..5821d66e66 100644 --- a/cli/command/container/run_test.go +++ b/cli/command/container/run_test.go @@ -9,13 +9,14 @@ import ( "github.com/docker/cli/internal/test/notary" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" + specs "github.com/opencontainers/image-spec/specs-go/v1" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" ) func TestRunLabel(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ - createContainerFunc: func(_ *container.Config, _ *container.HostConfig, _ *network.NetworkingConfig, _ string) (container.ContainerCreateCreatedBody, error) { + createContainerFunc: func(_ *container.Config, _ *container.HostConfig, _ *network.NetworkingConfig, _ *specs.Platform, _ string) (container.ContainerCreateCreatedBody, error) { return container.ContainerCreateCreatedBody{ ID: "id", }, nil @@ -58,6 +59,7 @@ func TestRunCommandWithContentTrustErrors(t *testing.T) { createContainerFunc: func(config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, + platform *specs.Platform, containerName string, ) (container.ContainerCreateCreatedBody, error) { return container.ContainerCreateCreatedBody{}, fmt.Errorf("shouldn't try to pull image")