github.com/xeptore/docker-cli@v20.10.14+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 := notfiyAllSignals() 101 go ForwardAllSignals(ctx, dockerCli, opts.container, sigc) 102 defer signal.StopCatch(sigc) 103 } 104 105 resp, errAttach := client.ContainerAttach(ctx, opts.container, options) 106 if errAttach != nil { 107 return errAttach 108 } 109 defer resp.Close() 110 111 // If use docker attach command to attach to a stop container, it will return 112 // "You cannot attach to a stopped container" error, it's ok, but when 113 // attach to a running container, it(docker attach) use inspect to check 114 // the container's state, if it pass the state check on the client side, 115 // and then the container is stopped, docker attach command still attach to 116 // the container and not exit. 117 // 118 // Recheck the container's state to avoid attach block. 119 _, err = inspectContainerAndCheckState(ctx, client, opts.container) 120 if err != nil { 121 return err 122 } 123 124 if c.Config.Tty && dockerCli.Out().IsTerminal() { 125 resizeTTY(ctx, dockerCli, opts.container) 126 } 127 128 streamer := hijackedIOStreamer{ 129 streams: dockerCli, 130 inputStream: in, 131 outputStream: dockerCli.Out(), 132 errorStream: dockerCli.Err(), 133 resp: resp, 134 tty: c.Config.Tty, 135 detachKeys: options.DetachKeys, 136 } 137 138 if err := streamer.stream(ctx); err != nil { 139 return err 140 } 141 142 return getExitStatus(errC, resultC) 143 } 144 145 func getExitStatus(errC <-chan error, resultC <-chan container.ContainerWaitOKBody) error { 146 select { 147 case result := <-resultC: 148 if result.Error != nil { 149 return fmt.Errorf(result.Error.Message) 150 } 151 if result.StatusCode != 0 { 152 return cli.StatusError{StatusCode: int(result.StatusCode)} 153 } 154 case err := <-errC: 155 return err 156 } 157 158 return nil 159 } 160 161 func resizeTTY(ctx context.Context, dockerCli command.Cli, containerID string) { 162 height, width := dockerCli.Out().GetTtySize() 163 // To handle the case where a user repeatedly attaches/detaches without resizing their 164 // terminal, the only way to get the shell prompt to display for attaches 2+ is to artificially 165 // resize it, then go back to normal. Without this, every attach after the first will 166 // require the user to manually resize or hit enter. 167 resizeTtyTo(ctx, dockerCli.Client(), containerID, height+1, width+1, false) 168 169 // After the above resizing occurs, the call to MonitorTtySize below will handle resetting back 170 // to the actual size. 171 if err := MonitorTtySize(ctx, dockerCli, containerID, false); err != nil { 172 logrus.Debugf("Error monitoring TTY size: %s", err) 173 } 174 }