2016-09-08 13:11:39 -04:00
|
|
|
package container
|
|
|
|
|
|
|
|
import (
|
2018-05-03 21:02:44 -04:00
|
|
|
"context"
|
2017-11-15 22:18:23 -05:00
|
|
|
"fmt"
|
2016-09-08 13:11:39 -04:00
|
|
|
"io"
|
|
|
|
|
2017-04-17 18:07:56 -04:00
|
|
|
"github.com/docker/cli/cli"
|
|
|
|
"github.com/docker/cli/cli/command"
|
2022-03-30 09:27:25 -04:00
|
|
|
"github.com/docker/cli/cli/command/completion"
|
2016-09-08 13:11:39 -04:00
|
|
|
"github.com/docker/docker/api/types"
|
2017-11-15 22:18:23 -05:00
|
|
|
"github.com/docker/docker/api/types/container"
|
2017-05-18 00:12:37 -04:00
|
|
|
"github.com/docker/docker/client"
|
2021-08-09 13:15:46 -04:00
|
|
|
"github.com/moby/sys/signal"
|
2017-03-27 21:21:59 -04:00
|
|
|
"github.com/pkg/errors"
|
2017-08-07 05:52:40 -04:00
|
|
|
"github.com/sirupsen/logrus"
|
2016-09-08 13:11:39 -04:00
|
|
|
"github.com/spf13/cobra"
|
|
|
|
)
|
|
|
|
|
2023-11-06 04:00:13 -05:00
|
|
|
// AttachOptions group options for `attach` command
|
|
|
|
type AttachOptions struct {
|
|
|
|
NoStdin bool
|
|
|
|
Proxy bool
|
|
|
|
DetachKeys string
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
2023-11-20 11:38:50 -05:00
|
|
|
func inspectContainerAndCheckState(ctx context.Context, apiClient client.APIClient, args string) (*types.ContainerJSON, error) {
|
|
|
|
c, err := apiClient.ContainerInspect(ctx, args)
|
2017-05-18 00:12:37 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if !c.State.Running {
|
|
|
|
return nil, errors.New("You cannot attach to a stopped container, start it first")
|
|
|
|
}
|
|
|
|
if c.State.Paused {
|
|
|
|
return nil, errors.New("You cannot attach to a paused container, unpause it first")
|
|
|
|
}
|
2017-06-07 19:12:39 -04:00
|
|
|
if c.State.Restarting {
|
|
|
|
return nil, errors.New("You cannot attach to a restarting container, wait until it is running")
|
|
|
|
}
|
2017-06-07 19:03:52 -04:00
|
|
|
|
2017-05-18 00:12:37 -04:00
|
|
|
return &c, nil
|
|
|
|
}
|
|
|
|
|
2016-09-08 13:11:39 -04:00
|
|
|
// NewAttachCommand creates a new cobra.Command for `docker attach`
|
2024-01-26 07:38:09 -05:00
|
|
|
func NewAttachCommand(dockerCLI command.Cli) *cobra.Command {
|
2023-11-06 04:00:13 -05:00
|
|
|
var opts AttachOptions
|
2016-09-08 13:11:39 -04:00
|
|
|
|
|
|
|
cmd := &cobra.Command{
|
|
|
|
Use: "attach [OPTIONS] CONTAINER",
|
2017-03-31 16:22:21 -04:00
|
|
|
Short: "Attach local standard input, output, and error streams to a running container",
|
2016-09-08 13:11:39 -04:00
|
|
|
Args: cli.ExactArgs(1),
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
2024-01-26 07:38:09 -05:00
|
|
|
containerID := args[0]
|
|
|
|
return RunAttach(cmd.Context(), dockerCLI, containerID, &opts)
|
2016-09-08 13:11:39 -04:00
|
|
|
},
|
cli: use custom annotation for aliases
Cobra allows for aliases to be defined for a command, but only allows these
to be defined at the same level (for example, `docker image ls` as alias for
`docker image list`). Our CLI has some commands that are available both as a
top-level shorthand as well as `docker <object> <verb>` subcommands. For example,
`docker ps` is a shorthand for `docker container ps` / `docker container ls`.
This patch introduces a custom "aliases" annotation that can be used to print
all available aliases for a command. While this requires these aliases to be
defined manually, in practice the list of aliases rarely changes, so maintenance
should be minimal.
As a convention, we could consider the first command in this list to be the
canonical command, so that we can use this information to add redirects in
our documentation in future.
Before this patch:
docker images --help
Usage: docker images [OPTIONS] [REPOSITORY[:TAG]]
List images
Options:
-a, --all Show all images (default hides intermediate images)
...
With this patch:
docker images --help
Usage: docker images [OPTIONS] [REPOSITORY[:TAG]]
List images
Aliases:
docker image ls, docker image list, docker images
Options:
-a, --all Show all images (default hides intermediate images)
...
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-06-28 04:52:25 -04:00
|
|
|
Annotations: map[string]string{
|
|
|
|
"aliases": "docker container attach, docker attach",
|
|
|
|
},
|
2024-01-26 07:38:09 -05:00
|
|
|
ValidArgsFunction: completion.ContainerNames(dockerCLI, false, func(ctr types.Container) bool {
|
2023-11-20 11:38:50 -05:00
|
|
|
return ctr.State != "paused"
|
2022-05-12 07:18:48 -04:00
|
|
|
}),
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
flags := cmd.Flags()
|
2023-11-06 04:00:13 -05:00
|
|
|
flags.BoolVar(&opts.NoStdin, "no-stdin", false, "Do not attach STDIN")
|
|
|
|
flags.BoolVar(&opts.Proxy, "sig-proxy", true, "Proxy all received signals to the process")
|
|
|
|
flags.StringVar(&opts.DetachKeys, "detach-keys", "", "Override the key sequence for detaching a container")
|
2016-09-08 13:11:39 -04:00
|
|
|
return cmd
|
|
|
|
}
|
|
|
|
|
2023-11-06 04:00:13 -05:00
|
|
|
// RunAttach executes an `attach` command
|
2024-01-26 07:38:09 -05:00
|
|
|
func RunAttach(ctx context.Context, dockerCLI command.Cli, containerID string, opts *AttachOptions) error {
|
2023-11-20 11:38:50 -05:00
|
|
|
apiClient := dockerCLI.Client()
|
2016-09-08 13:11:39 -04:00
|
|
|
|
2017-11-15 22:18:23 -05:00
|
|
|
// request channel to wait for client
|
2024-01-26 07:38:09 -05:00
|
|
|
resultC, errC := apiClient.ContainerWait(ctx, containerID, "")
|
2017-11-15 22:18:23 -05:00
|
|
|
|
2024-01-26 07:38:09 -05:00
|
|
|
c, err := inspectContainerAndCheckState(ctx, apiClient, containerID)
|
2016-09-08 13:11:39 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-11-20 11:38:50 -05:00
|
|
|
if err := dockerCLI.In().CheckTty(!opts.NoStdin, c.Config.Tty); err != nil {
|
2016-09-08 13:11:39 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-11-20 11:38:50 -05:00
|
|
|
detachKeys := dockerCLI.ConfigFile().DetachKeys
|
2023-11-06 04:00:13 -05:00
|
|
|
if opts.DetachKeys != "" {
|
|
|
|
detachKeys = opts.DetachKeys
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
2023-10-13 14:34:32 -04:00
|
|
|
options := container.AttachOptions{
|
2016-09-08 13:11:39 -04:00
|
|
|
Stream: true,
|
2023-11-06 04:00:13 -05:00
|
|
|
Stdin: !opts.NoStdin && c.Config.OpenStdin,
|
2016-09-08 13:11:39 -04:00
|
|
|
Stdout: true,
|
|
|
|
Stderr: true,
|
2023-06-08 10:53:30 -04:00
|
|
|
DetachKeys: detachKeys,
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
var in io.ReadCloser
|
|
|
|
if options.Stdin {
|
2023-11-20 11:38:50 -05:00
|
|
|
in = dockerCLI.In()
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
2023-11-06 04:00:13 -05:00
|
|
|
if opts.Proxy && !c.Config.Tty {
|
2021-09-16 09:46:27 -04:00
|
|
|
sigc := notifyAllSignals()
|
2024-04-08 04:11:09 -04:00
|
|
|
// since we're explicitly setting up signal handling here, and the daemon will
|
|
|
|
// get notified independently of the clients ctx cancellation, we use this context
|
|
|
|
// but without cancellation to avoid ForwardAllSignals from returning
|
|
|
|
// before all signals are forwarded.
|
|
|
|
bgCtx := context.WithoutCancel(ctx)
|
|
|
|
go ForwardAllSignals(bgCtx, apiClient, containerID, sigc)
|
2016-09-08 13:11:39 -04:00
|
|
|
defer signal.StopCatch(sigc)
|
|
|
|
}
|
|
|
|
|
2024-01-26 07:38:09 -05:00
|
|
|
resp, errAttach := apiClient.ContainerAttach(ctx, containerID, options)
|
2019-04-02 05:20:07 -04:00
|
|
|
if errAttach != nil {
|
2016-09-08 13:11:39 -04:00
|
|
|
return errAttach
|
|
|
|
}
|
|
|
|
defer resp.Close()
|
|
|
|
|
2017-05-18 00:12:37 -04:00
|
|
|
// If use docker attach command to attach to a stop container, it will return
|
|
|
|
// "You cannot attach to a stopped container" error, it's ok, but when
|
|
|
|
// attach to a running container, it(docker attach) use inspect to check
|
|
|
|
// the container's state, if it pass the state check on the client side,
|
|
|
|
// and then the container is stopped, docker attach command still attach to
|
|
|
|
// the container and not exit.
|
|
|
|
//
|
|
|
|
// Recheck the container's state to avoid attach block.
|
2024-01-26 07:38:09 -05:00
|
|
|
_, err = inspectContainerAndCheckState(ctx, apiClient, containerID)
|
2017-05-18 00:12:37 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-11-20 11:38:50 -05:00
|
|
|
if c.Config.Tty && dockerCLI.Out().IsTerminal() {
|
2024-01-26 07:38:09 -05:00
|
|
|
resizeTTY(ctx, dockerCLI, containerID)
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
2017-05-17 14:40:56 -04:00
|
|
|
|
|
|
|
streamer := hijackedIOStreamer{
|
2023-11-20 11:38:50 -05:00
|
|
|
streams: dockerCLI,
|
2017-05-17 14:40:56 -04:00
|
|
|
inputStream: in,
|
2023-11-20 11:38:50 -05:00
|
|
|
outputStream: dockerCLI.Out(),
|
|
|
|
errorStream: dockerCLI.Err(),
|
2017-05-17 14:40:56 -04:00
|
|
|
resp: resp,
|
|
|
|
tty: c.Config.Tty,
|
|
|
|
detachKeys: options.DetachKeys,
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := streamer.stream(ctx); err != nil {
|
2016-09-08 13:11:39 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-11-15 22:18:23 -05:00
|
|
|
return getExitStatus(errC, resultC)
|
|
|
|
}
|
|
|
|
|
2022-04-29 13:26:50 -04:00
|
|
|
func getExitStatus(errC <-chan error, resultC <-chan container.WaitResponse) error {
|
2017-11-15 22:18:23 -05:00
|
|
|
select {
|
|
|
|
case result := <-resultC:
|
|
|
|
if result.Error != nil {
|
|
|
|
return fmt.Errorf(result.Error.Message)
|
|
|
|
}
|
|
|
|
if result.StatusCode != 0 {
|
|
|
|
return cli.StatusError{StatusCode: int(result.StatusCode)}
|
|
|
|
}
|
|
|
|
case err := <-errC:
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2017-05-09 18:35:25 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func resizeTTY(ctx context.Context, dockerCli command.Cli, containerID string) {
|
|
|
|
height, width := dockerCli.Out().GetTtySize()
|
|
|
|
// To handle the case where a user repeatedly attaches/detaches without resizing their
|
|
|
|
// terminal, the only way to get the shell prompt to display for attaches 2+ is to artificially
|
|
|
|
// resize it, then go back to normal. Without this, every attach after the first will
|
|
|
|
// require the user to manually resize or hit enter.
|
|
|
|
resizeTtyTo(ctx, dockerCli.Client(), containerID, height+1, width+1, false)
|
2016-09-08 13:11:39 -04:00
|
|
|
|
2017-05-09 18:35:25 -04:00
|
|
|
// After the above resizing occurs, the call to MonitorTtySize below will handle resetting back
|
|
|
|
// to the actual size.
|
|
|
|
if err := MonitorTtySize(ctx, dockerCli, containerID, false); err != nil {
|
|
|
|
logrus.Debugf("Error monitoring TTY size: %s", err)
|
|
|
|
}
|
|
|
|
}
|