diff --git a/cmd/compose/attach.go b/cmd/compose/attach.go new file mode 100644 index 000000000..1899edef1 --- /dev/null +++ b/cmd/compose/attach.go @@ -0,0 +1,80 @@ +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package compose + +import ( + "context" + + "github.com/docker/cli/cli/command" + "github.com/docker/compose/v2/pkg/api" + "github.com/spf13/cobra" +) + +type attachOpts struct { + *composeOptions + + service string + index int + + detachKeys string + noStdin bool + proxy bool +} + +func attachCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { + opts := attachOpts{ + composeOptions: &composeOptions{ + ProjectOptions: p, + }, + } + runCmd := &cobra.Command{ + Use: "attach [OPTIONS] SERVICE", + Short: "Attach local standard input, output, and error streams to a service's running container.", + Args: cobra.MinimumNArgs(1), + PreRunE: Adapt(func(ctx context.Context, args []string) error { + opts.service = args[0] + return nil + }), + RunE: Adapt(func(ctx context.Context, args []string) error { + return runAttach(ctx, dockerCli, backend, opts) + }), + ValidArgsFunction: completeServiceNames(dockerCli, p), + } + + runCmd.Flags().IntVar(&opts.index, "index", 0, "index of the container if service has multiple replicas.") + runCmd.Flags().StringVarP(&opts.detachKeys, "detach-keys", "", "", "Override the key sequence for detaching from a container.") + + runCmd.Flags().BoolVar(&opts.noStdin, "no-stdin", false, "Do not attach STDIN") + runCmd.Flags().BoolVar(&opts.proxy, "sig-proxy", true, "Proxy all received signals to the process") + return runCmd +} + +func runAttach(ctx context.Context, dockerCli command.Cli, backend api.Service, opts attachOpts) error { + projectName, err := opts.toProjectName(dockerCli) + if err != nil { + return err + } + + attachOpts := api.AttachOptions{ + Service: opts.service, + Index: opts.index, + DetachKeys: opts.detachKeys, + NoStdin: opts.noStdin, + Proxy: opts.proxy, + } + return backend.Attach(ctx, projectName, attachOpts) +} diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go index cddcd3a4c..d7f7f9d7c 100644 --- a/cmd/compose/compose.go +++ b/cmd/compose/compose.go @@ -445,6 +445,7 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { // runCommand(&opts, dockerCli, backend), removeCommand(&opts, dockerCli, backend), execCommand(&opts, dockerCli, backend), + attachCommand(&opts, dockerCli, backend), pauseCommand(&opts, dockerCli, backend), unpauseCommand(&opts, dockerCli, backend), topCommand(&opts, dockerCli, backend), diff --git a/docs/reference/compose.md b/docs/reference/compose.md index a7508f85d..3728a76d4 100644 --- a/docs/reference/compose.md +++ b/docs/reference/compose.md @@ -5,37 +5,38 @@ Define and run multi-container applications with Docker. ### Subcommands -| Name | Description | -|:--------------------------------|:--------------------------------------------------------------------------------------| -| [`build`](compose_build.md) | Build or rebuild services | -| [`config`](compose_config.md) | Parse, resolve and render compose file in canonical format | -| [`cp`](compose_cp.md) | Copy files/folders between a service container and the local filesystem | -| [`create`](compose_create.md) | Creates containers for a service. | -| [`down`](compose_down.md) | Stop and remove containers, networks | -| [`events`](compose_events.md) | Receive real time events from containers. | -| [`exec`](compose_exec.md) | Execute a command in a running container. | -| [`images`](compose_images.md) | List images used by the created containers | -| [`kill`](compose_kill.md) | Force stop service containers. | -| [`logs`](compose_logs.md) | View output from containers | -| [`ls`](compose_ls.md) | List running compose projects | -| [`pause`](compose_pause.md) | Pause services | -| [`port`](compose_port.md) | Print the public port for a port binding. | -| [`ps`](compose_ps.md) | List containers | -| [`pull`](compose_pull.md) | Pull service images | -| [`push`](compose_push.md) | Push service images | -| [`restart`](compose_restart.md) | Restart service containers | -| [`rm`](compose_rm.md) | Removes stopped service containers | -| [`run`](compose_run.md) | Run a one-off command on a service. | -| [`scale`](compose_scale.md) | Scale services | -| [`start`](compose_start.md) | Start services | -| [`stats`](compose_stats.md) | Display a live stream of container(s) resource usage statistics | -| [`stop`](compose_stop.md) | Stop services | -| [`top`](compose_top.md) | Display the running processes | -| [`unpause`](compose_unpause.md) | Unpause services | -| [`up`](compose_up.md) | Create and start containers | -| [`version`](compose_version.md) | Show the Docker Compose version information | -| [`wait`](compose_wait.md) | Block until the first service container stops | -| [`watch`](compose_watch.md) | Watch build context for service and rebuild/refresh containers when files are updated | +| Name | Description | +|:--------------------------------|:-----------------------------------------------------------------------------------------| +| [`attach`](compose_attach.md) | Attach local standard input, output, and error streams to a service's running container. | +| [`build`](compose_build.md) | Build or rebuild services | +| [`config`](compose_config.md) | Parse, resolve and render compose file in canonical format | +| [`cp`](compose_cp.md) | Copy files/folders between a service container and the local filesystem | +| [`create`](compose_create.md) | Creates containers for a service. | +| [`down`](compose_down.md) | Stop and remove containers, networks | +| [`events`](compose_events.md) | Receive real time events from containers. | +| [`exec`](compose_exec.md) | Execute a command in a running container. | +| [`images`](compose_images.md) | List images used by the created containers | +| [`kill`](compose_kill.md) | Force stop service containers. | +| [`logs`](compose_logs.md) | View output from containers | +| [`ls`](compose_ls.md) | List running compose projects | +| [`pause`](compose_pause.md) | Pause services | +| [`port`](compose_port.md) | Print the public port for a port binding. | +| [`ps`](compose_ps.md) | List containers | +| [`pull`](compose_pull.md) | Pull service images | +| [`push`](compose_push.md) | Push service images | +| [`restart`](compose_restart.md) | Restart service containers | +| [`rm`](compose_rm.md) | Removes stopped service containers | +| [`run`](compose_run.md) | Run a one-off command on a service. | +| [`scale`](compose_scale.md) | Scale services | +| [`start`](compose_start.md) | Start services | +| [`stats`](compose_stats.md) | Display a live stream of container(s) resource usage statistics | +| [`stop`](compose_stop.md) | Stop services | +| [`top`](compose_top.md) | Display the running processes | +| [`unpause`](compose_unpause.md) | Unpause services | +| [`up`](compose_up.md) | Create and start containers | +| [`version`](compose_version.md) | Show the Docker Compose version information | +| [`wait`](compose_wait.md) | Block until the first service container stops | +| [`watch`](compose_watch.md) | Watch build context for service and rebuild/refresh containers when files are updated | ### Options diff --git a/docs/reference/compose_attach.md b/docs/reference/compose_attach.md new file mode 100644 index 000000000..405fed98a --- /dev/null +++ b/docs/reference/compose_attach.md @@ -0,0 +1,18 @@ +# docker compose attach + + +Attach local standard input, output, and error streams to a service's running container. + +### Options + +| Name | Type | Default | Description | +|:----------------|:---------|:--------|:----------------------------------------------------------| +| `--detach-keys` | `string` | | Override the key sequence for detaching from a container. | +| `--dry-run` | | | Execute command in dry run mode | +| `--index` | `int` | `0` | index of the container if service has multiple replicas. | +| `--no-stdin` | | | Do not attach STDIN | +| `--sig-proxy` | | | Proxy all received signals to the process | + + + + diff --git a/docs/reference/docker_compose.yaml b/docs/reference/docker_compose.yaml index ef9a95247..83f555b6c 100644 --- a/docs/reference/docker_compose.yaml +++ b/docs/reference/docker_compose.yaml @@ -146,6 +146,7 @@ usage: docker compose pname: docker plink: docker.yaml cname: + - docker compose attach - docker compose build - docker compose config - docker compose cp @@ -176,6 +177,7 @@ cname: - docker compose wait - docker compose watch clink: + - docker_compose_attach.yaml - docker_compose_build.yaml - docker_compose_config.yaml - docker_compose_cp.yaml diff --git a/docs/reference/docker_compose_attach.yaml b/docs/reference/docker_compose_attach.yaml new file mode 100644 index 000000000..14c584f41 --- /dev/null +++ b/docs/reference/docker_compose_attach.yaml @@ -0,0 +1,66 @@ +command: docker compose attach +short: | + Attach local standard input, output, and error streams to a service's running container. +long: | + Attach local standard input, output, and error streams to a service's running container. +usage: docker compose attach [OPTIONS] SERVICE +pname: docker compose +plink: docker_compose.yaml +options: + - option: detach-keys + value_type: string + description: Override the key sequence for detaching from a container. + deprecated: false + hidden: false + experimental: false + experimentalcli: false + kubernetes: false + swarm: false + - option: index + value_type: int + default_value: "0" + description: index of the container if service has multiple replicas. + deprecated: false + hidden: false + experimental: false + experimentalcli: false + kubernetes: false + swarm: false + - option: no-stdin + value_type: bool + default_value: "false" + description: Do not attach STDIN + deprecated: false + hidden: false + experimental: false + experimentalcli: false + kubernetes: false + swarm: false + - option: sig-proxy + value_type: bool + default_value: "true" + description: Proxy all received signals to the process + deprecated: false + hidden: false + experimental: false + experimentalcli: false + kubernetes: false + swarm: false +inherited_options: + - option: dry-run + value_type: bool + default_value: "false" + description: Execute command in dry run mode + deprecated: false + hidden: false + experimental: false + experimentalcli: false + kubernetes: false + swarm: false +deprecated: false +hidden: false +experimental: false +experimentalcli: false +kubernetes: false +swarm: false + diff --git a/pkg/api/api.go b/pkg/api/api.go index 7dafbe9c1..3b8f09161 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -62,6 +62,8 @@ type Service interface { Remove(ctx context.Context, projectName string, options RemoveOptions) error // Exec executes a command in a running service container Exec(ctx context.Context, projectName string, options RunOptions) (int, error) + // Attach STDIN,STDOUT,STDERR to a running service container + Attach(ctx context.Context, projectName string, options AttachOptions) error // Copy copies a file/folder between a service container and the local filesystem Copy(ctx context.Context, projectName string, options CopyOptions) error // Pause executes the equivalent to a `compose pause` @@ -342,6 +344,16 @@ type RunOptions struct { Index int } +// AttachOptions group options of the Attach API +type AttachOptions struct { + Project *types.Project + Service string + Index int + DetachKeys string + NoStdin bool + Proxy bool +} + // EventsOptions group options of the Events API type EventsOptions struct { Services []string diff --git a/pkg/compose/attach_service.go b/pkg/compose/attach_service.go new file mode 100644 index 000000000..3dbf37c7e --- /dev/null +++ b/pkg/compose/attach_service.go @@ -0,0 +1,39 @@ +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package compose + +import ( + "context" + "strings" + + "github.com/docker/cli/cli/command/container" + "github.com/docker/compose/v2/pkg/api" +) + +func (s *composeService) Attach(ctx context.Context, projectName string, options api.AttachOptions) error { + projectName = strings.ToLower(projectName) + target, err := s.getSpecifiedContainer(ctx, projectName, oneOffInclude, false, options.Service, options.Index) + if err != nil { + return err + } + + var attach container.AttachOptions + attach.DetachKeys = options.DetachKeys + attach.NoStdin = options.NoStdin + attach.Proxy = options.Proxy + return container.RunAttach(ctx, s.dockerCli, target.ID, &attach) +} diff --git a/pkg/mocks/mock_docker_compose_api.go b/pkg/mocks/mock_docker_compose_api.go index 580c1a38d..7a28e54cd 100644 --- a/pkg/mocks/mock_docker_compose_api.go +++ b/pkg/mocks/mock_docker_compose_api.go @@ -40,6 +40,20 @@ func (m *MockService) EXPECT() *MockServiceMockRecorder { return m.recorder } +// Attach mocks base method. +func (m *MockService) Attach(ctx context.Context, projectName string, options api.AttachOptions) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Attach", ctx, projectName, options) + ret0, _ := ret[0].(error) + return ret0 +} + +// Attach indicates an expected call of Attach. +func (mr *MockServiceMockRecorder) Attach(ctx, projectName, options any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Attach", reflect.TypeOf((*MockService)(nil).Attach), ctx, projectName, options) +} + // Build mocks base method. func (m *MockService) Build(ctx context.Context, project *types.Project, options api.BuildOptions) error { m.ctrl.T.Helper()