7 "github.com/Sirupsen/logrus"
8 "github.com/docker/cli/cli"
9 "github.com/docker/cli/cli/command"
10 "github.com/docker/docker/api/types"
11 "github.com/docker/docker/client"
12 "github.com/docker/docker/pkg/signal"
13 "github.com/pkg/errors"
14 "github.com/spf13/cobra"
15 "golang.org/x/net/context"
18 type attachOptions struct {
26 func inspectContainerAndCheckState(ctx context.Context, cli client.APIClient, args string) (*types.ContainerJSON, error) {
27 c, err := cli.ContainerInspect(ctx, args)
32 return nil, errors.New("You cannot attach to a stopped container, start it first")
35 return nil, errors.New("You cannot attach to a paused container, unpause it first")
37 if c.State.Restarting {
38 return nil, errors.New("You cannot attach to a restarting container, wait until it is running")
44 // NewAttachCommand creates a new cobra.Command for `docker attach`
45 func NewAttachCommand(dockerCli command.Cli) *cobra.Command {
46 var opts attachOptions
48 cmd := &cobra.Command{
49 Use: "attach [OPTIONS] CONTAINER",
50 Short: "Attach local standard input, output, and error streams to a running container",
51 Args: cli.ExactArgs(1),
52 RunE: func(cmd *cobra.Command, args []string) error {
53 opts.container = args[0]
54 return runAttach(dockerCli, &opts)
59 flags.BoolVar(&opts.noStdin, "no-stdin", false, "Do not attach STDIN")
60 flags.BoolVar(&opts.proxy, "sig-proxy", true, "Proxy all received signals to the process")
61 flags.StringVar(&opts.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container")
65 func runAttach(dockerCli command.Cli, opts *attachOptions) error {
66 ctx := context.Background()
67 client := dockerCli.Client()
69 c, err := inspectContainerAndCheckState(ctx, client, opts.container)
74 if err := dockerCli.In().CheckTty(!opts.noStdin, c.Config.Tty); err != nil {
78 if opts.detachKeys != "" {
79 dockerCli.ConfigFile().DetachKeys = opts.detachKeys
82 options := types.ContainerAttachOptions{
84 Stdin: !opts.noStdin && c.Config.OpenStdin,
87 DetachKeys: dockerCli.ConfigFile().DetachKeys,
95 if opts.proxy && !c.Config.Tty {
96 sigc := ForwardAllSignals(ctx, dockerCli, opts.container)
97 defer signal.StopCatch(sigc)
100 resp, errAttach := client.ContainerAttach(ctx, opts.container, options)
101 if errAttach != nil && errAttach != httputil.ErrPersistEOF {
102 // ContainerAttach returns an ErrPersistEOF (connection closed)
103 // means server met an error and put it in Hijacked connection
104 // keep the error and read detailed error message from hijacked connection later
109 // If use docker attach command to attach to a stop container, it will return
110 // "You cannot attach to a stopped container" error, it's ok, but when
111 // attach to a running container, it(docker attach) use inspect to check
112 // the container's state, if it pass the state check on the client side,
113 // and then the container is stopped, docker attach command still attach to
114 // the container and not exit.
116 // Recheck the container's state to avoid attach block.
117 _, err = inspectContainerAndCheckState(ctx, client, opts.container)
122 if c.Config.Tty && dockerCli.Out().IsTerminal() {
123 height, width := dockerCli.Out().GetTtySize()
124 // To handle the case where a user repeatedly attaches/detaches without resizing their
125 // terminal, the only way to get the shell prompt to display for attaches 2+ is to artificially
126 // resize it, then go back to normal. Without this, every attach after the first will
127 // require the user to manually resize or hit enter.
128 resizeTtyTo(ctx, client, opts.container, height+1, width+1, false)
130 // After the above resizing occurs, the call to MonitorTtySize below will handle resetting back
131 // to the actual size.
132 if err := MonitorTtySize(ctx, dockerCli, opts.container, false); err != nil {
133 logrus.Debugf("Error monitoring TTY size: %s", err)
137 streamer := hijackedIOStreamer{
140 outputStream: dockerCli.Out(),
141 errorStream: dockerCli.Err(),
144 detachKeys: options.DetachKeys,
147 if err := streamer.stream(ctx); err != nil {
151 if errAttach != nil {
155 _, status, err := getExitCode(ctx, dockerCli, opts.container)
160 return cli.StatusError{StatusCode: status}