]>
Commit | Line | Data |
---|---|---|
9b12e4fe JC |
1 | // Copyright 2015 go-dockerclient authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style | |
3 | // license that can be found in the LICENSE file. | |
4 | ||
5 | package docker | |
6 | ||
7 | import ( | |
8 | "encoding/json" | |
9 | "fmt" | |
10 | "io" | |
11 | "net/http" | |
12 | "net/url" | |
13 | "strconv" | |
14 | ) | |
15 | ||
16 | // Exec is the type representing a `docker exec` instance and containing the | |
17 | // instance ID | |
18 | type Exec struct { | |
19 | ID string `json:"Id,omitempty" yaml:"Id,omitempty"` | |
20 | } | |
21 | ||
22 | // CreateExecOptions specify parameters to the CreateExecContainer function. | |
23 | // | |
24 | // See https://goo.gl/1KSIb7 for more details | |
25 | type CreateExecOptions struct { | |
26 | AttachStdin bool `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty"` | |
27 | AttachStdout bool `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty"` | |
28 | AttachStderr bool `json:"AttachStderr,omitempty" yaml:"AttachStderr,omitempty"` | |
29 | Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty"` | |
30 | Cmd []string `json:"Cmd,omitempty" yaml:"Cmd,omitempty"` | |
31 | Container string `json:"Container,omitempty" yaml:"Container,omitempty"` | |
32 | User string `json:"User,omitempty" yaml:"User,omitempty"` | |
33 | } | |
34 | ||
35 | // CreateExec sets up an exec instance in a running container `id`, returning the exec | |
36 | // instance, or an error in case of failure. | |
37 | // | |
38 | // See https://goo.gl/1KSIb7 for more details | |
39 | func (c *Client) CreateExec(opts CreateExecOptions) (*Exec, error) { | |
40 | path := fmt.Sprintf("/containers/%s/exec", opts.Container) | |
41 | resp, err := c.do("POST", path, doOptions{data: opts}) | |
42 | if err != nil { | |
43 | if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { | |
44 | return nil, &NoSuchContainer{ID: opts.Container} | |
45 | } | |
46 | return nil, err | |
47 | } | |
48 | defer resp.Body.Close() | |
49 | var exec Exec | |
50 | if err := json.NewDecoder(resp.Body).Decode(&exec); err != nil { | |
51 | return nil, err | |
52 | } | |
53 | ||
54 | return &exec, nil | |
55 | } | |
56 | ||
57 | // StartExecOptions specify parameters to the StartExecContainer function. | |
58 | // | |
59 | // See https://goo.gl/iQCnto for more details | |
60 | type StartExecOptions struct { | |
61 | Detach bool `json:"Detach,omitempty" yaml:"Detach,omitempty"` | |
62 | ||
63 | Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty"` | |
64 | ||
65 | InputStream io.Reader `qs:"-"` | |
66 | OutputStream io.Writer `qs:"-"` | |
67 | ErrorStream io.Writer `qs:"-"` | |
68 | ||
69 | // Use raw terminal? Usually true when the container contains a TTY. | |
70 | RawTerminal bool `qs:"-"` | |
71 | ||
72 | // If set, after a successful connect, a sentinel will be sent and then the | |
73 | // client will block on receive before continuing. | |
74 | // | |
75 | // It must be an unbuffered channel. Using a buffered channel can lead | |
76 | // to unexpected behavior. | |
77 | Success chan struct{} `json:"-"` | |
78 | } | |
79 | ||
80 | // StartExec starts a previously set up exec instance id. If opts.Detach is | |
81 | // true, it returns after starting the exec command. Otherwise, it sets up an | |
82 | // interactive session with the exec command. | |
83 | // | |
84 | // See https://goo.gl/iQCnto for more details | |
85 | func (c *Client) StartExec(id string, opts StartExecOptions) error { | |
86 | cw, err := c.StartExecNonBlocking(id, opts) | |
87 | if err != nil { | |
88 | return err | |
89 | } | |
90 | if cw != nil { | |
91 | return cw.Wait() | |
92 | } | |
93 | return nil | |
94 | } | |
95 | ||
96 | // StartExecNonBlocking starts a previously set up exec instance id. If opts.Detach is | |
97 | // true, it returns after starting the exec command. Otherwise, it sets up an | |
98 | // interactive session with the exec command. | |
99 | // | |
100 | // See https://goo.gl/iQCnto for more details | |
101 | func (c *Client) StartExecNonBlocking(id string, opts StartExecOptions) (CloseWaiter, error) { | |
102 | if id == "" { | |
103 | return nil, &NoSuchExec{ID: id} | |
104 | } | |
105 | ||
106 | path := fmt.Sprintf("/exec/%s/start", id) | |
107 | ||
108 | if opts.Detach { | |
109 | resp, err := c.do("POST", path, doOptions{data: opts}) | |
110 | if err != nil { | |
111 | if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { | |
112 | return nil, &NoSuchExec{ID: id} | |
113 | } | |
114 | return nil, err | |
115 | } | |
116 | defer resp.Body.Close() | |
117 | return nil, nil | |
118 | } | |
119 | ||
120 | return c.hijack("POST", path, hijackOptions{ | |
121 | success: opts.Success, | |
122 | setRawTerminal: opts.RawTerminal, | |
123 | in: opts.InputStream, | |
124 | stdout: opts.OutputStream, | |
125 | stderr: opts.ErrorStream, | |
126 | data: opts, | |
127 | }) | |
128 | } | |
129 | ||
130 | // ResizeExecTTY resizes the tty session used by the exec command id. This API | |
131 | // is valid only if Tty was specified as part of creating and starting the exec | |
132 | // command. | |
133 | // | |
134 | // See https://goo.gl/e1JpsA for more details | |
135 | func (c *Client) ResizeExecTTY(id string, height, width int) error { | |
136 | params := make(url.Values) | |
137 | params.Set("h", strconv.Itoa(height)) | |
138 | params.Set("w", strconv.Itoa(width)) | |
139 | ||
140 | path := fmt.Sprintf("/exec/%s/resize?%s", id, params.Encode()) | |
141 | resp, err := c.do("POST", path, doOptions{}) | |
142 | if err != nil { | |
143 | return err | |
144 | } | |
145 | resp.Body.Close() | |
146 | return nil | |
147 | } | |
148 | ||
149 | // ExecProcessConfig is a type describing the command associated to a Exec | |
150 | // instance. It's used in the ExecInspect type. | |
151 | type ExecProcessConfig struct { | |
152 | Privileged bool `json:"privileged,omitempty" yaml:"privileged,omitempty"` | |
153 | User string `json:"user,omitempty" yaml:"user,omitempty"` | |
154 | Tty bool `json:"tty,omitempty" yaml:"tty,omitempty"` | |
155 | EntryPoint string `json:"entrypoint,omitempty" yaml:"entrypoint,omitempty"` | |
156 | Arguments []string `json:"arguments,omitempty" yaml:"arguments,omitempty"` | |
157 | } | |
158 | ||
159 | // ExecInspect is a type with details about a exec instance, including the | |
160 | // exit code if the command has finished running. It's returned by a api | |
161 | // call to /exec/(id)/json | |
162 | // | |
163 | // See https://goo.gl/gPtX9R for more details | |
164 | type ExecInspect struct { | |
165 | ID string `json:"ID,omitempty" yaml:"ID,omitempty"` | |
166 | Running bool `json:"Running,omitempty" yaml:"Running,omitempty"` | |
167 | ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty"` | |
168 | OpenStdin bool `json:"OpenStdin,omitempty" yaml:"OpenStdin,omitempty"` | |
169 | OpenStderr bool `json:"OpenStderr,omitempty" yaml:"OpenStderr,omitempty"` | |
170 | OpenStdout bool `json:"OpenStdout,omitempty" yaml:"OpenStdout,omitempty"` | |
171 | ProcessConfig ExecProcessConfig `json:"ProcessConfig,omitempty" yaml:"ProcessConfig,omitempty"` | |
172 | Container Container `json:"Container,omitempty" yaml:"Container,omitempty"` | |
173 | } | |
174 | ||
175 | // InspectExec returns low-level information about the exec command id. | |
176 | // | |
177 | // See https://goo.gl/gPtX9R for more details | |
178 | func (c *Client) InspectExec(id string) (*ExecInspect, error) { | |
179 | path := fmt.Sprintf("/exec/%s/json", id) | |
180 | resp, err := c.do("GET", path, doOptions{}) | |
181 | if err != nil { | |
182 | if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { | |
183 | return nil, &NoSuchExec{ID: id} | |
184 | } | |
185 | return nil, err | |
186 | } | |
187 | defer resp.Body.Close() | |
188 | var exec ExecInspect | |
189 | if err := json.NewDecoder(resp.Body).Decode(&exec); err != nil { | |
190 | return nil, err | |
191 | } | |
192 | return &exec, nil | |
193 | } | |
194 | ||
195 | // NoSuchExec is the error returned when a given exec instance does not exist. | |
196 | type NoSuchExec struct { | |
197 | ID string | |
198 | } | |
199 | ||
200 | func (err *NoSuchExec) Error() string { | |
201 | return "No such exec instance: " + err.ID | |
202 | } |