diff --git a/cli/command/image/load.go b/cli/command/image/load.go index e80367fdad..a12d671e4b 100644 --- a/cli/command/image/load.go +++ b/cli/command/image/load.go @@ -11,6 +11,7 @@ import ( "github.com/docker/cli/internal/jsonstream" "github.com/moby/moby/client" "github.com/moby/sys/sequential" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -18,7 +19,7 @@ import ( type loadOptions struct { input string quiet bool - platform string + platform []string } // NewLoadCommand creates a new `docker load` command @@ -42,7 +43,7 @@ func NewLoadCommand(dockerCli command.Cli) *cobra.Command { flags.StringVarP(&opts.input, "input", "i", "", "Read from tar archive file, instead of STDIN") flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress the load output") - flags.StringVar(&opts.platform, "platform", "", `Load only the given platform variant. Formatted as "os[/arch[/variant]]" (e.g., "linux/amd64")`) + flags.StringSliceVar(&opts.platform, "platform", []string{}, `Load only the given platform(s). Formatted as a comma-separated list of "os[/arch[/variant]]" (e.g., "linux/amd64,linux/arm64/v8").`) _ = flags.SetAnnotation("platform", "version", []string{"1.48"}) _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) @@ -76,13 +77,16 @@ func runLoad(ctx context.Context, dockerCli command.Cli, opts loadOptions) error options = append(options, client.ImageLoadWithQuiet(true)) } - if opts.platform != "" { - p, err := platforms.Parse(opts.platform) + platformList := []ocispec.Platform{} + for _, p := range opts.platform { + pp, err := platforms.Parse(p) if err != nil { return errors.Wrap(err, "invalid platform") } - // TODO(thaJeztah): change flag-type to support multiple platforms. - options = append(options, client.ImageLoadWithPlatforms(p)) + platformList = append(platformList, pp) + } + if len(platformList) > 0 { + options = append(options, client.ImageLoadWithPlatforms(platformList...)) } response, err := dockerCli.Client().ImageLoad(ctx, input, options...) diff --git a/cli/command/image/load_test.go b/cli/command/image/load_test.go index d066a631e0..7e292c0f72 100644 --- a/cli/command/image/load_test.go +++ b/cli/command/image/load_test.go @@ -104,7 +104,7 @@ func TestNewLoadCommandSuccess(t *testing.T) { }, }, { - name: "with platform", + name: "with-single-platform", args: []string{"--platform", "linux/amd64"}, imageLoadFunc: func(input io.Reader, options ...client.ImageLoadOption) (image.LoadResponse, error) { // FIXME(thaJeztah): need to find appropriate way to test the result of "ImageHistoryWithPlatform" being applied @@ -113,6 +113,22 @@ func TestNewLoadCommandSuccess(t *testing.T) { return image.LoadResponse{Body: io.NopCloser(strings.NewReader("Success"))}, nil }, }, + { + name: "with-comma-separated-platforms", + args: []string{"--platform", "linux/amd64,linux/arm64/v8,linux/riscv64"}, + imageLoadFunc: func(input io.Reader, options ...client.ImageLoadOption) (image.LoadResponse, error) { + assert.Check(t, len(options) > 0) // can be 1 or two depending on whether a terminal is attached :/ + return image.LoadResponse{Body: io.NopCloser(strings.NewReader("Success"))}, nil + }, + }, + { + name: "with-multiple-platform-options", + args: []string{"--platform", "linux/amd64", "--platform", "linux/arm64/v8", "--platform", "linux/riscv64"}, + imageLoadFunc: func(input io.Reader, options ...client.ImageLoadOption) (image.LoadResponse, error) { + assert.Check(t, len(options) > 0) // can be 1 or two depending on whether a terminal is attached :/ + return image.LoadResponse{Body: io.NopCloser(strings.NewReader("Success"))}, nil + }, + }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { diff --git a/cli/command/image/save.go b/cli/command/image/save.go index 49869aa545..2829873bcf 100644 --- a/cli/command/image/save.go +++ b/cli/command/image/save.go @@ -10,6 +10,7 @@ import ( "github.com/docker/cli/cli/command/completion" "github.com/moby/moby/client" "github.com/moby/sys/atomicwriter" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -17,7 +18,7 @@ import ( type saveOptions struct { images []string output string - platform string + platform []string } // NewSaveCommand creates a new `docker save` command @@ -41,7 +42,7 @@ func NewSaveCommand(dockerCli command.Cli) *cobra.Command { flags := cmd.Flags() flags.StringVarP(&opts.output, "output", "o", "", "Write to a file, instead of STDOUT") - flags.StringVar(&opts.platform, "platform", "", `Save only the given platform variant. Formatted as "os[/arch[/variant]]" (e.g., "linux/amd64")`) + flags.StringSliceVar(&opts.platform, "platform", []string{}, `Save only the given platform(s). Formatted as a comma-separated list of "os[/arch[/variant]]" (e.g., "linux/amd64,linux/arm64/v8")`) _ = flags.SetAnnotation("platform", "version", []string{"1.48"}) _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) @@ -51,13 +52,17 @@ func NewSaveCommand(dockerCli command.Cli) *cobra.Command { // runSave performs a save against the engine based on the specified options func runSave(ctx context.Context, dockerCLI command.Cli, opts saveOptions) error { var options []client.ImageSaveOption - if opts.platform != "" { - p, err := platforms.Parse(opts.platform) + + platformList := []ocispec.Platform{} + for _, p := range opts.platform { + pp, err := platforms.Parse(p) if err != nil { return errors.Wrap(err, "invalid platform") } - // TODO(thaJeztah): change flag-type to support multiple platforms. - options = append(options, client.ImageSaveWithPlatforms(p)) + platformList = append(platformList, pp) + } + if len(platformList) > 0 { + options = append(options, client.ImageSaveWithPlatforms(platformList...)) } var output io.Writer diff --git a/cli/command/image/save_test.go b/cli/command/image/save_test.go index 6a3c1bd742..6984ed58fe 100644 --- a/cli/command/image/save_test.go +++ b/cli/command/image/save_test.go @@ -111,6 +111,26 @@ func TestNewSaveCommandSuccess(t *testing.T) { return io.NopCloser(strings.NewReader("")), nil }, }, + { + args: []string{"--platform", "linux/amd64,linux/arm64/v8,linux/riscv64", "arg1"}, + isTerminal: false, + imageSaveFunc: func(images []string, options ...client.ImageSaveOption) (io.ReadCloser, error) { + assert.Assert(t, is.Len(images, 1)) + assert.Check(t, is.Equal("arg1", images[0])) + assert.Check(t, len(options) > 0) // can be 1 or 2 depending on whether a terminal is attached :/ + return io.NopCloser(strings.NewReader("")), nil + }, + }, + { + args: []string{"--platform", "linux/amd64", "--platform", "linux/arm64/v8", "--platform", "linux/riscv64", "arg1"}, + isTerminal: false, + imageSaveFunc: func(images []string, options ...client.ImageSaveOption) (io.ReadCloser, error) { + assert.Assert(t, is.Len(images, 1)) + assert.Check(t, is.Equal("arg1", images[0])) + assert.Check(t, len(options) > 0) // can be 1 or 2 depending on whether a terminal is attached :/ + return io.NopCloser(strings.NewReader("")), nil + }, + }, } for _, tc := range testCases { t.Run(strings.Join(tc.args, " "), func(t *testing.T) { diff --git a/cli/command/image/testdata/load-command-success.with platform.golden b/cli/command/image/testdata/load-command-success.with-comma-separated-platforms.golden similarity index 100% rename from cli/command/image/testdata/load-command-success.with platform.golden rename to cli/command/image/testdata/load-command-success.with-comma-separated-platforms.golden diff --git a/cli/command/image/testdata/load-command-success.with-multiple-platform-options.golden b/cli/command/image/testdata/load-command-success.with-multiple-platform-options.golden new file mode 100644 index 0000000000..51da4200ab --- /dev/null +++ b/cli/command/image/testdata/load-command-success.with-multiple-platform-options.golden @@ -0,0 +1 @@ +Success \ No newline at end of file diff --git a/cli/command/image/testdata/load-command-success.with-single-platform.golden b/cli/command/image/testdata/load-command-success.with-single-platform.golden new file mode 100644 index 0000000000..51da4200ab --- /dev/null +++ b/cli/command/image/testdata/load-command-success.with-single-platform.golden @@ -0,0 +1 @@ +Success \ No newline at end of file diff --git a/docs/reference/commandline/image_load.md b/docs/reference/commandline/image_load.md index bd104ed352..d2d11ef81d 100644 --- a/docs/reference/commandline/image_load.md +++ b/docs/reference/commandline/image_load.md @@ -9,11 +9,11 @@ Load an image from a tar archive or STDIN ### Options -| Name | Type | Default | Description | -|:------------------------------------|:---------|:--------|:-----------------------------------------------------------------------------------------------| -| [`-i`](#input), [`--input`](#input) | `string` | | Read from tar archive file, instead of STDIN | -| [`--platform`](#platform) | `string` | | Load only the given platform variant. Formatted as `os[/arch[/variant]]` (e.g., `linux/amd64`) | -| `-q`, `--quiet` | `bool` | | Suppress the load output | +| Name | Type | Default | Description | +|:------------------------------------|:--------------|:--------|:------------------------------------------------------------------------------------------------------------------------------------| +| [`-i`](#input), [`--input`](#input) | `string` | | Read from tar archive file, instead of STDIN | +| [`--platform`](#platform) | `stringSlice` | | Load only the given platform(s). Formatted as a comma-separated list of `os[/arch[/variant]]` (e.g., `linux/amd64,linux/arm64/v8`). | +| `-q`, `--quiet` | `bool` | | Suppress the load output | diff --git a/docs/reference/commandline/image_save.md b/docs/reference/commandline/image_save.md index 0a01c45b93..25b18a58fe 100644 --- a/docs/reference/commandline/image_save.md +++ b/docs/reference/commandline/image_save.md @@ -9,10 +9,10 @@ Save one or more images to a tar archive (streamed to STDOUT by default) ### Options -| Name | Type | Default | Description | -|:--------------------------|:---------|:--------|:-----------------------------------------------------------------------------------------------| -| `-o`, `--output` | `string` | | Write to a file, instead of STDOUT | -| [`--platform`](#platform) | `string` | | Save only the given platform variant. Formatted as `os[/arch[/variant]]` (e.g., `linux/amd64`) | +| Name | Type | Default | Description | +|:--------------------------|:--------------|:--------|:-----------------------------------------------------------------------------------------------------------------------------------| +| `-o`, `--output` | `string` | | Write to a file, instead of STDOUT | +| [`--platform`](#platform) | `stringSlice` | | Save only the given platform(s). Formatted as a comma-separated list of `os[/arch[/variant]]` (e.g., `linux/amd64,linux/arm64/v8`) | diff --git a/docs/reference/commandline/load.md b/docs/reference/commandline/load.md index 41251b1b13..c1b94ca678 100644 --- a/docs/reference/commandline/load.md +++ b/docs/reference/commandline/load.md @@ -9,11 +9,11 @@ Load an image from a tar archive or STDIN ### Options -| Name | Type | Default | Description | -|:----------------|:---------|:--------|:-----------------------------------------------------------------------------------------------| -| `-i`, `--input` | `string` | | Read from tar archive file, instead of STDIN | -| `--platform` | `string` | | Load only the given platform variant. Formatted as `os[/arch[/variant]]` (e.g., `linux/amd64`) | -| `-q`, `--quiet` | `bool` | | Suppress the load output | +| Name | Type | Default | Description | +|:----------------|:--------------|:--------|:------------------------------------------------------------------------------------------------------------------------------------| +| `-i`, `--input` | `string` | | Read from tar archive file, instead of STDIN | +| `--platform` | `stringSlice` | | Load only the given platform(s). Formatted as a comma-separated list of `os[/arch[/variant]]` (e.g., `linux/amd64,linux/arm64/v8`). | +| `-q`, `--quiet` | `bool` | | Suppress the load output | diff --git a/docs/reference/commandline/save.md b/docs/reference/commandline/save.md index 19e8c353da..8a2d75e751 100644 --- a/docs/reference/commandline/save.md +++ b/docs/reference/commandline/save.md @@ -9,10 +9,10 @@ Save one or more images to a tar archive (streamed to STDOUT by default) ### Options -| Name | Type | Default | Description | -|:-----------------|:---------|:--------|:-----------------------------------------------------------------------------------------------| -| `-o`, `--output` | `string` | | Write to a file, instead of STDOUT | -| `--platform` | `string` | | Save only the given platform variant. Formatted as `os[/arch[/variant]]` (e.g., `linux/amd64`) | +| Name | Type | Default | Description | +|:-----------------|:--------------|:--------|:-----------------------------------------------------------------------------------------------------------------------------------| +| `-o`, `--output` | `string` | | Write to a file, instead of STDOUT | +| `--platform` | `stringSlice` | | Save only the given platform(s). Formatted as a comma-separated list of `os[/arch[/variant]]` (e.g., `linux/amd64,linux/arm64/v8`) |