Skip to content

Commit a20eb45

Browse files
vvolandthaJeztah
authored andcommitted
image/save: Add --platform
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com> Signed-off-by: Sebastiaan van Stijn <github@gone.nl> Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
1 parent 6a78e92 commit a20eb45

4 files changed

Lines changed: 90 additions & 9 deletions

File tree

cli/command/image/save.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"io"
66

7+
"github.com/containerd/platforms"
78
"github.com/docker/cli/cli"
89
"github.com/docker/cli/cli/command"
910
"github.com/docker/cli/cli/command/completion"
@@ -13,8 +14,9 @@ import (
1314
)
1415

1516
type saveOptions struct {
16-
images []string
17-
output string
17+
images []string
18+
output string
19+
platform string
1820
}
1921

2022
// NewSaveCommand creates a new `docker save` command
@@ -38,7 +40,10 @@ func NewSaveCommand(dockerCli command.Cli) *cobra.Command {
3840
flags := cmd.Flags()
3941

4042
flags.StringVarP(&opts.output, "output", "o", "", "Write to a file, instead of STDOUT")
43+
flags.StringVar(&opts.platform, "platform", "", `Save only the given platform variant. Formatted as "os[/arch[/variant]]" (e.g., "linux/amd64")`)
44+
_ = flags.SetAnnotation("platform", "version", []string{"1.48"})
4145

46+
_ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms)
4247
return cmd
4348
}
4449

@@ -52,7 +57,16 @@ func RunSave(ctx context.Context, dockerCli command.Cli, opts saveOptions) error
5257
return errors.Wrap(err, "failed to save image")
5358
}
5459

55-
responseBody, err := dockerCli.Client().ImageSave(ctx, opts.images, image.SaveOptions{})
60+
var options image.SaveOptions
61+
if opts.platform != "" {
62+
p, err := platforms.Parse(opts.platform)
63+
if err != nil {
64+
return errors.Wrap(err, "invalid platform")
65+
}
66+
options.Platform = &p
67+
}
68+
69+
responseBody, err := dockerCli.Client().ImageSave(ctx, opts.images, options)
5670
if err != nil {
5771
return err
5872
}

cli/command/image/save_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/docker/cli/internal/test"
1010
"github.com/docker/docker/api/types/image"
11+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
1112
"github.com/pkg/errors"
1213
"gotest.tools/v3/assert"
1314
is "gotest.tools/v3/assert/cmp"
@@ -51,6 +52,11 @@ func TestNewSaveCommandErrors(t *testing.T) {
5152
args: []string{"-o", "/dev/null", "arg1"},
5253
expectedError: "failed to save image: invalid output path: \"/dev/null\" must be a directory or a regular file",
5354
},
55+
{
56+
name: "invalid platform",
57+
args: []string{"--platform", "<invalid>", "arg1"},
58+
expectedError: `invalid platform`,
59+
},
5460
}
5561
for _, tc := range testCases {
5662
tc := tc
@@ -95,6 +101,16 @@ func TestNewSaveCommandSuccess(t *testing.T) {
95101
return io.NopCloser(strings.NewReader("")), nil
96102
},
97103
},
104+
{
105+
args: []string{"--platform", "linux/amd64", "arg1"},
106+
isTerminal: false,
107+
imageSaveFunc: func(images []string, options image.SaveOptions) (io.ReadCloser, error) {
108+
assert.Assert(t, is.Len(images, 1))
109+
assert.Check(t, is.Equal("arg1", images[0]))
110+
assert.Check(t, is.DeepEqual(ocispec.Platform{OS: "linux", Architecture: "amd64"}, *options.Platform))
111+
return io.NopCloser(strings.NewReader("")), nil
112+
},
113+
},
98114
}
99115
for _, tc := range testCases {
100116
tc := tc

docs/reference/commandline/image_save.md

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ Save one or more images to a tar archive (streamed to STDOUT by default)
99

1010
### Options
1111

12-
| Name | Type | Default | Description |
13-
|:-----------------|:---------|:--------|:-----------------------------------|
14-
| `-o`, `--output` | `string` | | Write to a file, instead of STDOUT |
12+
| Name | Type | Default | Description |
13+
|:--------------------------|:---------|:--------|:-----------------------------------------------------------------------------------------------|
14+
| `-o`, `--output` | `string` | | Write to a file, instead of STDOUT |
15+
| [`--platform`](#platform) | `string` | | Save only the given platform variant. Formatted as `os[/arch[/variant]]` (e.g., `linux/amd64`) |
1516

1617

1718
<!---MARKER_GEN_END-->
@@ -59,3 +60,52 @@ You can even cherry-pick particular tags of an image repository.
5960
```console
6061
$ docker save -o ubuntu.tar ubuntu:lucid ubuntu:saucy
6162
```
63+
64+
### <a name="platform"></a> Save a specific platform (--platform)
65+
66+
The `--platform` option allows you to specify which platform variant of the
67+
image to save. By default, `docker save` saves all platform variants that
68+
are present in the daemon's image store. Use the `--platform` option
69+
to specify which platform variant of the image to save. An error is produced
70+
if the given platform is not present in the local image store.
71+
72+
The platform option takes the `os[/arch[/variant]]` format; for example,
73+
`linux/amd64` or `linux/arm64/v8`. Architecture and variant are optional,
74+
and default to the daemon's native architecture if omitted.
75+
76+
The following example pulls the RISC-V variant of the `alpine:latest` image
77+
and saves it to a tar archive.
78+
79+
```console
80+
$ docker pull --platform=linux/riscv64 alpine:latest
81+
latest: Pulling from library/alpine
82+
8c4a05189a5f: Download complete
83+
Digest: sha256:beefdbd8a1da6d2915566fde36db9db0b524eb737fc57cd1367effd16dc0d06d
84+
Status: Downloaded newer image for alpine:latest
85+
docker.io/library/alpine:latest
86+
87+
$ docker image save --platform=linux/riscv64 -o alpine-riscv.tar alpine:latest
88+
89+
$ ls -lh image.tar
90+
-rw------- 1 thajeztah staff 3.9M Oct 7 11:06 alpine-riscv.tar
91+
```
92+
93+
The following example attempts to save a platform variant of `alpine:latest`
94+
that doesn't exist in the local image store, resulting in an error.
95+
96+
```console
97+
$ docker image ls --tree
98+
IMAGE ID DISK USAGE CONTENT SIZE IN USE
99+
alpine:latest beefdbd8a1da 10.6MB 3.37MB
100+
├─ linux/riscv64 80cde017a105 10.6MB 3.37MB
101+
├─ linux/amd64 33735bd63cf8 0B 0B
102+
├─ linux/arm/v6 50f635c8b04d 0B 0B
103+
├─ linux/arm/v7 f2f82d424957 0B 0B
104+
├─ linux/arm64/v8 9cee2b382fe2 0B 0B
105+
├─ linux/386 b3e87f642f5c 0B 0B
106+
├─ linux/ppc64le c7a6800e3dc5 0B 0B
107+
└─ linux/s390x 2b5b26e09ca2 0B 0B
108+
109+
$ docker image save --platform=linux/s390x -o alpine-s390x.tar alpine:latest
110+
Error response from daemon: no suitable export target found for platform linux/s390x
111+
```

docs/reference/commandline/save.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ Save one or more images to a tar archive (streamed to STDOUT by default)
99

1010
### Options
1111

12-
| Name | Type | Default | Description |
13-
|:-----------------|:---------|:--------|:-----------------------------------|
14-
| `-o`, `--output` | `string` | | Write to a file, instead of STDOUT |
12+
| Name | Type | Default | Description |
13+
|:-----------------|:---------|:--------|:-----------------------------------------------------------------------------------------------|
14+
| `-o`, `--output` | `string` | | Write to a file, instead of STDOUT |
15+
| `--platform` | `string` | | Save only the given platform variant. Formatted as `os[/arch[/variant]]` (e.g., `linux/amd64`) |
1516

1617

1718
<!---MARKER_GEN_END-->

0 commit comments

Comments
 (0)