Tizen_4.0 base
[platform/upstream/docker-engine.git] / vendor / github.com / docker / cli / cli / command / container / attach.go
1 package container
2
3 import (
4         "io"
5         "net/http/httputil"
6
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"
16 )
17
18 type attachOptions struct {
19         noStdin    bool
20         proxy      bool
21         detachKeys string
22
23         container string
24 }
25
26 func inspectContainerAndCheckState(ctx context.Context, cli client.APIClient, args string) (*types.ContainerJSON, error) {
27         c, err := cli.ContainerInspect(ctx, args)
28         if err != nil {
29                 return nil, err
30         }
31         if !c.State.Running {
32                 return nil, errors.New("You cannot attach to a stopped container, start it first")
33         }
34         if c.State.Paused {
35                 return nil, errors.New("You cannot attach to a paused container, unpause it first")
36         }
37         if c.State.Restarting {
38                 return nil, errors.New("You cannot attach to a restarting container, wait until it is running")
39         }
40
41         return &c, nil
42 }
43
44 // NewAttachCommand creates a new cobra.Command for `docker attach`
45 func NewAttachCommand(dockerCli command.Cli) *cobra.Command {
46         var opts attachOptions
47
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)
55                 },
56         }
57
58         flags := cmd.Flags()
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")
62         return cmd
63 }
64
65 func runAttach(dockerCli command.Cli, opts *attachOptions) error {
66         ctx := context.Background()
67         client := dockerCli.Client()
68
69         c, err := inspectContainerAndCheckState(ctx, client, opts.container)
70         if err != nil {
71                 return err
72         }
73
74         if err := dockerCli.In().CheckTty(!opts.noStdin, c.Config.Tty); err != nil {
75                 return err
76         }
77
78         if opts.detachKeys != "" {
79                 dockerCli.ConfigFile().DetachKeys = opts.detachKeys
80         }
81
82         options := types.ContainerAttachOptions{
83                 Stream:     true,
84                 Stdin:      !opts.noStdin && c.Config.OpenStdin,
85                 Stdout:     true,
86                 Stderr:     true,
87                 DetachKeys: dockerCli.ConfigFile().DetachKeys,
88         }
89
90         var in io.ReadCloser
91         if options.Stdin {
92                 in = dockerCli.In()
93         }
94
95         if opts.proxy && !c.Config.Tty {
96                 sigc := ForwardAllSignals(ctx, dockerCli, opts.container)
97                 defer signal.StopCatch(sigc)
98         }
99
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
105                 return errAttach
106         }
107         defer resp.Close()
108
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.
115         //
116         // Recheck the container's state to avoid attach block.
117         _, err = inspectContainerAndCheckState(ctx, client, opts.container)
118         if err != nil {
119                 return err
120         }
121
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)
129
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)
134                 }
135         }
136
137         streamer := hijackedIOStreamer{
138                 streams:      dockerCli,
139                 inputStream:  in,
140                 outputStream: dockerCli.Out(),
141                 errorStream:  dockerCli.Err(),
142                 resp:         resp,
143                 tty:          c.Config.Tty,
144                 detachKeys:   options.DetachKeys,
145         }
146
147         if err := streamer.stream(ctx); err != nil {
148                 return err
149         }
150
151         if errAttach != nil {
152                 return errAttach
153         }
154
155         _, status, err := getExitCode(ctx, dockerCli, opts.container)
156         if err != nil {
157                 return err
158         }
159         if status != 0 {
160                 return cli.StatusError{StatusCode: status}
161         }
162
163         return nil
164 }