diff --git a/go.mod b/go.mod index 61e1c94ad..317ace275 100644 --- a/go.mod +++ b/go.mod @@ -30,6 +30,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 github.com/moby/buildkit v0.20.1 github.com/moby/patternmatcher v0.6.0 + github.com/moby/sys/atomicwriter v0.1.0 github.com/moby/term v0.5.2 github.com/morikuni/aec v1.0.0 github.com/opencontainers/go-digest v1.0.0 diff --git a/go.sum b/go.sum index 039db180a..f2b2bed1e 100644 --- a/go.sum +++ b/go.sum @@ -330,6 +330,8 @@ github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkV github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8= github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= +github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk= github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I= github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= diff --git a/pkg/compose/export.go b/pkg/compose/export.go index 795208ae1..183b3eee4 100644 --- a/pkg/compose/export.go +++ b/pkg/compose/export.go @@ -25,6 +25,7 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/progress" + "github.com/moby/sys/atomicwriter" ) func (s *composeService) Export(ctx context.Context, projectName string, options api.ExportOptions) error { @@ -41,11 +42,11 @@ func (s *composeService) export(ctx context.Context, projectName string, options return err } - if options.Output == "" && s.dockerCli.Out().IsTerminal() { - return fmt.Errorf("output option is required when exporting to terminal") - } - - if err := command.ValidateOutputPath(options.Output); err != nil { + if options.Output == "" { + if s.dockerCli.Out().IsTerminal() { + return fmt.Errorf("output option is required when exporting to terminal") + } + } else if err := command.ValidateOutputPath(options.Output); err != nil { return fmt.Errorf("failed to export container: %w", err) } @@ -83,9 +84,14 @@ func (s *composeService) export(ctx context.Context, projectName string, options if options.Output == "" { _, err := io.Copy(s.dockerCli.Out(), responseBody) return err - } + } else { + writer, err := atomicwriter.New(options.Output, 0o600) + if err != nil { + return err + } + defer func() { _ = writer.Close() }() - if err := command.CopyToFile(options.Output, responseBody); err != nil { + _, err = io.Copy(writer, responseBody) return err } }