diff --git a/api/compose/api.go b/api/compose/api.go index e25a0b7b8..9ebff652b 100644 --- a/api/compose/api.go +++ b/api/compose/api.go @@ -277,6 +277,7 @@ type PsOptions struct { type CopyOptions struct { Source string Destination string + All bool Index int FollowLink bool CopyUIDGID bool diff --git a/cli/cmd/compose/cp.go b/cli/cmd/compose/cp.go index 0f0b35480..7ed292656 100644 --- a/cli/cmd/compose/cp.go +++ b/cli/cmd/compose/cp.go @@ -32,6 +32,7 @@ type copyOptions struct { source string destination string index int + all bool followLink bool copyUIDGID bool } @@ -60,7 +61,8 @@ func copyCommand(p *projectOptions, backend compose.Service) *cobra.Command { } flags := copyCmd.Flags() - flags.IntVar(&opts.index, "index", 1, "index of the container if there are multiple instances of a service [default: 1].") + flags.IntVar(&opts.index, "index", 1, "Index of the container if there are multiple instances of a service [default: 1].") + flags.BoolVar(&opts.all, "all", false, "Copy to all the containers of the service.") flags.BoolVarP(&opts.followLink, "follow-link", "L", false, "Always follow symbol link in SRC_PATH") flags.BoolVarP(&opts.copyUIDGID, "archive", "a", false, "Archive mode (copy all uid/gid information)") @@ -76,6 +78,7 @@ func runCopy(ctx context.Context, backend compose.Service, opts copyOptions) err return backend.Copy(ctx, projects, compose.CopyOptions{ Source: opts.source, Destination: opts.destination, + All: opts.all, Index: opts.index, FollowLink: opts.followLink, CopyUIDGID: opts.copyUIDGID, diff --git a/local/compose/cp.go b/local/compose/cp.go index 7e049c79c..ee5bd3f21 100644 --- a/local/compose/cp.go +++ b/local/compose/cp.go @@ -24,6 +24,8 @@ import ( "path/filepath" "strings" + "golang.org/x/sync/errgroup" + "github.com/compose-spec/compose-go/types" "github.com/docker/cli/cli/command" "github.com/docker/compose-cli/api/compose" @@ -51,19 +53,25 @@ func (s *composeService) Copy(ctx context.Context, project *types.Project, opts if srcService != "" { direction |= fromService serviceName = srcService + + // copying from multiple containers of a services doesn't make sense. + if opts.All { + return errors.New("cannot use the --all flag when copying from a service") + } } if destService != "" { direction |= toService serviceName = destService } - containers, err := s.apiClient.ContainerList(ctx, apitypes.ContainerListOptions{ - Filters: filters.NewArgs( - projectFilter(project.Name), - serviceFilter(serviceName), - filters.Arg("label", fmt.Sprintf("%s=%d", containerNumberLabel, opts.Index)), - ), - }) + f := filters.NewArgs( + projectFilter(project.Name), + serviceFilter(serviceName), + ) + if !opts.All { + f.Add("label", fmt.Sprintf("%s=%d", containerNumberLabel, opts.Index)) + } + containers, err := s.apiClient.ContainerList(ctx, apitypes.ContainerListOptions{Filters: f}) if err != nil { return err } @@ -72,17 +80,25 @@ func (s *composeService) Copy(ctx context.Context, project *types.Project, opts return fmt.Errorf("service %s not running", serviceName) } - containerID := containers[0].ID - switch direction { - case fromService: - return s.copyFromContainer(ctx, containerID, srcPath, dstPath, opts) - case toService: - return s.copyToContainer(ctx, containerID, srcPath, dstPath, opts) - case acrossServices: - return errors.New("copying between services is not supported") - default: - return errors.New("unknown copy direction") + g := errgroup.Group{} + for i := range containers { + containerID := containers[i].ID + + g.Go(func() error { + switch direction { + case fromService: + return s.copyFromContainer(ctx, containerID, srcPath, dstPath, opts) + case toService: + return s.copyToContainer(ctx, containerID, srcPath, dstPath, opts) + case acrossServices: + return errors.New("copying between services is not supported") + default: + return errors.New("unknown copy direction") + } + }) } + + return g.Wait() } func (s *composeService) copyToContainer(ctx context.Context, containerID string, srcPath string, dstPath string, opts compose.CopyOptions) error {