diff --git a/cli/command/container/create.go b/cli/command/container/create.go index dc0f4735ba..099184d940 100644 --- a/cli/command/container/create.go +++ b/cli/command/container/create.go @@ -21,7 +21,8 @@ import ( ) type createOptions struct { - name string + name string + platform string } // NewCreateCommand creates a new cobra.Command for `docker create` @@ -51,6 +52,7 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command { // with hostname flags.Bool("help", false, "Print usage") + command.AddPlatformFlag(flags, &opts.platform) command.AddTrustVerificationFlags(flags) copts = addFlags(flags) return cmd @@ -62,7 +64,7 @@ func runCreate(dockerCli command.Cli, flags *pflag.FlagSet, opts *createOptions, reportError(dockerCli.Err(), "create", err.Error(), true) return cli.StatusError{StatusCode: 125} } - response, err := createContainer(context.Background(), dockerCli, containerConfig, opts.name) + response, err := createContainer(context.Background(), dockerCli, containerConfig, opts.name, opts.platform) if err != nil { return err } @@ -70,7 +72,7 @@ func runCreate(dockerCli command.Cli, flags *pflag.FlagSet, opts *createOptions, return nil } -func pullImage(ctx context.Context, dockerCli command.Cli, image string, out io.Writer) error { +func pullImage(ctx context.Context, dockerCli command.Cli, image string, platform string, out io.Writer) error { ref, err := reference.ParseNormalizedNamed(image) if err != nil { return err @@ -90,6 +92,7 @@ func pullImage(ctx context.Context, dockerCli command.Cli, image string, out io. options := types.ImageCreateOptions{ RegistryAuth: encodedAuth, + Platform: platform, } responseBody, err := dockerCli.Client().ImageCreate(ctx, image, options) @@ -155,7 +158,7 @@ func newCIDFile(path string) (*cidFile, error) { return &cidFile{path: path, file: f}, nil } -func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig *containerConfig, name string) (*container.ContainerCreateCreatedBody, error) { +func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig *containerConfig, name string, platform string) (*container.ContainerCreateCreatedBody, error) { config := containerConfig.Config hostConfig := containerConfig.HostConfig networkingConfig := containerConfig.NetworkingConfig @@ -198,7 +201,7 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig fmt.Fprintf(stderr, "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef)) // we don't want to write to stdout anything apart from container.ID - if err := pullImage(ctx, dockerCli, config.Image, stderr); err != nil { + if err := pullImage(ctx, dockerCli, config.Image, platform, stderr); err != nil { return nil, err } if taggedRef, ok := namedRef.(reference.NamedTagged); ok && trustedRef != nil { diff --git a/cli/command/container/create_test.go b/cli/command/container/create_test.go index 21354e093a..6bbdb909c0 100644 --- a/cli/command/container/create_test.go +++ b/cli/command/container/create_test.go @@ -5,6 +5,7 @@ import ( "io" "io/ioutil" "os" + "runtime" "strings" "testing" @@ -106,7 +107,7 @@ func TestCreateContainerPullsImageIfMissing(t *testing.T) { }, HostConfig: &container.HostConfig{}, } - body, err := createContainer(context.Background(), cli, config, "name") + body, err := createContainer(context.Background(), cli, config, "name", runtime.GOOS) require.NoError(t, err) expected := container.ContainerCreateCreatedBody{ID: containerID} assert.Equal(t, expected, *body) diff --git a/cli/command/container/run.go b/cli/command/container/run.go index 5b3426b7a0..40fecdc717 100644 --- a/cli/command/container/run.go +++ b/cli/command/container/run.go @@ -29,6 +29,7 @@ type runOptions struct { sigProxy bool name string detachKeys string + platform string } // NewRunCommand create a new `docker run` command @@ -62,6 +63,7 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command { // with hostname flags.Bool("help", false, "Print usage") + command.AddPlatformFlag(flags, &opts.platform) command.AddTrustVerificationFlags(flags) copts = addFlags(flags) return cmd @@ -160,7 +162,7 @@ func runContainer(dockerCli command.Cli, opts *runOptions, copts *containerOptio ctx, cancelFun := context.WithCancel(context.Background()) - createResponse, err := createContainer(ctx, dockerCli, containerConfig, opts.name) + createResponse, err := createContainer(ctx, dockerCli, containerConfig, opts.name, opts.platform) if err != nil { reportError(stderr, cmdPath, err.Error(), true) return runStartContainerErr(err) diff --git a/cli/command/image/build.go b/cli/command/image/build.go index 11c4acb266..77d0c32448 100644 --- a/cli/command/image/build.go +++ b/cli/command/image/build.go @@ -64,6 +64,7 @@ type buildOptions struct { target string imageIDFile string stream bool + platform string } // dockerfileFromStdin returns true when the user specified that the Dockerfile @@ -135,6 +136,7 @@ func NewBuildCommand(dockerCli command.Cli) *cobra.Command { flags.StringVar(&options.imageIDFile, "iidfile", "", "Write the image ID to the file") command.AddTrustVerificationFlags(flags) + command.AddPlatformFlag(flags, &options.platform) flags.BoolVar(&options.squash, "squash", false, "Squash newly built layers into a single new layer") flags.SetAnnotation("squash", "experimental", nil) @@ -374,6 +376,7 @@ func runBuild(dockerCli command.Cli, options buildOptions) error { ExtraHosts: options.extraHosts.GetAll(), Target: options.target, RemoteContext: remote, + Platform: options.platform, } if s != nil { diff --git a/cli/command/image/pull.go b/cli/command/image/pull.go index 1824de2a9b..be653ec926 100644 --- a/cli/command/image/pull.go +++ b/cli/command/image/pull.go @@ -14,8 +14,9 @@ import ( ) type pullOptions struct { - remote string - all bool + remote string + all bool + platform string } // NewPullCommand creates a new `docker pull` command @@ -35,6 +36,8 @@ func NewPullCommand(dockerCli command.Cli) *cobra.Command { flags := cmd.Flags() flags.BoolVarP(&opts.all, "all-tags", "a", false, "Download all tagged images in the repository") + + command.AddPlatformFlag(flags, &opts.platform) command.AddTrustVerificationFlags(flags) return cmd @@ -63,9 +66,9 @@ func runPull(cli command.Cli, opts pullOptions) error { // Check if reference has a digest _, isCanonical := distributionRef.(reference.Canonical) if command.IsTrusted() && !isCanonical { - err = trustedPull(ctx, cli, imgRefAndAuth) + err = trustedPull(ctx, cli, imgRefAndAuth, opts.platform) } else { - err = imagePullPrivileged(ctx, cli, imgRefAndAuth, opts.all) + err = imagePullPrivileged(ctx, cli, imgRefAndAuth, opts.all, opts.platform) } if err != nil { if strings.Contains(err.Error(), "when fetching 'plugin'") { diff --git a/cli/command/image/trust.go b/cli/command/image/trust.go index 2a443661c4..530dfa3b20 100644 --- a/cli/command/image/trust.go +++ b/cli/command/image/trust.go @@ -180,7 +180,7 @@ func imagePushPrivileged(ctx context.Context, cli command.Cli, authConfig types. } // trustedPull handles content trust pulling of an image -func trustedPull(ctx context.Context, cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth) error { +func trustedPull(ctx context.Context, cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth, platform string) error { refs, err := getTrustedPullTargets(cli, imgRefAndAuth) if err != nil { return err @@ -202,7 +202,7 @@ func trustedPull(ctx context.Context, cli command.Cli, imgRefAndAuth trust.Image if err != nil { return err } - if err := imagePullPrivileged(ctx, cli, updatedImgRefAndAuth, false); err != nil { + if err := imagePullPrivileged(ctx, cli, updatedImgRefAndAuth, false, platform); err != nil { return err } @@ -268,7 +268,7 @@ func getTrustedPullTargets(cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth) } // imagePullPrivileged pulls the image and displays it to the output -func imagePullPrivileged(ctx context.Context, cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth, all bool) error { +func imagePullPrivileged(ctx context.Context, cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth, all bool, platform string) error { ref := reference.FamiliarString(imgRefAndAuth.Reference()) encodedAuth, err := command.EncodeAuthToBase64(*imgRefAndAuth.AuthConfig()) @@ -280,8 +280,8 @@ func imagePullPrivileged(ctx context.Context, cli command.Cli, imgRefAndAuth tru RegistryAuth: encodedAuth, PrivilegeFunc: requestPrivilege, All: all, + Platform: platform, } - responseBody, err := cli.Client().ImagePull(ctx, ref, options) if err != nil { return err diff --git a/cli/command/utils.go b/cli/command/utils.go index 3f4acaa2e9..dc543e7dcf 100644 --- a/cli/command/utils.go +++ b/cli/command/utils.go @@ -11,6 +11,7 @@ import ( "github.com/docker/docker/api/types/filters" "github.com/docker/docker/pkg/system" + "github.com/spf13/pflag" ) // CopyToFile writes the content of the reader to the specified file @@ -117,3 +118,10 @@ func PruneFilters(dockerCli Cli, pruneFilters filters.Args) filters.Args { return pruneFilters } + +// AddPlatformFlag adds `platform` to a set of flags for API version 1.32 and later. +func AddPlatformFlag(flags *pflag.FlagSet, target *string) { + flags.StringVar(target, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable") + flags.SetAnnotation("platform", "version", []string{"1.32"}) + flags.SetAnnotation("platform", "experimental", nil) +}