From b8bcf6f5adc799d882025fd41770c86c42c4c804 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Sun, 9 Mar 2025 21:25:44 +0100 Subject: [PATCH] container export: 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 container export -o ./no/such/foo mycontainer failed to export container: invalid output path: directory "no/such" does not exist docker container export -o /no/permissions mycontainer failed to export container: stat /no/permissions: permission denied After: docker container export -o ./no/such/foo mycontainer failed to export container: invalid file path: stat no/such: no such file or directory docker container export -o /no/permissions mycontainer failed to export container: failed to stat output path: lstat /no/permissions: permission denied Signed-off-by: Sebastiaan van Stijn --- cli/command/container/export.go | 34 +++++++++++++++------------- cli/command/container/export_test.go | 6 ++--- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/cli/command/container/export.go b/cli/command/container/export.go index e9afa7dbe1..990c2e66f8 100644 --- a/cli/command/container/export.go +++ b/cli/command/container/export.go @@ -7,6 +7,7 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" + "github.com/moby/sys/atomicwriter" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -41,27 +42,28 @@ func NewExportCommand(dockerCli command.Cli) *cobra.Command { return cmd } -func runExport(ctx context.Context, dockerCli command.Cli, opts exportOptions) error { - if opts.output == "" && dockerCli.Out().IsTerminal() { - return errors.New("cowardly refusing to save to a terminal. Use the -o flag or redirect") +func runExport(ctx context.Context, dockerCLI command.Cli, opts exportOptions) error { + var output io.Writer + if opts.output == "" { + if dockerCLI.Out().IsTerminal() { + return errors.New("cowardly refusing to save to a terminal. Use the -o flag or redirect") + } + output = dockerCLI.Out() + } else { + writer, err := atomicwriter.New(opts.output, 0o600) + if err != nil { + return errors.Wrap(err, "failed to export container") + } + defer writer.Close() + output = writer } - if err := command.ValidateOutputPath(opts.output); err != nil { - return errors.Wrap(err, "failed to export container") - } - - clnt := dockerCli.Client() - - responseBody, err := clnt.ContainerExport(ctx, opts.container) + responseBody, err := dockerCLI.Client().ContainerExport(ctx, opts.container) if err != nil { return err } defer responseBody.Close() - if opts.output == "" { - _, err := io.Copy(dockerCli.Out(), responseBody) - return err - } - - return command.CopyToFile(opts.output, responseBody) + _, err = io.Copy(output, responseBody) + return err } diff --git a/cli/command/container/export_test.go b/cli/command/container/export_test.go index ae21a87995..182713ab99 100644 --- a/cli/command/container/export_test.go +++ b/cli/command/container/export_test.go @@ -42,8 +42,6 @@ func TestContainerExportOutputToIrregularFile(t *testing.T) { cmd.SetErr(io.Discard) cmd.SetArgs([]string{"-o", "/dev/random", "container"}) - err := cmd.Execute() - assert.Assert(t, err != nil) - expected := `"/dev/random" must be a directory or a regular file` - assert.ErrorContains(t, err, expected) + const expected = `failed to export container: cannot write to a character device file` + assert.Error(t, cmd.Execute(), expected) }