]>
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 | "errors" | |
10 | "fmt" | |
11 | "io" | |
12 | "net/http" | |
13 | "net/url" | |
14 | "strconv" | |
15 | "strings" | |
16 | "time" | |
17 | ||
18 | "github.com/fsouza/go-dockerclient/external/github.com/docker/go-units" | |
19 | ) | |
20 | ||
21 | // ErrContainerAlreadyExists is the error returned by CreateContainer when the | |
22 | // container already exists. | |
23 | var ErrContainerAlreadyExists = errors.New("container already exists") | |
24 | ||
25 | // ListContainersOptions specify parameters to the ListContainers function. | |
26 | // | |
27 | // See https://goo.gl/47a6tO for more details. | |
28 | type ListContainersOptions struct { | |
29 | All bool | |
30 | Size bool | |
31 | Limit int | |
32 | Since string | |
33 | Before string | |
34 | Filters map[string][]string | |
35 | } | |
36 | ||
37 | // APIPort is a type that represents a port mapping returned by the Docker API | |
38 | type APIPort struct { | |
39 | PrivatePort int64 `json:"PrivatePort,omitempty" yaml:"PrivatePort,omitempty"` | |
40 | PublicPort int64 `json:"PublicPort,omitempty" yaml:"PublicPort,omitempty"` | |
41 | Type string `json:"Type,omitempty" yaml:"Type,omitempty"` | |
42 | IP string `json:"IP,omitempty" yaml:"IP,omitempty"` | |
43 | } | |
44 | ||
45 | // APIMount represents a mount point for a container. | |
46 | type APIMount struct { | |
47 | Name string `json:"Name,omitempty" yaml:"Name,omitempty"` | |
48 | Source string `json:"Source,omitempty" yaml:"Source,omitempty"` | |
49 | Destination string `json:"Destination,omitempty" yaml:"Destination,omitempty"` | |
50 | Driver string `json:"Driver,omitempty" yaml:"Driver,omitempty"` | |
51 | Mode string `json:"Mode,omitempty" yaml:"Mode,omitempty"` | |
52 | RW bool `json:"RW,omitempty" yaml:"RW,omitempty"` | |
53 | Propogation string `json:"Propogation,omitempty" yaml:"Propogation,omitempty"` | |
54 | } | |
55 | ||
56 | // APIContainers represents each container in the list returned by | |
57 | // ListContainers. | |
58 | type APIContainers struct { | |
59 | ID string `json:"Id" yaml:"Id"` | |
60 | Image string `json:"Image,omitempty" yaml:"Image,omitempty"` | |
61 | Command string `json:"Command,omitempty" yaml:"Command,omitempty"` | |
62 | Created int64 `json:"Created,omitempty" yaml:"Created,omitempty"` | |
63 | State string `json:"State,omitempty" yaml:"State,omitempty"` | |
64 | Status string `json:"Status,omitempty" yaml:"Status,omitempty"` | |
65 | Ports []APIPort `json:"Ports,omitempty" yaml:"Ports,omitempty"` | |
66 | SizeRw int64 `json:"SizeRw,omitempty" yaml:"SizeRw,omitempty"` | |
67 | SizeRootFs int64 `json:"SizeRootFs,omitempty" yaml:"SizeRootFs,omitempty"` | |
68 | Names []string `json:"Names,omitempty" yaml:"Names,omitempty"` | |
69 | Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty"` | |
70 | Networks NetworkList `json:"NetworkSettings,omitempty" yaml:"NetworkSettings,omitempty"` | |
71 | Mounts []APIMount `json:"Mounts,omitempty" yaml:"Mounts,omitempty"` | |
72 | } | |
73 | ||
74 | // NetworkList encapsulates a map of networks, as returned by the Docker API in | |
75 | // ListContainers. | |
76 | type NetworkList struct { | |
77 | Networks map[string]ContainerNetwork `json:"Networks" yaml:"Networks,omitempty"` | |
78 | } | |
79 | ||
80 | // ListContainers returns a slice of containers matching the given criteria. | |
81 | // | |
82 | // See https://goo.gl/47a6tO for more details. | |
83 | func (c *Client) ListContainers(opts ListContainersOptions) ([]APIContainers, error) { | |
84 | path := "/containers/json?" + queryString(opts) | |
85 | resp, err := c.do("GET", path, doOptions{}) | |
86 | if err != nil { | |
87 | return nil, err | |
88 | } | |
89 | defer resp.Body.Close() | |
90 | var containers []APIContainers | |
91 | if err := json.NewDecoder(resp.Body).Decode(&containers); err != nil { | |
92 | return nil, err | |
93 | } | |
94 | return containers, nil | |
95 | } | |
96 | ||
97 | // Port represents the port number and the protocol, in the form | |
98 | // <number>/<protocol>. For example: 80/tcp. | |
99 | type Port string | |
100 | ||
101 | // Port returns the number of the port. | |
102 | func (p Port) Port() string { | |
103 | return strings.Split(string(p), "/")[0] | |
104 | } | |
105 | ||
106 | // Proto returns the name of the protocol. | |
107 | func (p Port) Proto() string { | |
108 | parts := strings.Split(string(p), "/") | |
109 | if len(parts) == 1 { | |
110 | return "tcp" | |
111 | } | |
112 | return parts[1] | |
113 | } | |
114 | ||
115 | // State represents the state of a container. | |
116 | type State struct { | |
117 | Status string `json:"Status,omitempty" yaml:"Status,omitempty"` | |
118 | Running bool `json:"Running,omitempty" yaml:"Running,omitempty"` | |
119 | Paused bool `json:"Paused,omitempty" yaml:"Paused,omitempty"` | |
120 | Restarting bool `json:"Restarting,omitempty" yaml:"Restarting,omitempty"` | |
121 | OOMKilled bool `json:"OOMKilled,omitempty" yaml:"OOMKilled,omitempty"` | |
122 | RemovalInProgress bool `json:"RemovalInProgress,omitempty" yaml:"RemovalInProgress,omitempty"` | |
123 | Dead bool `json:"Dead,omitempty" yaml:"Dead,omitempty"` | |
124 | Pid int `json:"Pid,omitempty" yaml:"Pid,omitempty"` | |
125 | ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty"` | |
126 | Error string `json:"Error,omitempty" yaml:"Error,omitempty"` | |
127 | StartedAt time.Time `json:"StartedAt,omitempty" yaml:"StartedAt,omitempty"` | |
128 | FinishedAt time.Time `json:"FinishedAt,omitempty" yaml:"FinishedAt,omitempty"` | |
129 | } | |
130 | ||
131 | // String returns a human-readable description of the state | |
132 | func (s *State) String() string { | |
133 | if s.Running { | |
134 | if s.Paused { | |
135 | return fmt.Sprintf("Up %s (Paused)", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt))) | |
136 | } | |
137 | if s.Restarting { | |
138 | return fmt.Sprintf("Restarting (%d) %s ago", s.ExitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt))) | |
139 | } | |
140 | ||
141 | return fmt.Sprintf("Up %s", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt))) | |
142 | } | |
143 | ||
144 | if s.RemovalInProgress { | |
145 | return "Removal In Progress" | |
146 | } | |
147 | ||
148 | if s.Dead { | |
149 | return "Dead" | |
150 | } | |
151 | ||
152 | if s.StartedAt.IsZero() { | |
153 | return "Created" | |
154 | } | |
155 | ||
156 | if s.FinishedAt.IsZero() { | |
157 | return "" | |
158 | } | |
159 | ||
160 | return fmt.Sprintf("Exited (%d) %s ago", s.ExitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt))) | |
161 | } | |
162 | ||
163 | // StateString returns a single string to describe state | |
164 | func (s *State) StateString() string { | |
165 | if s.Running { | |
166 | if s.Paused { | |
167 | return "paused" | |
168 | } | |
169 | if s.Restarting { | |
170 | return "restarting" | |
171 | } | |
172 | return "running" | |
173 | } | |
174 | ||
175 | if s.Dead { | |
176 | return "dead" | |
177 | } | |
178 | ||
179 | if s.StartedAt.IsZero() { | |
180 | return "created" | |
181 | } | |
182 | ||
183 | return "exited" | |
184 | } | |
185 | ||
186 | // PortBinding represents the host/container port mapping as returned in the | |
187 | // `docker inspect` json | |
188 | type PortBinding struct { | |
189 | HostIP string `json:"HostIP,omitempty" yaml:"HostIP,omitempty"` | |
190 | HostPort string `json:"HostPort,omitempty" yaml:"HostPort,omitempty"` | |
191 | } | |
192 | ||
193 | // PortMapping represents a deprecated field in the `docker inspect` output, | |
194 | // and its value as found in NetworkSettings should always be nil | |
195 | type PortMapping map[string]string | |
196 | ||
197 | // ContainerNetwork represents the networking settings of a container per network. | |
198 | type ContainerNetwork struct { | |
199 | MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"` | |
200 | GlobalIPv6PrefixLen int `json:"GlobalIPv6PrefixLen,omitempty" yaml:"GlobalIPv6PrefixLen,omitempty"` | |
201 | GlobalIPv6Address string `json:"GlobalIPv6Address,omitempty" yaml:"GlobalIPv6Address,omitempty"` | |
202 | IPv6Gateway string `json:"IPv6Gateway,omitempty" yaml:"IPv6Gateway,omitempty"` | |
203 | IPPrefixLen int `json:"IPPrefixLen,omitempty" yaml:"IPPrefixLen,omitempty"` | |
204 | IPAddress string `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty"` | |
205 | Gateway string `json:"Gateway,omitempty" yaml:"Gateway,omitempty"` | |
206 | EndpointID string `json:"EndpointID,omitempty" yaml:"EndpointID,omitempty"` | |
207 | NetworkID string `json:"NetworkID,omitempty" yaml:"NetworkID,omitempty"` | |
208 | } | |
209 | ||
210 | // NetworkSettings contains network-related information about a container | |
211 | type NetworkSettings struct { | |
212 | Networks map[string]ContainerNetwork `json:"Networks,omitempty" yaml:"Networks,omitempty"` | |
213 | IPAddress string `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty"` | |
214 | IPPrefixLen int `json:"IPPrefixLen,omitempty" yaml:"IPPrefixLen,omitempty"` | |
215 | MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"` | |
216 | Gateway string `json:"Gateway,omitempty" yaml:"Gateway,omitempty"` | |
217 | Bridge string `json:"Bridge,omitempty" yaml:"Bridge,omitempty"` | |
218 | PortMapping map[string]PortMapping `json:"PortMapping,omitempty" yaml:"PortMapping,omitempty"` | |
219 | Ports map[Port][]PortBinding `json:"Ports,omitempty" yaml:"Ports,omitempty"` | |
220 | NetworkID string `json:"NetworkID,omitempty" yaml:"NetworkID,omitempty"` | |
221 | EndpointID string `json:"EndpointID,omitempty" yaml:"EndpointID,omitempty"` | |
222 | SandboxKey string `json:"SandboxKey,omitempty" yaml:"SandboxKey,omitempty"` | |
223 | GlobalIPv6Address string `json:"GlobalIPv6Address,omitempty" yaml:"GlobalIPv6Address,omitempty"` | |
224 | GlobalIPv6PrefixLen int `json:"GlobalIPv6PrefixLen,omitempty" yaml:"GlobalIPv6PrefixLen,omitempty"` | |
225 | IPv6Gateway string `json:"IPv6Gateway,omitempty" yaml:"IPv6Gateway,omitempty"` | |
226 | LinkLocalIPv6Address string `json:"LinkLocalIPv6Address,omitempty" yaml:"LinkLocalIPv6Address,omitempty"` | |
227 | LinkLocalIPv6PrefixLen int `json:"LinkLocalIPv6PrefixLen,omitempty" yaml:"LinkLocalIPv6PrefixLen,omitempty"` | |
228 | SecondaryIPAddresses []string `json:"SecondaryIPAddresses,omitempty" yaml:"SecondaryIPAddresses,omitempty"` | |
229 | SecondaryIPv6Addresses []string `json:"SecondaryIPv6Addresses,omitempty" yaml:"SecondaryIPv6Addresses,omitempty"` | |
230 | } | |
231 | ||
232 | // PortMappingAPI translates the port mappings as contained in NetworkSettings | |
233 | // into the format in which they would appear when returned by the API | |
234 | func (settings *NetworkSettings) PortMappingAPI() []APIPort { | |
235 | var mapping []APIPort | |
236 | for port, bindings := range settings.Ports { | |
237 | p, _ := parsePort(port.Port()) | |
238 | if len(bindings) == 0 { | |
239 | mapping = append(mapping, APIPort{ | |
240 | PrivatePort: int64(p), | |
241 | Type: port.Proto(), | |
242 | }) | |
243 | continue | |
244 | } | |
245 | for _, binding := range bindings { | |
246 | p, _ := parsePort(port.Port()) | |
247 | h, _ := parsePort(binding.HostPort) | |
248 | mapping = append(mapping, APIPort{ | |
249 | PrivatePort: int64(p), | |
250 | PublicPort: int64(h), | |
251 | Type: port.Proto(), | |
252 | IP: binding.HostIP, | |
253 | }) | |
254 | } | |
255 | } | |
256 | return mapping | |
257 | } | |
258 | ||
259 | func parsePort(rawPort string) (int, error) { | |
260 | port, err := strconv.ParseUint(rawPort, 10, 16) | |
261 | if err != nil { | |
262 | return 0, err | |
263 | } | |
264 | return int(port), nil | |
265 | } | |
266 | ||
267 | // Config is the list of configuration options used when creating a container. | |
268 | // Config does not contain the options that are specific to starting a container on a | |
269 | // given host. Those are contained in HostConfig | |
270 | type Config struct { | |
271 | Hostname string `json:"Hostname,omitempty" yaml:"Hostname,omitempty"` | |
272 | Domainname string `json:"Domainname,omitempty" yaml:"Domainname,omitempty"` | |
273 | User string `json:"User,omitempty" yaml:"User,omitempty"` | |
274 | Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty"` | |
275 | MemorySwap int64 `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty"` | |
276 | MemoryReservation int64 `json:"MemoryReservation,omitempty" yaml:"MemoryReservation,omitempty"` | |
277 | KernelMemory int64 `json:"KernelMemory,omitempty" yaml:"KernelMemory,omitempty"` | |
278 | PidsLimit int64 `json:"PidsLimit,omitempty" yaml:"PidsLimit,omitempty"` | |
279 | CPUShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty"` | |
280 | CPUSet string `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty"` | |
281 | AttachStdin bool `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty"` | |
282 | AttachStdout bool `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty"` | |
283 | AttachStderr bool `json:"AttachStderr,omitempty" yaml:"AttachStderr,omitempty"` | |
284 | PortSpecs []string `json:"PortSpecs,omitempty" yaml:"PortSpecs,omitempty"` | |
285 | ExposedPorts map[Port]struct{} `json:"ExposedPorts,omitempty" yaml:"ExposedPorts,omitempty"` | |
286 | StopSignal string `json:"StopSignal,omitempty" yaml:"StopSignal,omitempty"` | |
287 | Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty"` | |
288 | OpenStdin bool `json:"OpenStdin,omitempty" yaml:"OpenStdin,omitempty"` | |
289 | StdinOnce bool `json:"StdinOnce,omitempty" yaml:"StdinOnce,omitempty"` | |
290 | Env []string `json:"Env,omitempty" yaml:"Env,omitempty"` | |
291 | Cmd []string `json:"Cmd" yaml:"Cmd"` | |
292 | DNS []string `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.9 and below only | |
293 | Image string `json:"Image,omitempty" yaml:"Image,omitempty"` | |
294 | Volumes map[string]struct{} `json:"Volumes,omitempty" yaml:"Volumes,omitempty"` | |
295 | VolumeDriver string `json:"VolumeDriver,omitempty" yaml:"VolumeDriver,omitempty"` | |
296 | VolumesFrom string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"` | |
297 | WorkingDir string `json:"WorkingDir,omitempty" yaml:"WorkingDir,omitempty"` | |
298 | MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"` | |
299 | Entrypoint []string `json:"Entrypoint" yaml:"Entrypoint"` | |
300 | NetworkDisabled bool `json:"NetworkDisabled,omitempty" yaml:"NetworkDisabled,omitempty"` | |
301 | SecurityOpts []string `json:"SecurityOpts,omitempty" yaml:"SecurityOpts,omitempty"` | |
302 | OnBuild []string `json:"OnBuild,omitempty" yaml:"OnBuild,omitempty"` | |
303 | Mounts []Mount `json:"Mounts,omitempty" yaml:"Mounts,omitempty"` | |
304 | Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty"` | |
305 | } | |
306 | ||
307 | // Mount represents a mount point in the container. | |
308 | // | |
309 | // It has been added in the version 1.20 of the Docker API, available since | |
310 | // Docker 1.8. | |
311 | type Mount struct { | |
312 | Name string | |
313 | Source string | |
314 | Destination string | |
315 | Driver string | |
316 | Mode string | |
317 | RW bool | |
318 | } | |
319 | ||
320 | // LogConfig defines the log driver type and the configuration for it. | |
321 | type LogConfig struct { | |
322 | Type string `json:"Type,omitempty" yaml:"Type,omitempty"` | |
323 | Config map[string]string `json:"Config,omitempty" yaml:"Config,omitempty"` | |
324 | } | |
325 | ||
326 | // ULimit defines system-wide resource limitations | |
327 | // This can help a lot in system administration, e.g. when a user starts too many processes and therefore makes the system unresponsive for other users. | |
328 | type ULimit struct { | |
329 | Name string `json:"Name,omitempty" yaml:"Name,omitempty"` | |
330 | Soft int64 `json:"Soft,omitempty" yaml:"Soft,omitempty"` | |
331 | Hard int64 `json:"Hard,omitempty" yaml:"Hard,omitempty"` | |
332 | } | |
333 | ||
334 | // SwarmNode containers information about which Swarm node the container is on | |
335 | type SwarmNode struct { | |
336 | ID string `json:"ID,omitempty" yaml:"ID,omitempty"` | |
337 | IP string `json:"IP,omitempty" yaml:"IP,omitempty"` | |
338 | Addr string `json:"Addr,omitempty" yaml:"Addr,omitempty"` | |
339 | Name string `json:"Name,omitempty" yaml:"Name,omitempty"` | |
340 | CPUs int64 `json:"CPUs,omitempty" yaml:"CPUs,omitempty"` | |
341 | Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty"` | |
342 | Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty"` | |
343 | } | |
344 | ||
345 | // GraphDriver contains information about the GraphDriver used by the container | |
346 | type GraphDriver struct { | |
347 | Name string `json:"Name,omitempty" yaml:"Name,omitempty"` | |
348 | Data map[string]string `json:"Data,omitempty" yaml:"Data,omitempty"` | |
349 | } | |
350 | ||
351 | // Container is the type encompasing everything about a container - its config, | |
352 | // hostconfig, etc. | |
353 | type Container struct { | |
354 | ID string `json:"Id" yaml:"Id"` | |
355 | ||
356 | Created time.Time `json:"Created,omitempty" yaml:"Created,omitempty"` | |
357 | ||
358 | Path string `json:"Path,omitempty" yaml:"Path,omitempty"` | |
359 | Args []string `json:"Args,omitempty" yaml:"Args,omitempty"` | |
360 | ||
361 | Config *Config `json:"Config,omitempty" yaml:"Config,omitempty"` | |
362 | State State `json:"State,omitempty" yaml:"State,omitempty"` | |
363 | Image string `json:"Image,omitempty" yaml:"Image,omitempty"` | |
364 | ||
365 | Node *SwarmNode `json:"Node,omitempty" yaml:"Node,omitempty"` | |
366 | ||
367 | NetworkSettings *NetworkSettings `json:"NetworkSettings,omitempty" yaml:"NetworkSettings,omitempty"` | |
368 | ||
369 | SysInitPath string `json:"SysInitPath,omitempty" yaml:"SysInitPath,omitempty"` | |
370 | ResolvConfPath string `json:"ResolvConfPath,omitempty" yaml:"ResolvConfPath,omitempty"` | |
371 | HostnamePath string `json:"HostnamePath,omitempty" yaml:"HostnamePath,omitempty"` | |
372 | HostsPath string `json:"HostsPath,omitempty" yaml:"HostsPath,omitempty"` | |
373 | LogPath string `json:"LogPath,omitempty" yaml:"LogPath,omitempty"` | |
374 | Name string `json:"Name,omitempty" yaml:"Name,omitempty"` | |
375 | Driver string `json:"Driver,omitempty" yaml:"Driver,omitempty"` | |
376 | Mounts []Mount `json:"Mounts,omitempty" yaml:"Mounts,omitempty"` | |
377 | ||
378 | Volumes map[string]string `json:"Volumes,omitempty" yaml:"Volumes,omitempty"` | |
379 | VolumesRW map[string]bool `json:"VolumesRW,omitempty" yaml:"VolumesRW,omitempty"` | |
380 | HostConfig *HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty"` | |
381 | ExecIDs []string `json:"ExecIDs,omitempty" yaml:"ExecIDs,omitempty"` | |
382 | GraphDriver *GraphDriver `json:"GraphDriver,omitempty" yaml:"GraphDriver,omitempty"` | |
383 | ||
384 | RestartCount int `json:"RestartCount,omitempty" yaml:"RestartCount,omitempty"` | |
385 | ||
386 | AppArmorProfile string `json:"AppArmorProfile,omitempty" yaml:"AppArmorProfile,omitempty"` | |
387 | } | |
388 | ||
389 | // UpdateContainerOptions specify parameters to the UpdateContainer function. | |
390 | // | |
391 | // See https://goo.gl/Y6fXUy for more details. | |
392 | type UpdateContainerOptions struct { | |
393 | BlkioWeight int `json:"BlkioWeight"` | |
394 | CPUShares int `json:"CpuShares"` | |
395 | CPUPeriod int `json:"CpuPeriod"` | |
396 | CPUQuota int `json:"CpuQuota"` | |
397 | CpusetCpus string `json:"CpusetCpus"` | |
398 | CpusetMems string `json:"CpusetMems"` | |
399 | Memory int `json:"Memory"` | |
400 | MemorySwap int `json:"MemorySwap"` | |
401 | MemoryReservation int `json:"MemoryReservation"` | |
402 | KernelMemory int `json:"KernelMemory"` | |
403 | RestartPolicy RestartPolicy `json:"RestartPolicy,omitempty"` | |
404 | } | |
405 | ||
406 | // UpdateContainer updates the container at ID with the options | |
407 | // | |
408 | // See https://goo.gl/Y6fXUy for more details. | |
409 | func (c *Client) UpdateContainer(id string, opts UpdateContainerOptions) error { | |
410 | resp, err := c.do("POST", fmt.Sprintf("/containers/"+id+"/update"), doOptions{data: opts, forceJSON: true}) | |
411 | if err != nil { | |
412 | return err | |
413 | } | |
414 | defer resp.Body.Close() | |
415 | return nil | |
416 | } | |
417 | ||
418 | // RenameContainerOptions specify parameters to the RenameContainer function. | |
419 | // | |
420 | // See https://goo.gl/laSOIy for more details. | |
421 | type RenameContainerOptions struct { | |
422 | // ID of container to rename | |
423 | ID string `qs:"-"` | |
424 | ||
425 | // New name | |
426 | Name string `json:"name,omitempty" yaml:"name,omitempty"` | |
427 | } | |
428 | ||
429 | // RenameContainer updates and existing containers name | |
430 | // | |
431 | // See https://goo.gl/laSOIy for more details. | |
432 | func (c *Client) RenameContainer(opts RenameContainerOptions) error { | |
433 | resp, err := c.do("POST", fmt.Sprintf("/containers/"+opts.ID+"/rename?%s", queryString(opts)), doOptions{}) | |
434 | if err != nil { | |
435 | return err | |
436 | } | |
437 | resp.Body.Close() | |
438 | return nil | |
439 | } | |
440 | ||
441 | // InspectContainer returns information about a container by its ID. | |
442 | // | |
443 | // See https://goo.gl/RdIq0b for more details. | |
444 | func (c *Client) InspectContainer(id string) (*Container, error) { | |
445 | path := "/containers/" + id + "/json" | |
446 | resp, err := c.do("GET", path, doOptions{}) | |
447 | if err != nil { | |
448 | if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { | |
449 | return nil, &NoSuchContainer{ID: id} | |
450 | } | |
451 | return nil, err | |
452 | } | |
453 | defer resp.Body.Close() | |
454 | var container Container | |
455 | if err := json.NewDecoder(resp.Body).Decode(&container); err != nil { | |
456 | return nil, err | |
457 | } | |
458 | return &container, nil | |
459 | } | |
460 | ||
461 | // ContainerChanges returns changes in the filesystem of the given container. | |
462 | // | |
463 | // See https://goo.gl/9GsTIF for more details. | |
464 | func (c *Client) ContainerChanges(id string) ([]Change, error) { | |
465 | path := "/containers/" + id + "/changes" | |
466 | resp, err := c.do("GET", path, doOptions{}) | |
467 | if err != nil { | |
468 | if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { | |
469 | return nil, &NoSuchContainer{ID: id} | |
470 | } | |
471 | return nil, err | |
472 | } | |
473 | defer resp.Body.Close() | |
474 | var changes []Change | |
475 | if err := json.NewDecoder(resp.Body).Decode(&changes); err != nil { | |
476 | return nil, err | |
477 | } | |
478 | return changes, nil | |
479 | } | |
480 | ||
481 | // CreateContainerOptions specify parameters to the CreateContainer function. | |
482 | // | |
483 | // See https://goo.gl/WxQzrr for more details. | |
484 | type CreateContainerOptions struct { | |
485 | Name string | |
486 | Config *Config `qs:"-"` | |
487 | HostConfig *HostConfig `qs:"-"` | |
488 | } | |
489 | ||
490 | // CreateContainer creates a new container, returning the container instance, | |
491 | // or an error in case of failure. | |
492 | // | |
493 | // See https://goo.gl/WxQzrr for more details. | |
494 | func (c *Client) CreateContainer(opts CreateContainerOptions) (*Container, error) { | |
495 | path := "/containers/create?" + queryString(opts) | |
496 | resp, err := c.do( | |
497 | "POST", | |
498 | path, | |
499 | doOptions{ | |
500 | data: struct { | |
501 | *Config | |
502 | HostConfig *HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty"` | |
503 | }{ | |
504 | opts.Config, | |
505 | opts.HostConfig, | |
506 | }, | |
507 | }, | |
508 | ) | |
509 | ||
510 | if e, ok := err.(*Error); ok { | |
511 | if e.Status == http.StatusNotFound { | |
512 | return nil, ErrNoSuchImage | |
513 | } | |
514 | if e.Status == http.StatusConflict { | |
515 | return nil, ErrContainerAlreadyExists | |
516 | } | |
517 | } | |
518 | ||
519 | if err != nil { | |
520 | return nil, err | |
521 | } | |
522 | defer resp.Body.Close() | |
523 | var container Container | |
524 | if err := json.NewDecoder(resp.Body).Decode(&container); err != nil { | |
525 | return nil, err | |
526 | } | |
527 | ||
528 | container.Name = opts.Name | |
529 | ||
530 | return &container, nil | |
531 | } | |
532 | ||
533 | // KeyValuePair is a type for generic key/value pairs as used in the Lxc | |
534 | // configuration | |
535 | type KeyValuePair struct { | |
536 | Key string `json:"Key,omitempty" yaml:"Key,omitempty"` | |
537 | Value string `json:"Value,omitempty" yaml:"Value,omitempty"` | |
538 | } | |
539 | ||
540 | // RestartPolicy represents the policy for automatically restarting a container. | |
541 | // | |
542 | // Possible values are: | |
543 | // | |
544 | // - always: the docker daemon will always restart the container | |
545 | // - on-failure: the docker daemon will restart the container on failures, at | |
546 | // most MaximumRetryCount times | |
547 | // - no: the docker daemon will not restart the container automatically | |
548 | type RestartPolicy struct { | |
549 | Name string `json:"Name,omitempty" yaml:"Name,omitempty"` | |
550 | MaximumRetryCount int `json:"MaximumRetryCount,omitempty" yaml:"MaximumRetryCount,omitempty"` | |
551 | } | |
552 | ||
553 | // AlwaysRestart returns a restart policy that tells the Docker daemon to | |
554 | // always restart the container. | |
555 | func AlwaysRestart() RestartPolicy { | |
556 | return RestartPolicy{Name: "always"} | |
557 | } | |
558 | ||
559 | // RestartOnFailure returns a restart policy that tells the Docker daemon to | |
560 | // restart the container on failures, trying at most maxRetry times. | |
561 | func RestartOnFailure(maxRetry int) RestartPolicy { | |
562 | return RestartPolicy{Name: "on-failure", MaximumRetryCount: maxRetry} | |
563 | } | |
564 | ||
565 | // NeverRestart returns a restart policy that tells the Docker daemon to never | |
566 | // restart the container on failures. | |
567 | func NeverRestart() RestartPolicy { | |
568 | return RestartPolicy{Name: "no"} | |
569 | } | |
570 | ||
571 | // Device represents a device mapping between the Docker host and the | |
572 | // container. | |
573 | type Device struct { | |
574 | PathOnHost string `json:"PathOnHost,omitempty" yaml:"PathOnHost,omitempty"` | |
575 | PathInContainer string `json:"PathInContainer,omitempty" yaml:"PathInContainer,omitempty"` | |
576 | CgroupPermissions string `json:"CgroupPermissions,omitempty" yaml:"CgroupPermissions,omitempty"` | |
577 | } | |
578 | ||
579 | // BlockWeight represents a relative device weight for an individual device inside | |
580 | // of a container | |
581 | // | |
582 | // See https://goo.gl/FSdP0H for more details. | |
583 | type BlockWeight struct { | |
584 | Path string `json:"Path,omitempty"` | |
585 | Weight string `json:"Weight,omitempty"` | |
586 | } | |
587 | ||
588 | // BlockLimit represents a read/write limit in IOPS or Bandwidth for a device | |
589 | // inside of a container | |
590 | // | |
591 | // See https://goo.gl/FSdP0H for more details. | |
592 | type BlockLimit struct { | |
593 | Path string `json:"Path,omitempty"` | |
594 | Rate string `json:"Rate,omitempty"` | |
595 | } | |
596 | ||
597 | // HostConfig contains the container options related to starting a container on | |
598 | // a given host | |
599 | type HostConfig struct { | |
600 | Binds []string `json:"Binds,omitempty" yaml:"Binds,omitempty"` | |
601 | CapAdd []string `json:"CapAdd,omitempty" yaml:"CapAdd,omitempty"` | |
602 | CapDrop []string `json:"CapDrop,omitempty" yaml:"CapDrop,omitempty"` | |
603 | GroupAdd []string `json:"GroupAdd,omitempty" yaml:"GroupAdd,omitempty"` | |
604 | ContainerIDFile string `json:"ContainerIDFile,omitempty" yaml:"ContainerIDFile,omitempty"` | |
605 | LxcConf []KeyValuePair `json:"LxcConf,omitempty" yaml:"LxcConf,omitempty"` | |
606 | Privileged bool `json:"Privileged,omitempty" yaml:"Privileged,omitempty"` | |
607 | PortBindings map[Port][]PortBinding `json:"PortBindings,omitempty" yaml:"PortBindings,omitempty"` | |
608 | Links []string `json:"Links,omitempty" yaml:"Links,omitempty"` | |
609 | PublishAllPorts bool `json:"PublishAllPorts,omitempty" yaml:"PublishAllPorts,omitempty"` | |
610 | DNS []string `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.10 and above only | |
611 | DNSOptions []string `json:"DnsOptions,omitempty" yaml:"DnsOptions,omitempty"` | |
612 | DNSSearch []string `json:"DnsSearch,omitempty" yaml:"DnsSearch,omitempty"` | |
613 | ExtraHosts []string `json:"ExtraHosts,omitempty" yaml:"ExtraHosts,omitempty"` | |
614 | VolumesFrom []string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"` | |
615 | UsernsMode string `json:"UsernsMode,omitempty" yaml:"UsernsMode,omitempty"` | |
616 | NetworkMode string `json:"NetworkMode,omitempty" yaml:"NetworkMode,omitempty"` | |
617 | IpcMode string `json:"IpcMode,omitempty" yaml:"IpcMode,omitempty"` | |
618 | PidMode string `json:"PidMode,omitempty" yaml:"PidMode,omitempty"` | |
619 | UTSMode string `json:"UTSMode,omitempty" yaml:"UTSMode,omitempty"` | |
620 | RestartPolicy RestartPolicy `json:"RestartPolicy,omitempty" yaml:"RestartPolicy,omitempty"` | |
621 | Devices []Device `json:"Devices,omitempty" yaml:"Devices,omitempty"` | |
622 | LogConfig LogConfig `json:"LogConfig,omitempty" yaml:"LogConfig,omitempty"` | |
623 | ReadonlyRootfs bool `json:"ReadonlyRootfs,omitempty" yaml:"ReadonlyRootfs,omitempty"` | |
624 | SecurityOpt []string `json:"SecurityOpt,omitempty" yaml:"SecurityOpt,omitempty"` | |
625 | CgroupParent string `json:"CgroupParent,omitempty" yaml:"CgroupParent,omitempty"` | |
626 | Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty"` | |
627 | MemorySwap int64 `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty"` | |
628 | MemorySwappiness int64 `json:"MemorySwappiness,omitempty" yaml:"MemorySwappiness,omitempty"` | |
629 | OOMKillDisable bool `json:"OomKillDisable,omitempty" yaml:"OomKillDisable"` | |
630 | CPUShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty"` | |
631 | CPUSet string `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty"` | |
632 | CPUSetCPUs string `json:"CpusetCpus,omitempty" yaml:"CpusetCpus,omitempty"` | |
633 | CPUSetMEMs string `json:"CpusetMems,omitempty" yaml:"CpusetMems,omitempty"` | |
634 | CPUQuota int64 `json:"CpuQuota,omitempty" yaml:"CpuQuota,omitempty"` | |
635 | CPUPeriod int64 `json:"CpuPeriod,omitempty" yaml:"CpuPeriod,omitempty"` | |
636 | BlkioWeight int64 `json:"BlkioWeight,omitempty" yaml:"BlkioWeight"` | |
637 | BlkioWeightDevice []BlockWeight `json:"BlkioWeightDevice,omitempty" yaml:"BlkioWeightDevice"` | |
638 | BlkioDeviceReadBps []BlockLimit `json:"BlkioDeviceReadBps,omitempty" yaml:"BlkioDeviceReadBps"` | |
639 | BlkioDeviceReadIOps []BlockLimit `json:"BlkioDeviceReadIOps,omitempty" yaml:"BlkioDeviceReadIOps"` | |
640 | BlkioDeviceWriteBps []BlockLimit `json:"BlkioDeviceWriteBps,omitempty" yaml:"BlkioDeviceWriteBps"` | |
641 | BlkioDeviceWriteIOps []BlockLimit `json:"BlkioDeviceWriteIOps,omitempty" yaml:"BlkioDeviceWriteIOps"` | |
642 | Ulimits []ULimit `json:"Ulimits,omitempty" yaml:"Ulimits,omitempty"` | |
643 | VolumeDriver string `json:"VolumeDriver,omitempty" yaml:"VolumeDriver,omitempty"` | |
644 | OomScoreAdj int `json:"OomScoreAdj,omitempty" yaml:"OomScoreAdj,omitempty"` | |
645 | ShmSize int64 `json:"ShmSize,omitempty" yaml:"ShmSize,omitempty"` | |
646 | } | |
647 | ||
648 | // StartContainer starts a container, returning an error in case of failure. | |
649 | // | |
650 | // See https://goo.gl/MrBAJv for more details. | |
651 | func (c *Client) StartContainer(id string, hostConfig *HostConfig) error { | |
652 | path := "/containers/" + id + "/start" | |
653 | resp, err := c.do("POST", path, doOptions{data: hostConfig, forceJSON: true}) | |
654 | if err != nil { | |
655 | if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { | |
656 | return &NoSuchContainer{ID: id, Err: err} | |
657 | } | |
658 | return err | |
659 | } | |
660 | if resp.StatusCode == http.StatusNotModified { | |
661 | return &ContainerAlreadyRunning{ID: id} | |
662 | } | |
663 | resp.Body.Close() | |
664 | return nil | |
665 | } | |
666 | ||
667 | // StopContainer stops a container, killing it after the given timeout (in | |
668 | // seconds). | |
669 | // | |
670 | // See https://goo.gl/USqsFt for more details. | |
671 | func (c *Client) StopContainer(id string, timeout uint) error { | |
672 | path := fmt.Sprintf("/containers/%s/stop?t=%d", id, timeout) | |
673 | resp, err := c.do("POST", path, doOptions{}) | |
674 | if err != nil { | |
675 | if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { | |
676 | return &NoSuchContainer{ID: id} | |
677 | } | |
678 | return err | |
679 | } | |
680 | if resp.StatusCode == http.StatusNotModified { | |
681 | return &ContainerNotRunning{ID: id} | |
682 | } | |
683 | resp.Body.Close() | |
684 | return nil | |
685 | } | |
686 | ||
687 | // RestartContainer stops a container, killing it after the given timeout (in | |
688 | // seconds), during the stop process. | |
689 | // | |
690 | // See https://goo.gl/QzsDnz for more details. | |
691 | func (c *Client) RestartContainer(id string, timeout uint) error { | |
692 | path := fmt.Sprintf("/containers/%s/restart?t=%d", id, timeout) | |
693 | resp, err := c.do("POST", path, doOptions{}) | |
694 | if err != nil { | |
695 | if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { | |
696 | return &NoSuchContainer{ID: id} | |
697 | } | |
698 | return err | |
699 | } | |
700 | resp.Body.Close() | |
701 | return nil | |
702 | } | |
703 | ||
704 | // PauseContainer pauses the given container. | |
705 | // | |
706 | // See https://goo.gl/OF7W9X for more details. | |
707 | func (c *Client) PauseContainer(id string) error { | |
708 | path := fmt.Sprintf("/containers/%s/pause", id) | |
709 | resp, err := c.do("POST", path, doOptions{}) | |
710 | if err != nil { | |
711 | if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { | |
712 | return &NoSuchContainer{ID: id} | |
713 | } | |
714 | return err | |
715 | } | |
716 | resp.Body.Close() | |
717 | return nil | |
718 | } | |
719 | ||
720 | // UnpauseContainer unpauses the given container. | |
721 | // | |
722 | // See https://goo.gl/7dwyPA for more details. | |
723 | func (c *Client) UnpauseContainer(id string) error { | |
724 | path := fmt.Sprintf("/containers/%s/unpause", id) | |
725 | resp, err := c.do("POST", path, doOptions{}) | |
726 | if err != nil { | |
727 | if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { | |
728 | return &NoSuchContainer{ID: id} | |
729 | } | |
730 | return err | |
731 | } | |
732 | resp.Body.Close() | |
733 | return nil | |
734 | } | |
735 | ||
736 | // TopResult represents the list of processes running in a container, as | |
737 | // returned by /containers/<id>/top. | |
738 | // | |
739 | // See https://goo.gl/Rb46aY for more details. | |
740 | type TopResult struct { | |
741 | Titles []string | |
742 | Processes [][]string | |
743 | } | |
744 | ||
745 | // TopContainer returns processes running inside a container | |
746 | // | |
747 | // See https://goo.gl/Rb46aY for more details. | |
748 | func (c *Client) TopContainer(id string, psArgs string) (TopResult, error) { | |
749 | var args string | |
750 | var result TopResult | |
751 | if psArgs != "" { | |
752 | args = fmt.Sprintf("?ps_args=%s", psArgs) | |
753 | } | |
754 | path := fmt.Sprintf("/containers/%s/top%s", id, args) | |
755 | resp, err := c.do("GET", path, doOptions{}) | |
756 | if err != nil { | |
757 | if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { | |
758 | return result, &NoSuchContainer{ID: id} | |
759 | } | |
760 | return result, err | |
761 | } | |
762 | defer resp.Body.Close() | |
763 | if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { | |
764 | return result, err | |
765 | } | |
766 | return result, nil | |
767 | } | |
768 | ||
769 | // Stats represents container statistics, returned by /containers/<id>/stats. | |
770 | // | |
771 | // See https://goo.gl/GNmLHb for more details. | |
772 | type Stats struct { | |
773 | Read time.Time `json:"read,omitempty" yaml:"read,omitempty"` | |
774 | PidsStats struct { | |
775 | Current uint64 `json:"current,omitempty" yaml:"current,omitempty"` | |
776 | } `json:"pids_stats,omitempty" yaml:"pids_stats,omitempty"` | |
777 | Network NetworkStats `json:"network,omitempty" yaml:"network,omitempty"` | |
778 | Networks map[string]NetworkStats `json:"networks,omitempty" yaml:"networks,omitempty"` | |
779 | MemoryStats struct { | |
780 | Stats struct { | |
781 | TotalPgmafault uint64 `json:"total_pgmafault,omitempty" yaml:"total_pgmafault,omitempty"` | |
782 | Cache uint64 `json:"cache,omitempty" yaml:"cache,omitempty"` | |
783 | MappedFile uint64 `json:"mapped_file,omitempty" yaml:"mapped_file,omitempty"` | |
784 | TotalInactiveFile uint64 `json:"total_inactive_file,omitempty" yaml:"total_inactive_file,omitempty"` | |
785 | Pgpgout uint64 `json:"pgpgout,omitempty" yaml:"pgpgout,omitempty"` | |
786 | Rss uint64 `json:"rss,omitempty" yaml:"rss,omitempty"` | |
787 | TotalMappedFile uint64 `json:"total_mapped_file,omitempty" yaml:"total_mapped_file,omitempty"` | |
788 | Writeback uint64 `json:"writeback,omitempty" yaml:"writeback,omitempty"` | |
789 | Unevictable uint64 `json:"unevictable,omitempty" yaml:"unevictable,omitempty"` | |
790 | Pgpgin uint64 `json:"pgpgin,omitempty" yaml:"pgpgin,omitempty"` | |
791 | TotalUnevictable uint64 `json:"total_unevictable,omitempty" yaml:"total_unevictable,omitempty"` | |
792 | Pgmajfault uint64 `json:"pgmajfault,omitempty" yaml:"pgmajfault,omitempty"` | |
793 | TotalRss uint64 `json:"total_rss,omitempty" yaml:"total_rss,omitempty"` | |
794 | TotalRssHuge uint64 `json:"total_rss_huge,omitempty" yaml:"total_rss_huge,omitempty"` | |
795 | TotalWriteback uint64 `json:"total_writeback,omitempty" yaml:"total_writeback,omitempty"` | |
796 | TotalInactiveAnon uint64 `json:"total_inactive_anon,omitempty" yaml:"total_inactive_anon,omitempty"` | |
797 | RssHuge uint64 `json:"rss_huge,omitempty" yaml:"rss_huge,omitempty"` | |
798 | HierarchicalMemoryLimit uint64 `json:"hierarchical_memory_limit,omitempty" yaml:"hierarchical_memory_limit,omitempty"` | |
799 | TotalPgfault uint64 `json:"total_pgfault,omitempty" yaml:"total_pgfault,omitempty"` | |
800 | TotalActiveFile uint64 `json:"total_active_file,omitempty" yaml:"total_active_file,omitempty"` | |
801 | ActiveAnon uint64 `json:"active_anon,omitempty" yaml:"active_anon,omitempty"` | |
802 | TotalActiveAnon uint64 `json:"total_active_anon,omitempty" yaml:"total_active_anon,omitempty"` | |
803 | TotalPgpgout uint64 `json:"total_pgpgout,omitempty" yaml:"total_pgpgout,omitempty"` | |
804 | TotalCache uint64 `json:"total_cache,omitempty" yaml:"total_cache,omitempty"` | |
805 | InactiveAnon uint64 `json:"inactive_anon,omitempty" yaml:"inactive_anon,omitempty"` | |
806 | ActiveFile uint64 `json:"active_file,omitempty" yaml:"active_file,omitempty"` | |
807 | Pgfault uint64 `json:"pgfault,omitempty" yaml:"pgfault,omitempty"` | |
808 | InactiveFile uint64 `json:"inactive_file,omitempty" yaml:"inactive_file,omitempty"` | |
809 | TotalPgpgin uint64 `json:"total_pgpgin,omitempty" yaml:"total_pgpgin,omitempty"` | |
810 | HierarchicalMemswLimit uint64 `json:"hierarchical_memsw_limit,omitempty" yaml:"hierarchical_memsw_limit,omitempty"` | |
811 | Swap uint64 `json:"swap,omitempty" yaml:"swap,omitempty"` | |
812 | } `json:"stats,omitempty" yaml:"stats,omitempty"` | |
813 | MaxUsage uint64 `json:"max_usage,omitempty" yaml:"max_usage,omitempty"` | |
814 | Usage uint64 `json:"usage,omitempty" yaml:"usage,omitempty"` | |
815 | Failcnt uint64 `json:"failcnt,omitempty" yaml:"failcnt,omitempty"` | |
816 | Limit uint64 `json:"limit,omitempty" yaml:"limit,omitempty"` | |
817 | } `json:"memory_stats,omitempty" yaml:"memory_stats,omitempty"` | |
818 | BlkioStats struct { | |
819 | IOServiceBytesRecursive []BlkioStatsEntry `json:"io_service_bytes_recursive,omitempty" yaml:"io_service_bytes_recursive,omitempty"` | |
820 | IOServicedRecursive []BlkioStatsEntry `json:"io_serviced_recursive,omitempty" yaml:"io_serviced_recursive,omitempty"` | |
821 | IOQueueRecursive []BlkioStatsEntry `json:"io_queue_recursive,omitempty" yaml:"io_queue_recursive,omitempty"` | |
822 | IOServiceTimeRecursive []BlkioStatsEntry `json:"io_service_time_recursive,omitempty" yaml:"io_service_time_recursive,omitempty"` | |
823 | IOWaitTimeRecursive []BlkioStatsEntry `json:"io_wait_time_recursive,omitempty" yaml:"io_wait_time_recursive,omitempty"` | |
824 | IOMergedRecursive []BlkioStatsEntry `json:"io_merged_recursive,omitempty" yaml:"io_merged_recursive,omitempty"` | |
825 | IOTimeRecursive []BlkioStatsEntry `json:"io_time_recursive,omitempty" yaml:"io_time_recursive,omitempty"` | |
826 | SectorsRecursive []BlkioStatsEntry `json:"sectors_recursive,omitempty" yaml:"sectors_recursive,omitempty"` | |
827 | } `json:"blkio_stats,omitempty" yaml:"blkio_stats,omitempty"` | |
828 | CPUStats CPUStats `json:"cpu_stats,omitempty" yaml:"cpu_stats,omitempty"` | |
829 | PreCPUStats CPUStats `json:"precpu_stats,omitempty"` | |
830 | } | |
831 | ||
832 | // NetworkStats is a stats entry for network stats | |
833 | type NetworkStats struct { | |
834 | RxDropped uint64 `json:"rx_dropped,omitempty" yaml:"rx_dropped,omitempty"` | |
835 | RxBytes uint64 `json:"rx_bytes,omitempty" yaml:"rx_bytes,omitempty"` | |
836 | RxErrors uint64 `json:"rx_errors,omitempty" yaml:"rx_errors,omitempty"` | |
837 | TxPackets uint64 `json:"tx_packets,omitempty" yaml:"tx_packets,omitempty"` | |
838 | TxDropped uint64 `json:"tx_dropped,omitempty" yaml:"tx_dropped,omitempty"` | |
839 | RxPackets uint64 `json:"rx_packets,omitempty" yaml:"rx_packets,omitempty"` | |
840 | TxErrors uint64 `json:"tx_errors,omitempty" yaml:"tx_errors,omitempty"` | |
841 | TxBytes uint64 `json:"tx_bytes,omitempty" yaml:"tx_bytes,omitempty"` | |
842 | } | |
843 | ||
844 | // CPUStats is a stats entry for cpu stats | |
845 | type CPUStats struct { | |
846 | CPUUsage struct { | |
847 | PercpuUsage []uint64 `json:"percpu_usage,omitempty" yaml:"percpu_usage,omitempty"` | |
848 | UsageInUsermode uint64 `json:"usage_in_usermode,omitempty" yaml:"usage_in_usermode,omitempty"` | |
849 | TotalUsage uint64 `json:"total_usage,omitempty" yaml:"total_usage,omitempty"` | |
850 | UsageInKernelmode uint64 `json:"usage_in_kernelmode,omitempty" yaml:"usage_in_kernelmode,omitempty"` | |
851 | } `json:"cpu_usage,omitempty" yaml:"cpu_usage,omitempty"` | |
852 | SystemCPUUsage uint64 `json:"system_cpu_usage,omitempty" yaml:"system_cpu_usage,omitempty"` | |
853 | ThrottlingData struct { | |
854 | Periods uint64 `json:"periods,omitempty"` | |
855 | ThrottledPeriods uint64 `json:"throttled_periods,omitempty"` | |
856 | ThrottledTime uint64 `json:"throttled_time,omitempty"` | |
857 | } `json:"throttling_data,omitempty" yaml:"throttling_data,omitempty"` | |
858 | } | |
859 | ||
860 | // BlkioStatsEntry is a stats entry for blkio_stats | |
861 | type BlkioStatsEntry struct { | |
862 | Major uint64 `json:"major,omitempty" yaml:"major,omitempty"` | |
863 | Minor uint64 `json:"minor,omitempty" yaml:"minor,omitempty"` | |
864 | Op string `json:"op,omitempty" yaml:"op,omitempty"` | |
865 | Value uint64 `json:"value,omitempty" yaml:"value,omitempty"` | |
866 | } | |
867 | ||
868 | // StatsOptions specify parameters to the Stats function. | |
869 | // | |
870 | // See https://goo.gl/GNmLHb for more details. | |
871 | type StatsOptions struct { | |
872 | ID string | |
873 | Stats chan<- *Stats | |
874 | Stream bool | |
875 | // A flag that enables stopping the stats operation | |
876 | Done <-chan bool | |
877 | // Initial connection timeout | |
878 | Timeout time.Duration | |
879 | // Timeout with no data is received, it's reset every time new data | |
880 | // arrives | |
881 | InactivityTimeout time.Duration `qs:"-"` | |
882 | } | |
883 | ||
884 | // Stats sends container statistics for the given container to the given channel. | |
885 | // | |
886 | // This function is blocking, similar to a streaming call for logs, and should be run | |
887 | // on a separate goroutine from the caller. Note that this function will block until | |
888 | // the given container is removed, not just exited. When finished, this function | |
889 | // will close the given channel. Alternatively, function can be stopped by | |
890 | // signaling on the Done channel. | |
891 | // | |
892 | // See https://goo.gl/GNmLHb for more details. | |
893 | func (c *Client) Stats(opts StatsOptions) (retErr error) { | |
894 | errC := make(chan error, 1) | |
895 | readCloser, writeCloser := io.Pipe() | |
896 | ||
897 | defer func() { | |
898 | close(opts.Stats) | |
899 | ||
900 | select { | |
901 | case err := <-errC: | |
902 | if err != nil && retErr == nil { | |
903 | retErr = err | |
904 | } | |
905 | default: | |
906 | // No errors | |
907 | } | |
908 | ||
909 | if err := readCloser.Close(); err != nil && retErr == nil { | |
910 | retErr = err | |
911 | } | |
912 | }() | |
913 | ||
914 | go func() { | |
915 | err := c.stream("GET", fmt.Sprintf("/containers/%s/stats?stream=%v", opts.ID, opts.Stream), streamOptions{ | |
916 | rawJSONStream: true, | |
917 | useJSONDecoder: true, | |
918 | stdout: writeCloser, | |
919 | timeout: opts.Timeout, | |
920 | inactivityTimeout: opts.InactivityTimeout, | |
921 | }) | |
922 | if err != nil { | |
923 | dockerError, ok := err.(*Error) | |
924 | if ok { | |
925 | if dockerError.Status == http.StatusNotFound { | |
926 | err = &NoSuchContainer{ID: opts.ID} | |
927 | } | |
928 | } | |
929 | } | |
930 | if closeErr := writeCloser.Close(); closeErr != nil && err == nil { | |
931 | err = closeErr | |
932 | } | |
933 | errC <- err | |
934 | close(errC) | |
935 | }() | |
936 | ||
937 | quit := make(chan struct{}) | |
938 | defer close(quit) | |
939 | go func() { | |
940 | // block here waiting for the signal to stop function | |
941 | select { | |
942 | case <-opts.Done: | |
943 | readCloser.Close() | |
944 | case <-quit: | |
945 | return | |
946 | } | |
947 | }() | |
948 | ||
949 | decoder := json.NewDecoder(readCloser) | |
950 | stats := new(Stats) | |
951 | for err := decoder.Decode(stats); err != io.EOF; err = decoder.Decode(stats) { | |
952 | if err != nil { | |
953 | return err | |
954 | } | |
955 | opts.Stats <- stats | |
956 | stats = new(Stats) | |
957 | } | |
958 | return nil | |
959 | } | |
960 | ||
961 | // KillContainerOptions represents the set of options that can be used in a | |
962 | // call to KillContainer. | |
963 | // | |
964 | // See https://goo.gl/hkS9i8 for more details. | |
965 | type KillContainerOptions struct { | |
966 | // The ID of the container. | |
967 | ID string `qs:"-"` | |
968 | ||
969 | // The signal to send to the container. When omitted, Docker server | |
970 | // will assume SIGKILL. | |
971 | Signal Signal | |
972 | } | |
973 | ||
974 | // KillContainer sends a signal to a container, returning an error in case of | |
975 | // failure. | |
976 | // | |
977 | // See https://goo.gl/hkS9i8 for more details. | |
978 | func (c *Client) KillContainer(opts KillContainerOptions) error { | |
979 | path := "/containers/" + opts.ID + "/kill" + "?" + queryString(opts) | |
980 | resp, err := c.do("POST", path, doOptions{}) | |
981 | if err != nil { | |
982 | if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { | |
983 | return &NoSuchContainer{ID: opts.ID} | |
984 | } | |
985 | return err | |
986 | } | |
987 | resp.Body.Close() | |
988 | return nil | |
989 | } | |
990 | ||
991 | // RemoveContainerOptions encapsulates options to remove a container. | |
992 | // | |
993 | // See https://goo.gl/RQyX62 for more details. | |
994 | type RemoveContainerOptions struct { | |
995 | // The ID of the container. | |
996 | ID string `qs:"-"` | |
997 | ||
998 | // A flag that indicates whether Docker should remove the volumes | |
999 | // associated to the container. | |
1000 | RemoveVolumes bool `qs:"v"` | |
1001 | ||
1002 | // A flag that indicates whether Docker should remove the container | |
1003 | // even if it is currently running. | |
1004 | Force bool | |
1005 | } | |
1006 | ||
1007 | // RemoveContainer removes a container, returning an error in case of failure. | |
1008 | // | |
1009 | // See https://goo.gl/RQyX62 for more details. | |
1010 | func (c *Client) RemoveContainer(opts RemoveContainerOptions) error { | |
1011 | path := "/containers/" + opts.ID + "?" + queryString(opts) | |
1012 | resp, err := c.do("DELETE", path, doOptions{}) | |
1013 | if err != nil { | |
1014 | if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { | |
1015 | return &NoSuchContainer{ID: opts.ID} | |
1016 | } | |
1017 | return err | |
1018 | } | |
1019 | resp.Body.Close() | |
1020 | return nil | |
1021 | } | |
1022 | ||
1023 | // UploadToContainerOptions is the set of options that can be used when | |
1024 | // uploading an archive into a container. | |
1025 | // | |
1026 | // See https://goo.gl/Ss97HW for more details. | |
1027 | type UploadToContainerOptions struct { | |
1028 | InputStream io.Reader `json:"-" qs:"-"` | |
1029 | Path string `qs:"path"` | |
1030 | NoOverwriteDirNonDir bool `qs:"noOverwriteDirNonDir"` | |
1031 | } | |
1032 | ||
1033 | // UploadToContainer uploads a tar archive to be extracted to a path in the | |
1034 | // filesystem of the container. | |
1035 | // | |
1036 | // See https://goo.gl/Ss97HW for more details. | |
1037 | func (c *Client) UploadToContainer(id string, opts UploadToContainerOptions) error { | |
1038 | url := fmt.Sprintf("/containers/%s/archive?", id) + queryString(opts) | |
1039 | ||
1040 | return c.stream("PUT", url, streamOptions{ | |
1041 | in: opts.InputStream, | |
1042 | }) | |
1043 | } | |
1044 | ||
1045 | // DownloadFromContainerOptions is the set of options that can be used when | |
1046 | // downloading resources from a container. | |
1047 | // | |
1048 | // See https://goo.gl/KnZJDX for more details. | |
1049 | type DownloadFromContainerOptions struct { | |
1050 | OutputStream io.Writer `json:"-" qs:"-"` | |
1051 | Path string `qs:"path"` | |
1052 | InactivityTimeout time.Duration `qs:"-"` | |
1053 | } | |
1054 | ||
1055 | // DownloadFromContainer downloads a tar archive of files or folders in a container. | |
1056 | // | |
1057 | // See https://goo.gl/KnZJDX for more details. | |
1058 | func (c *Client) DownloadFromContainer(id string, opts DownloadFromContainerOptions) error { | |
1059 | url := fmt.Sprintf("/containers/%s/archive?", id) + queryString(opts) | |
1060 | ||
1061 | return c.stream("GET", url, streamOptions{ | |
1062 | setRawTerminal: true, | |
1063 | stdout: opts.OutputStream, | |
1064 | inactivityTimeout: opts.InactivityTimeout, | |
1065 | }) | |
1066 | } | |
1067 | ||
1068 | // CopyFromContainerOptions has been DEPRECATED, please use DownloadFromContainerOptions along with DownloadFromContainer. | |
1069 | // | |
1070 | // See https://goo.gl/R2jevW for more details. | |
1071 | type CopyFromContainerOptions struct { | |
1072 | OutputStream io.Writer `json:"-"` | |
1073 | Container string `json:"-"` | |
1074 | Resource string | |
1075 | } | |
1076 | ||
1077 | // CopyFromContainer has been DEPRECATED, please use DownloadFromContainerOptions along with DownloadFromContainer. | |
1078 | // | |
1079 | // See https://goo.gl/R2jevW for more details. | |
1080 | func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error { | |
1081 | if opts.Container == "" { | |
1082 | return &NoSuchContainer{ID: opts.Container} | |
1083 | } | |
1084 | url := fmt.Sprintf("/containers/%s/copy", opts.Container) | |
1085 | resp, err := c.do("POST", url, doOptions{data: opts}) | |
1086 | if err != nil { | |
1087 | if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { | |
1088 | return &NoSuchContainer{ID: opts.Container} | |
1089 | } | |
1090 | return err | |
1091 | } | |
1092 | defer resp.Body.Close() | |
1093 | _, err = io.Copy(opts.OutputStream, resp.Body) | |
1094 | return err | |
1095 | } | |
1096 | ||
1097 | // WaitContainer blocks until the given container stops, return the exit code | |
1098 | // of the container status. | |
1099 | // | |
1100 | // See https://goo.gl/Gc1rge for more details. | |
1101 | func (c *Client) WaitContainer(id string) (int, error) { | |
1102 | resp, err := c.do("POST", "/containers/"+id+"/wait", doOptions{}) | |
1103 | if err != nil { | |
1104 | if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { | |
1105 | return 0, &NoSuchContainer{ID: id} | |
1106 | } | |
1107 | return 0, err | |
1108 | } | |
1109 | defer resp.Body.Close() | |
1110 | var r struct{ StatusCode int } | |
1111 | if err := json.NewDecoder(resp.Body).Decode(&r); err != nil { | |
1112 | return 0, err | |
1113 | } | |
1114 | return r.StatusCode, nil | |
1115 | } | |
1116 | ||
1117 | // CommitContainerOptions aggregates parameters to the CommitContainer method. | |
1118 | // | |
1119 | // See https://goo.gl/mqfoCw for more details. | |
1120 | type CommitContainerOptions struct { | |
1121 | Container string | |
1122 | Repository string `qs:"repo"` | |
1123 | Tag string | |
1124 | Message string `qs:"comment"` | |
1125 | Author string | |
1126 | Run *Config `qs:"-"` | |
1127 | } | |
1128 | ||
1129 | // CommitContainer creates a new image from a container's changes. | |
1130 | // | |
1131 | // See https://goo.gl/mqfoCw for more details. | |
1132 | func (c *Client) CommitContainer(opts CommitContainerOptions) (*Image, error) { | |
1133 | path := "/commit?" + queryString(opts) | |
1134 | resp, err := c.do("POST", path, doOptions{data: opts.Run}) | |
1135 | if err != nil { | |
1136 | if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { | |
1137 | return nil, &NoSuchContainer{ID: opts.Container} | |
1138 | } | |
1139 | return nil, err | |
1140 | } | |
1141 | defer resp.Body.Close() | |
1142 | var image Image | |
1143 | if err := json.NewDecoder(resp.Body).Decode(&image); err != nil { | |
1144 | return nil, err | |
1145 | } | |
1146 | return &image, nil | |
1147 | } | |
1148 | ||
1149 | // AttachToContainerOptions is the set of options that can be used when | |
1150 | // attaching to a container. | |
1151 | // | |
1152 | // See https://goo.gl/NKpkFk for more details. | |
1153 | type AttachToContainerOptions struct { | |
1154 | Container string `qs:"-"` | |
1155 | InputStream io.Reader `qs:"-"` | |
1156 | OutputStream io.Writer `qs:"-"` | |
1157 | ErrorStream io.Writer `qs:"-"` | |
1158 | ||
1159 | // Get container logs, sending it to OutputStream. | |
1160 | Logs bool | |
1161 | ||
1162 | // Stream the response? | |
1163 | Stream bool | |
1164 | ||
1165 | // Attach to stdin, and use InputStream. | |
1166 | Stdin bool | |
1167 | ||
1168 | // Attach to stdout, and use OutputStream. | |
1169 | Stdout bool | |
1170 | ||
1171 | // Attach to stderr, and use ErrorStream. | |
1172 | Stderr bool | |
1173 | ||
1174 | // If set, after a successful connect, a sentinel will be sent and then the | |
1175 | // client will block on receive before continuing. | |
1176 | // | |
1177 | // It must be an unbuffered channel. Using a buffered channel can lead | |
1178 | // to unexpected behavior. | |
1179 | Success chan struct{} | |
1180 | ||
1181 | // Use raw terminal? Usually true when the container contains a TTY. | |
1182 | RawTerminal bool `qs:"-"` | |
1183 | } | |
1184 | ||
1185 | // AttachToContainer attaches to a container, using the given options. | |
1186 | // | |
1187 | // See https://goo.gl/NKpkFk for more details. | |
1188 | func (c *Client) AttachToContainer(opts AttachToContainerOptions) error { | |
1189 | cw, err := c.AttachToContainerNonBlocking(opts) | |
1190 | if err != nil { | |
1191 | return err | |
1192 | } | |
1193 | return cw.Wait() | |
1194 | } | |
1195 | ||
1196 | // AttachToContainerNonBlocking attaches to a container, using the given options. | |
1197 | // This function does not block. | |
1198 | // | |
1199 | // See https://goo.gl/NKpkFk for more details. | |
1200 | func (c *Client) AttachToContainerNonBlocking(opts AttachToContainerOptions) (CloseWaiter, error) { | |
1201 | if opts.Container == "" { | |
1202 | return nil, &NoSuchContainer{ID: opts.Container} | |
1203 | } | |
1204 | path := "/containers/" + opts.Container + "/attach?" + queryString(opts) | |
1205 | return c.hijack("POST", path, hijackOptions{ | |
1206 | success: opts.Success, | |
1207 | setRawTerminal: opts.RawTerminal, | |
1208 | in: opts.InputStream, | |
1209 | stdout: opts.OutputStream, | |
1210 | stderr: opts.ErrorStream, | |
1211 | }) | |
1212 | } | |
1213 | ||
1214 | // LogsOptions represents the set of options used when getting logs from a | |
1215 | // container. | |
1216 | // | |
1217 | // See https://goo.gl/yl8PGm for more details. | |
1218 | type LogsOptions struct { | |
1219 | Container string `qs:"-"` | |
1220 | OutputStream io.Writer `qs:"-"` | |
1221 | ErrorStream io.Writer `qs:"-"` | |
1222 | InactivityTimeout time.Duration `qs:"-"` | |
1223 | Follow bool | |
1224 | Stdout bool | |
1225 | Stderr bool | |
1226 | Since int64 | |
1227 | Timestamps bool | |
1228 | Tail string | |
1229 | ||
1230 | // Use raw terminal? Usually true when the container contains a TTY. | |
1231 | RawTerminal bool `qs:"-"` | |
1232 | } | |
1233 | ||
1234 | // Logs gets stdout and stderr logs from the specified container. | |
1235 | // | |
1236 | // See https://goo.gl/yl8PGm for more details. | |
1237 | func (c *Client) Logs(opts LogsOptions) error { | |
1238 | if opts.Container == "" { | |
1239 | return &NoSuchContainer{ID: opts.Container} | |
1240 | } | |
1241 | if opts.Tail == "" { | |
1242 | opts.Tail = "all" | |
1243 | } | |
1244 | path := "/containers/" + opts.Container + "/logs?" + queryString(opts) | |
1245 | return c.stream("GET", path, streamOptions{ | |
1246 | setRawTerminal: opts.RawTerminal, | |
1247 | stdout: opts.OutputStream, | |
1248 | stderr: opts.ErrorStream, | |
1249 | inactivityTimeout: opts.InactivityTimeout, | |
1250 | }) | |
1251 | } | |
1252 | ||
1253 | // ResizeContainerTTY resizes the terminal to the given height and width. | |
1254 | // | |
1255 | // See https://goo.gl/xERhCc for more details. | |
1256 | func (c *Client) ResizeContainerTTY(id string, height, width int) error { | |
1257 | params := make(url.Values) | |
1258 | params.Set("h", strconv.Itoa(height)) | |
1259 | params.Set("w", strconv.Itoa(width)) | |
1260 | resp, err := c.do("POST", "/containers/"+id+"/resize?"+params.Encode(), doOptions{}) | |
1261 | if err != nil { | |
1262 | return err | |
1263 | } | |
1264 | resp.Body.Close() | |
1265 | return nil | |
1266 | } | |
1267 | ||
1268 | // ExportContainerOptions is the set of parameters to the ExportContainer | |
1269 | // method. | |
1270 | // | |
1271 | // See https://goo.gl/dOkTyk for more details. | |
1272 | type ExportContainerOptions struct { | |
1273 | ID string | |
1274 | OutputStream io.Writer | |
1275 | InactivityTimeout time.Duration `qs:"-"` | |
1276 | } | |
1277 | ||
1278 | // ExportContainer export the contents of container id as tar archive | |
1279 | // and prints the exported contents to stdout. | |
1280 | // | |
1281 | // See https://goo.gl/dOkTyk for more details. | |
1282 | func (c *Client) ExportContainer(opts ExportContainerOptions) error { | |
1283 | if opts.ID == "" { | |
1284 | return &NoSuchContainer{ID: opts.ID} | |
1285 | } | |
1286 | url := fmt.Sprintf("/containers/%s/export", opts.ID) | |
1287 | return c.stream("GET", url, streamOptions{ | |
1288 | setRawTerminal: true, | |
1289 | stdout: opts.OutputStream, | |
1290 | inactivityTimeout: opts.InactivityTimeout, | |
1291 | }) | |
1292 | } | |
1293 | ||
1294 | // NoSuchContainer is the error returned when a given container does not exist. | |
1295 | type NoSuchContainer struct { | |
1296 | ID string | |
1297 | Err error | |
1298 | } | |
1299 | ||
1300 | func (err *NoSuchContainer) Error() string { | |
1301 | if err.Err != nil { | |
1302 | return err.Err.Error() | |
1303 | } | |
1304 | return "No such container: " + err.ID | |
1305 | } | |
1306 | ||
1307 | // ContainerAlreadyRunning is the error returned when a given container is | |
1308 | // already running. | |
1309 | type ContainerAlreadyRunning struct { | |
1310 | ID string | |
1311 | } | |
1312 | ||
1313 | func (err *ContainerAlreadyRunning) Error() string { | |
1314 | return "Container already running: " + err.ID | |
1315 | } | |
1316 | ||
1317 | // ContainerNotRunning is the error returned when a given container is not | |
1318 | // running. | |
1319 | type ContainerNotRunning struct { | |
1320 | ID string | |
1321 | } | |
1322 | ||
1323 | func (err *ContainerNotRunning) Error() string { | |
1324 | return "Container not running: " + err.ID | |
1325 | } |