github.com/cspotcode/docker-cli@v20.10.0-rc1.0.20201201121459-3faad7acc5b8+incompatible/cli/command/container/attach.go (about) 1 package container 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 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/api/types/container" 12 "github.com/docker/docker/client" 13 "github.com/docker/docker/pkg/signal" 14 "github.com/pkg/errors" 15 "github.com/sirupsen/logrus" 16 "github.com/spf13/cobra" 17 ) 18 19 type attachOptions struct { 20 noStdin bool 21 proxy bool 22 detachKeys string 23 24 container string 25 } 26 27 func inspectContainerAndCheckState(ctx context.Context, cli client.APIClient, args string) (*types.ContainerJSON, error) { 28 c, err := cli.ContainerInspect(ctx, args) 29 if err != nil { 30 return nil, err 31 } 32 if !c.State.Running { 33 return nil, errors.New("You cannot attach to a stopped container, start it first") 34 } 35 if c.State.Paused { 36 return nil, errors.New("You cannot attach to a paused container, unpause it first") 37 } 38 if c.State.Restarting { 39 return nil, errors.New("You cannot attach to a restarting container, wait until it is running") 40 } 41 42 return &c, nil 43 } 44 45 // NewAttachCommand creates a new cobra.Command for `docker attach` 46 func NewAttachCommand(dockerCli command.Cli) *cobra.Command { 47 var opts attachOptions 48 49 cmd := &cobra.Command{ 50 Use: "attach [OPTIONS] CONTAINER", 51 Short: "Attach local standard input, output, and error streams to a running container", 52 Args: cli.ExactArgs(1), 53 RunE: func(cmd *cobra.Command, args []string) error { 54 opts.container = args[0] 55 return runAttach(dockerCli, &opts) 56 }, 57 } 58 59 flags := cmd.Flags() 60 flags.BoolVar(&opts.noStdin, "no-stdin", false, "Do not attach STDIN") 61 flags.BoolVar(&opts.proxy, "sig-proxy", true, "Proxy all received signals to the process") 62 flags.StringVar(&opts.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container") 63 return cmd 64 } 65 66 func runAttach(dockerCli command.Cli, opts *attachOptions) error { 67 ctx := context.Background() 68 client := dockerCli.Client() 69 70 // request channel to wait for client 71 resultC, errC := client.ContainerWait(ctx, opts.container, "") 72 73 c, err := inspectContainerAndCheckState(ctx, client, opts.container) 74 if err != nil { 75 return err 76 } 77 78 if err := dockerCli.In().CheckTty(!opts.noStdin, c.Config.Tty); err != nil { 79 return err 80 } 81 82 if opts.detachKeys != "" { 83 dockerCli.ConfigFile().DetachKeys = opts.detachKeys 84 } 85 86 options := types.ContainerAttachOptions{ 87 Stream: true, 88 Stdin: !opts.noStdin && c.Config.OpenStdin, 89 Stdout: true, 90 Stderr: true, 91 DetachKeys: dockerCli.ConfigFile().DetachKeys, 92 } 93 94 var in io.ReadCloser 95 if options.Stdin { 96 in = dockerCli.In() 97 } 98 99 if opts.proxy && !c.Config.Tty { 100 sigc := ForwardAllSignals(ctx, dockerCli, opts.container) 101 defer signal.StopCatch(sigc) 102 } 103 104 resp, errAttach := client.ContainerAttach(ctx, opts.container, options) 105 if errAttach != nil { 106 return errAttach 107 } 108 defer resp.Close() 109 110 // If use docker attach command to attach to a stop container, it will return 111 // "You cannot attach to a stopped container" error, it's ok, but when 112 // attach to a running container, it(docker attach) use inspect to check 113 // the container's state, if it pass the state check on the client side, 114 // and then the container is stopped, docker attach command still attach to 115 // the container and not exit. 116 // 117 // Recheck the container's state to avoid attach block. 118 _, err = inspectContainerAndCheckState(ctx, client, opts.container) 119 if err != nil { 120 return err 121 } 122 123 if c.Config.Tty && dockerCli.Out().IsTerminal() { 124 resizeTTY(ctx, dockerCli, opts.container) 125 } 126 127 streamer := hijackedIOStreamer{ 128 streams: dockerCli, 129 inputStream: in, 130 outputStream: dockerCli.Out(), 131 errorStream: dockerCli.Err(), 132 resp: resp, 133 tty: c.Config.Tty, 134 detachKeys: options.DetachKeys, 135 } 136 137 if err := streamer.stream(ctx); err != nil { 138 return err 139 } 140 141 return getExitStatus(errC, resultC) 142 } 143 144 func getExitStatus(errC <-chan error, resultC <-chan container.ContainerWaitOKBody) error { 145 select { 146 case result := <-resultC: 147 if result.Error != nil { 148 return fmt.Errorf(result.Error.Message) 149 } 150 if result.StatusCode != 0 { 151 return cli.StatusError{StatusCode: int(result.StatusCode)} 152 } 153 case err := <-errC: 154 return err 155 } 156 157 return nil 158 } 159 160 func resizeTTY(ctx context.Context, dockerCli command.Cli, containerID string) { 161 height, width := dockerCli.Out().GetTtySize() 162 // To handle the case where a user repeatedly attaches/detaches without resizing their 163 // terminal, the only way to get the shell prompt to display for attaches 2+ is to artificially 164 // resize it, then go back to normal. Without this, every attach after the first will 165 // require the user to manually resize or hit enter. 166 resizeTtyTo(ctx, dockerCli.Client(), containerID, height+1, width+1, false) 167 168 // After the above resizing occurs, the call to MonitorTtySize below will handle resetting back 169 // to the actual size. 170 if err := MonitorTtySize(ctx, dockerCli, containerID, false); err != nil { 171 logrus.Debugf("Error monitoring TTY size: %s", err) 172 } 173 }