Skip to content

Commit d47d233

Browse files
committed
image save: implement file-write with atomicwriter
Same functionality, but implemented with atomicwriter. There's a slight difference in error-messages produced (but can be adjusted if we want). Before: docker image save -o ./no/such/foo busybox:latest failed to save image: invalid output path: directory "no/such" does not exist docker image save -o /no/permissions busybox:latest failed to save image: stat /no/permissions: permission denied After: docker image save -o ./no/such/foo busybox:latest failed to save image: invalid file path: stat no/such: no such file or directory docker image save -o /no/permissions busybox:latest failed to save image: failed to stat output path: lstat /no/permissions: permission denied Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
1 parent 410c0ba commit d47d233

2 files changed

Lines changed: 22 additions & 18 deletions

File tree

cli/command/image/save.go

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/docker/cli/cli/command"
1010
"github.com/docker/cli/cli/command/completion"
1111
"github.com/docker/docker/client"
12+
"github.com/moby/sys/atomicwriter"
1213
"github.com/pkg/errors"
1314
"github.com/spf13/cobra"
1415
)
@@ -48,15 +49,7 @@ func NewSaveCommand(dockerCli command.Cli) *cobra.Command {
4849
}
4950

5051
// runSave performs a save against the engine based on the specified options
51-
func runSave(ctx context.Context, dockerCli command.Cli, opts saveOptions) error {
52-
if opts.output == "" && dockerCli.Out().IsTerminal() {
53-
return errors.New("cowardly refusing to save to a terminal. Use the -o flag or redirect")
54-
}
55-
56-
if err := command.ValidateOutputPath(opts.output); err != nil {
57-
return errors.Wrap(err, "failed to save image")
58-
}
59-
52+
func runSave(ctx context.Context, dockerCLI command.Cli, opts saveOptions) error {
6053
var options []client.ImageSaveOption
6154
if opts.platform != "" {
6255
p, err := platforms.Parse(opts.platform)
@@ -67,16 +60,27 @@ func runSave(ctx context.Context, dockerCli command.Cli, opts saveOptions) error
6760
options = append(options, client.ImageSaveWithPlatforms(p))
6861
}
6962

70-
responseBody, err := dockerCli.Client().ImageSave(ctx, opts.images, options...)
71-
if err != nil {
72-
return err
63+
var output io.Writer
64+
if opts.output == "" {
65+
if dockerCLI.Out().IsTerminal() {
66+
return errors.New("cowardly refusing to save to a terminal. Use the -o flag or redirect")
67+
}
68+
output = dockerCLI.Out()
69+
} else {
70+
writer, err := atomicwriter.New(opts.output, 0o600)
71+
if err != nil {
72+
return errors.Wrap(err, "failed to save image")
73+
}
74+
defer writer.Close()
75+
output = writer
7376
}
74-
defer responseBody.Close()
7577

76-
if opts.output == "" {
77-
_, err := io.Copy(dockerCli.Out(), responseBody)
78+
responseBody, err := dockerCLI.Client().ImageSave(ctx, opts.images, options...)
79+
if err != nil {
7880
return err
7981
}
82+
defer responseBody.Close()
8083

81-
return command.CopyToFile(opts.output, responseBody)
84+
_, err = io.Copy(output, responseBody)
85+
return err
8286
}

cli/command/image/save_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,12 @@ func TestNewSaveCommandErrors(t *testing.T) {
4444
{
4545
name: "output directory does not exist",
4646
args: []string{"-o", "fakedir/out.tar", "arg1"},
47-
expectedError: "failed to save image: invalid output path: directory \"fakedir\" does not exist",
47+
expectedError: `failed to save image: invalid output path: stat fakedir: no such file or directory`,
4848
},
4949
{
5050
name: "output file is irregular",
5151
args: []string{"-o", "/dev/null", "arg1"},
52-
expectedError: "failed to save image: invalid output path: \"/dev/null\" must be a directory or a regular file",
52+
expectedError: `failed to save image: cannot write to a character device file`,
5353
},
5454
{
5555
name: "invalid platform",

0 commit comments

Comments
 (0)