| // Copyright 2015 go-dockerclient authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package docker |
| |
| import ( |
| "encoding/json" |
| "errors" |
| "fmt" |
| "io" |
| "net/http" |
| "net/url" |
| "strconv" |
| "strings" |
| "time" |
| |
| "github.com/fsouza/go-dockerclient/external/github.com/docker/go-units" |
| ) |
| |
| // ErrContainerAlreadyExists is the error returned by CreateContainer when the |
| // container already exists. |
| var ErrContainerAlreadyExists = errors.New("container already exists") |
| |
| // ListContainersOptions specify parameters to the ListContainers function. |
| // |
| // See https://goo.gl/47a6tO for more details. |
| type ListContainersOptions struct { |
| All bool |
| Size bool |
| Limit int |
| Since string |
| Before string |
| Filters map[string][]string |
| } |
| |
| // APIPort is a type that represents a port mapping returned by the Docker API |
| type APIPort struct { |
| PrivatePort int64 `json:"PrivatePort,omitempty" yaml:"PrivatePort,omitempty"` |
| PublicPort int64 `json:"PublicPort,omitempty" yaml:"PublicPort,omitempty"` |
| Type string `json:"Type,omitempty" yaml:"Type,omitempty"` |
| IP string `json:"IP,omitempty" yaml:"IP,omitempty"` |
| } |
| |
| // APIMount represents a mount point for a container. |
| type APIMount struct { |
| Name string `json:"Name,omitempty" yaml:"Name,omitempty"` |
| Source string `json:"Source,omitempty" yaml:"Source,omitempty"` |
| Destination string `json:"Destination,omitempty" yaml:"Destination,omitempty"` |
| Driver string `json:"Driver,omitempty" yaml:"Driver,omitempty"` |
| Mode string `json:"Mode,omitempty" yaml:"Mode,omitempty"` |
| RW bool `json:"RW,omitempty" yaml:"RW,omitempty"` |
| Propogation string `json:"Propogation,omitempty" yaml:"Propogation,omitempty"` |
| } |
| |
| // APIContainers represents each container in the list returned by |
| // ListContainers. |
| type APIContainers struct { |
| ID string `json:"Id" yaml:"Id"` |
| Image string `json:"Image,omitempty" yaml:"Image,omitempty"` |
| Command string `json:"Command,omitempty" yaml:"Command,omitempty"` |
| Created int64 `json:"Created,omitempty" yaml:"Created,omitempty"` |
| State string `json:"State,omitempty" yaml:"State,omitempty"` |
| Status string `json:"Status,omitempty" yaml:"Status,omitempty"` |
| Ports []APIPort `json:"Ports,omitempty" yaml:"Ports,omitempty"` |
| SizeRw int64 `json:"SizeRw,omitempty" yaml:"SizeRw,omitempty"` |
| SizeRootFs int64 `json:"SizeRootFs,omitempty" yaml:"SizeRootFs,omitempty"` |
| Names []string `json:"Names,omitempty" yaml:"Names,omitempty"` |
| Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty"` |
| Networks NetworkList `json:"NetworkSettings,omitempty" yaml:"NetworkSettings,omitempty"` |
| Mounts []APIMount `json:"Mounts,omitempty" yaml:"Mounts,omitempty"` |
| } |
| |
| // NetworkList encapsulates a map of networks, as returned by the Docker API in |
| // ListContainers. |
| type NetworkList struct { |
| Networks map[string]ContainerNetwork `json:"Networks" yaml:"Networks,omitempty"` |
| } |
| |
| // ListContainers returns a slice of containers matching the given criteria. |
| // |
| // See https://goo.gl/47a6tO for more details. |
| func (c *Client) ListContainers(opts ListContainersOptions) ([]APIContainers, error) { |
| path := "/containers/json?" + queryString(opts) |
| resp, err := c.do("GET", path, doOptions{}) |
| if err != nil { |
| return nil, err |
| } |
| defer resp.Body.Close() |
| var containers []APIContainers |
| if err := json.NewDecoder(resp.Body).Decode(&containers); err != nil { |
| return nil, err |
| } |
| return containers, nil |
| } |
| |
| // Port represents the port number and the protocol, in the form |
| // <number>/<protocol>. For example: 80/tcp. |
| type Port string |
| |
| // Port returns the number of the port. |
| func (p Port) Port() string { |
| return strings.Split(string(p), "/")[0] |
| } |
| |
| // Proto returns the name of the protocol. |
| func (p Port) Proto() string { |
| parts := strings.Split(string(p), "/") |
| if len(parts) == 1 { |
| return "tcp" |
| } |
| return parts[1] |
| } |
| |
| // State represents the state of a container. |
| type State struct { |
| Status string `json:"Status,omitempty" yaml:"Status,omitempty"` |
| Running bool `json:"Running,omitempty" yaml:"Running,omitempty"` |
| Paused bool `json:"Paused,omitempty" yaml:"Paused,omitempty"` |
| Restarting bool `json:"Restarting,omitempty" yaml:"Restarting,omitempty"` |
| OOMKilled bool `json:"OOMKilled,omitempty" yaml:"OOMKilled,omitempty"` |
| RemovalInProgress bool `json:"RemovalInProgress,omitempty" yaml:"RemovalInProgress,omitempty"` |
| Dead bool `json:"Dead,omitempty" yaml:"Dead,omitempty"` |
| Pid int `json:"Pid,omitempty" yaml:"Pid,omitempty"` |
| ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty"` |
| Error string `json:"Error,omitempty" yaml:"Error,omitempty"` |
| StartedAt time.Time `json:"StartedAt,omitempty" yaml:"StartedAt,omitempty"` |
| FinishedAt time.Time `json:"FinishedAt,omitempty" yaml:"FinishedAt,omitempty"` |
| } |
| |
| // String returns a human-readable description of the state |
| func (s *State) String() string { |
| if s.Running { |
| if s.Paused { |
| return fmt.Sprintf("Up %s (Paused)", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt))) |
| } |
| if s.Restarting { |
| return fmt.Sprintf("Restarting (%d) %s ago", s.ExitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt))) |
| } |
| |
| return fmt.Sprintf("Up %s", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt))) |
| } |
| |
| if s.RemovalInProgress { |
| return "Removal In Progress" |
| } |
| |
| if s.Dead { |
| return "Dead" |
| } |
| |
| if s.StartedAt.IsZero() { |
| return "Created" |
| } |
| |
| if s.FinishedAt.IsZero() { |
| return "" |
| } |
| |
| return fmt.Sprintf("Exited (%d) %s ago", s.ExitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt))) |
| } |
| |
| // StateString returns a single string to describe state |
| func (s *State) StateString() string { |
| if s.Running { |
| if s.Paused { |
| return "paused" |
| } |
| if s.Restarting { |
| return "restarting" |
| } |
| return "running" |
| } |
| |
| if s.Dead { |
| return "dead" |
| } |
| |
| if s.StartedAt.IsZero() { |
| return "created" |
| } |
| |
| return "exited" |
| } |
| |
| // PortBinding represents the host/container port mapping as returned in the |
| // `docker inspect` json |
| type PortBinding struct { |
| HostIP string `json:"HostIP,omitempty" yaml:"HostIP,omitempty"` |
| HostPort string `json:"HostPort,omitempty" yaml:"HostPort,omitempty"` |
| } |
| |
| // PortMapping represents a deprecated field in the `docker inspect` output, |
| // and its value as found in NetworkSettings should always be nil |
| type PortMapping map[string]string |
| |
| // ContainerNetwork represents the networking settings of a container per network. |
| type ContainerNetwork struct { |
| MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"` |
| GlobalIPv6PrefixLen int `json:"GlobalIPv6PrefixLen,omitempty" yaml:"GlobalIPv6PrefixLen,omitempty"` |
| GlobalIPv6Address string `json:"GlobalIPv6Address,omitempty" yaml:"GlobalIPv6Address,omitempty"` |
| IPv6Gateway string `json:"IPv6Gateway,omitempty" yaml:"IPv6Gateway,omitempty"` |
| IPPrefixLen int `json:"IPPrefixLen,omitempty" yaml:"IPPrefixLen,omitempty"` |
| IPAddress string `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty"` |
| Gateway string `json:"Gateway,omitempty" yaml:"Gateway,omitempty"` |
| EndpointID string `json:"EndpointID,omitempty" yaml:"EndpointID,omitempty"` |
| NetworkID string `json:"NetworkID,omitempty" yaml:"NetworkID,omitempty"` |
| } |
| |
| // NetworkSettings contains network-related information about a container |
| type NetworkSettings struct { |
| Networks map[string]ContainerNetwork `json:"Networks,omitempty" yaml:"Networks,omitempty"` |
| IPAddress string `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty"` |
| IPPrefixLen int `json:"IPPrefixLen,omitempty" yaml:"IPPrefixLen,omitempty"` |
| MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"` |
| Gateway string `json:"Gateway,omitempty" yaml:"Gateway,omitempty"` |
| Bridge string `json:"Bridge,omitempty" yaml:"Bridge,omitempty"` |
| PortMapping map[string]PortMapping `json:"PortMapping,omitempty" yaml:"PortMapping,omitempty"` |
| Ports map[Port][]PortBinding `json:"Ports,omitempty" yaml:"Ports,omitempty"` |
| NetworkID string `json:"NetworkID,omitempty" yaml:"NetworkID,omitempty"` |
| EndpointID string `json:"EndpointID,omitempty" yaml:"EndpointID,omitempty"` |
| SandboxKey string `json:"SandboxKey,omitempty" yaml:"SandboxKey,omitempty"` |
| GlobalIPv6Address string `json:"GlobalIPv6Address,omitempty" yaml:"GlobalIPv6Address,omitempty"` |
| GlobalIPv6PrefixLen int `json:"GlobalIPv6PrefixLen,omitempty" yaml:"GlobalIPv6PrefixLen,omitempty"` |
| IPv6Gateway string `json:"IPv6Gateway,omitempty" yaml:"IPv6Gateway,omitempty"` |
| LinkLocalIPv6Address string `json:"LinkLocalIPv6Address,omitempty" yaml:"LinkLocalIPv6Address,omitempty"` |
| LinkLocalIPv6PrefixLen int `json:"LinkLocalIPv6PrefixLen,omitempty" yaml:"LinkLocalIPv6PrefixLen,omitempty"` |
| SecondaryIPAddresses []string `json:"SecondaryIPAddresses,omitempty" yaml:"SecondaryIPAddresses,omitempty"` |
| SecondaryIPv6Addresses []string `json:"SecondaryIPv6Addresses,omitempty" yaml:"SecondaryIPv6Addresses,omitempty"` |
| } |
| |
| // PortMappingAPI translates the port mappings as contained in NetworkSettings |
| // into the format in which they would appear when returned by the API |
| func (settings *NetworkSettings) PortMappingAPI() []APIPort { |
| var mapping []APIPort |
| for port, bindings := range settings.Ports { |
| p, _ := parsePort(port.Port()) |
| if len(bindings) == 0 { |
| mapping = append(mapping, APIPort{ |
| PrivatePort: int64(p), |
| Type: port.Proto(), |
| }) |
| continue |
| } |
| for _, binding := range bindings { |
| p, _ := parsePort(port.Port()) |
| h, _ := parsePort(binding.HostPort) |
| mapping = append(mapping, APIPort{ |
| PrivatePort: int64(p), |
| PublicPort: int64(h), |
| Type: port.Proto(), |
| IP: binding.HostIP, |
| }) |
| } |
| } |
| return mapping |
| } |
| |
| func parsePort(rawPort string) (int, error) { |
| port, err := strconv.ParseUint(rawPort, 10, 16) |
| if err != nil { |
| return 0, err |
| } |
| return int(port), nil |
| } |
| |
| // Config is the list of configuration options used when creating a container. |
| // Config does not contain the options that are specific to starting a container on a |
| // given host. Those are contained in HostConfig |
| type Config struct { |
| Hostname string `json:"Hostname,omitempty" yaml:"Hostname,omitempty"` |
| Domainname string `json:"Domainname,omitempty" yaml:"Domainname,omitempty"` |
| User string `json:"User,omitempty" yaml:"User,omitempty"` |
| Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty"` |
| MemorySwap int64 `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty"` |
| MemoryReservation int64 `json:"MemoryReservation,omitempty" yaml:"MemoryReservation,omitempty"` |
| KernelMemory int64 `json:"KernelMemory,omitempty" yaml:"KernelMemory,omitempty"` |
| PidsLimit int64 `json:"PidsLimit,omitempty" yaml:"PidsLimit,omitempty"` |
| CPUShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty"` |
| CPUSet string `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty"` |
| AttachStdin bool `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty"` |
| AttachStdout bool `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty"` |
| AttachStderr bool `json:"AttachStderr,omitempty" yaml:"AttachStderr,omitempty"` |
| PortSpecs []string `json:"PortSpecs,omitempty" yaml:"PortSpecs,omitempty"` |
| ExposedPorts map[Port]struct{} `json:"ExposedPorts,omitempty" yaml:"ExposedPorts,omitempty"` |
| StopSignal string `json:"StopSignal,omitempty" yaml:"StopSignal,omitempty"` |
| Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty"` |
| OpenStdin bool `json:"OpenStdin,omitempty" yaml:"OpenStdin,omitempty"` |
| StdinOnce bool `json:"StdinOnce,omitempty" yaml:"StdinOnce,omitempty"` |
| Env []string `json:"Env,omitempty" yaml:"Env,omitempty"` |
| Cmd []string `json:"Cmd" yaml:"Cmd"` |
| DNS []string `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.9 and below only |
| Image string `json:"Image,omitempty" yaml:"Image,omitempty"` |
| Volumes map[string]struct{} `json:"Volumes,omitempty" yaml:"Volumes,omitempty"` |
| VolumeDriver string `json:"VolumeDriver,omitempty" yaml:"VolumeDriver,omitempty"` |
| VolumesFrom string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"` |
| WorkingDir string `json:"WorkingDir,omitempty" yaml:"WorkingDir,omitempty"` |
| MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"` |
| Entrypoint []string `json:"Entrypoint" yaml:"Entrypoint"` |
| NetworkDisabled bool `json:"NetworkDisabled,omitempty" yaml:"NetworkDisabled,omitempty"` |
| SecurityOpts []string `json:"SecurityOpts,omitempty" yaml:"SecurityOpts,omitempty"` |
| OnBuild []string `json:"OnBuild,omitempty" yaml:"OnBuild,omitempty"` |
| Mounts []Mount `json:"Mounts,omitempty" yaml:"Mounts,omitempty"` |
| Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty"` |
| } |
| |
| // Mount represents a mount point in the container. |
| // |
| // It has been added in the version 1.20 of the Docker API, available since |
| // Docker 1.8. |
| type Mount struct { |
| Name string |
| Source string |
| Destination string |
| Driver string |
| Mode string |
| RW bool |
| } |
| |
| // LogConfig defines the log driver type and the configuration for it. |
| type LogConfig struct { |
| Type string `json:"Type,omitempty" yaml:"Type,omitempty"` |
| Config map[string]string `json:"Config,omitempty" yaml:"Config,omitempty"` |
| } |
| |
| // ULimit defines system-wide resource limitations |
| // 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. |
| type ULimit struct { |
| Name string `json:"Name,omitempty" yaml:"Name,omitempty"` |
| Soft int64 `json:"Soft,omitempty" yaml:"Soft,omitempty"` |
| Hard int64 `json:"Hard,omitempty" yaml:"Hard,omitempty"` |
| } |
| |
| // SwarmNode containers information about which Swarm node the container is on |
| type SwarmNode struct { |
| ID string `json:"ID,omitempty" yaml:"ID,omitempty"` |
| IP string `json:"IP,omitempty" yaml:"IP,omitempty"` |
| Addr string `json:"Addr,omitempty" yaml:"Addr,omitempty"` |
| Name string `json:"Name,omitempty" yaml:"Name,omitempty"` |
| CPUs int64 `json:"CPUs,omitempty" yaml:"CPUs,omitempty"` |
| Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty"` |
| Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty"` |
| } |
| |
| // GraphDriver contains information about the GraphDriver used by the container |
| type GraphDriver struct { |
| Name string `json:"Name,omitempty" yaml:"Name,omitempty"` |
| Data map[string]string `json:"Data,omitempty" yaml:"Data,omitempty"` |
| } |
| |
| // Container is the type encompasing everything about a container - its config, |
| // hostconfig, etc. |
| type Container struct { |
| ID string `json:"Id" yaml:"Id"` |
| |
| Created time.Time `json:"Created,omitempty" yaml:"Created,omitempty"` |
| |
| Path string `json:"Path,omitempty" yaml:"Path,omitempty"` |
| Args []string `json:"Args,omitempty" yaml:"Args,omitempty"` |
| |
| Config *Config `json:"Config,omitempty" yaml:"Config,omitempty"` |
| State State `json:"State,omitempty" yaml:"State,omitempty"` |
| Image string `json:"Image,omitempty" yaml:"Image,omitempty"` |
| |
| Node *SwarmNode `json:"Node,omitempty" yaml:"Node,omitempty"` |
| |
| NetworkSettings *NetworkSettings `json:"NetworkSettings,omitempty" yaml:"NetworkSettings,omitempty"` |
| |
| SysInitPath string `json:"SysInitPath,omitempty" yaml:"SysInitPath,omitempty"` |
| ResolvConfPath string `json:"ResolvConfPath,omitempty" yaml:"ResolvConfPath,omitempty"` |
| HostnamePath string `json:"HostnamePath,omitempty" yaml:"HostnamePath,omitempty"` |
| HostsPath string `json:"HostsPath,omitempty" yaml:"HostsPath,omitempty"` |
| LogPath string `json:"LogPath,omitempty" yaml:"LogPath,omitempty"` |
| Name string `json:"Name,omitempty" yaml:"Name,omitempty"` |
| Driver string `json:"Driver,omitempty" yaml:"Driver,omitempty"` |
| Mounts []Mount `json:"Mounts,omitempty" yaml:"Mounts,omitempty"` |
| |
| Volumes map[string]string `json:"Volumes,omitempty" yaml:"Volumes,omitempty"` |
| VolumesRW map[string]bool `json:"VolumesRW,omitempty" yaml:"VolumesRW,omitempty"` |
| HostConfig *HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty"` |
| ExecIDs []string `json:"ExecIDs,omitempty" yaml:"ExecIDs,omitempty"` |
| GraphDriver *GraphDriver `json:"GraphDriver,omitempty" yaml:"GraphDriver,omitempty"` |
| |
| RestartCount int `json:"RestartCount,omitempty" yaml:"RestartCount,omitempty"` |
| |
| AppArmorProfile string `json:"AppArmorProfile,omitempty" yaml:"AppArmorProfile,omitempty"` |
| } |
| |
| // UpdateContainerOptions specify parameters to the UpdateContainer function. |
| // |
| // See https://goo.gl/Y6fXUy for more details. |
| type UpdateContainerOptions struct { |
| BlkioWeight int `json:"BlkioWeight"` |
| CPUShares int `json:"CpuShares"` |
| CPUPeriod int `json:"CpuPeriod"` |
| CPUQuota int `json:"CpuQuota"` |
| CpusetCpus string `json:"CpusetCpus"` |
| CpusetMems string `json:"CpusetMems"` |
| Memory int `json:"Memory"` |
| MemorySwap int `json:"MemorySwap"` |
| MemoryReservation int `json:"MemoryReservation"` |
| KernelMemory int `json:"KernelMemory"` |
| RestartPolicy RestartPolicy `json:"RestartPolicy,omitempty"` |
| } |
| |
| // UpdateContainer updates the container at ID with the options |
| // |
| // See https://goo.gl/Y6fXUy for more details. |
| func (c *Client) UpdateContainer(id string, opts UpdateContainerOptions) error { |
| resp, err := c.do("POST", fmt.Sprintf("/containers/"+id+"/update"), doOptions{data: opts, forceJSON: true}) |
| if err != nil { |
| return err |
| } |
| defer resp.Body.Close() |
| return nil |
| } |
| |
| // RenameContainerOptions specify parameters to the RenameContainer function. |
| // |
| // See https://goo.gl/laSOIy for more details. |
| type RenameContainerOptions struct { |
| // ID of container to rename |
| ID string `qs:"-"` |
| |
| // New name |
| Name string `json:"name,omitempty" yaml:"name,omitempty"` |
| } |
| |
| // RenameContainer updates and existing containers name |
| // |
| // See https://goo.gl/laSOIy for more details. |
| func (c *Client) RenameContainer(opts RenameContainerOptions) error { |
| resp, err := c.do("POST", fmt.Sprintf("/containers/"+opts.ID+"/rename?%s", queryString(opts)), doOptions{}) |
| if err != nil { |
| return err |
| } |
| resp.Body.Close() |
| return nil |
| } |
| |
| // InspectContainer returns information about a container by its ID. |
| // |
| // See https://goo.gl/RdIq0b for more details. |
| func (c *Client) InspectContainer(id string) (*Container, error) { |
| path := "/containers/" + id + "/json" |
| resp, err := c.do("GET", path, doOptions{}) |
| if err != nil { |
| if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { |
| return nil, &NoSuchContainer{ID: id} |
| } |
| return nil, err |
| } |
| defer resp.Body.Close() |
| var container Container |
| if err := json.NewDecoder(resp.Body).Decode(&container); err != nil { |
| return nil, err |
| } |
| return &container, nil |
| } |
| |
| // ContainerChanges returns changes in the filesystem of the given container. |
| // |
| // See https://goo.gl/9GsTIF for more details. |
| func (c *Client) ContainerChanges(id string) ([]Change, error) { |
| path := "/containers/" + id + "/changes" |
| resp, err := c.do("GET", path, doOptions{}) |
| if err != nil { |
| if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { |
| return nil, &NoSuchContainer{ID: id} |
| } |
| return nil, err |
| } |
| defer resp.Body.Close() |
| var changes []Change |
| if err := json.NewDecoder(resp.Body).Decode(&changes); err != nil { |
| return nil, err |
| } |
| return changes, nil |
| } |
| |
| // CreateContainerOptions specify parameters to the CreateContainer function. |
| // |
| // See https://goo.gl/WxQzrr for more details. |
| type CreateContainerOptions struct { |
| Name string |
| Config *Config `qs:"-"` |
| HostConfig *HostConfig `qs:"-"` |
| } |
| |
| // CreateContainer creates a new container, returning the container instance, |
| // or an error in case of failure. |
| // |
| // See https://goo.gl/WxQzrr for more details. |
| func (c *Client) CreateContainer(opts CreateContainerOptions) (*Container, error) { |
| path := "/containers/create?" + queryString(opts) |
| resp, err := c.do( |
| "POST", |
| path, |
| doOptions{ |
| data: struct { |
| *Config |
| HostConfig *HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty"` |
| }{ |
| opts.Config, |
| opts.HostConfig, |
| }, |
| }, |
| ) |
| |
| if e, ok := err.(*Error); ok { |
| if e.Status == http.StatusNotFound { |
| return nil, ErrNoSuchImage |
| } |
| if e.Status == http.StatusConflict { |
| return nil, ErrContainerAlreadyExists |
| } |
| } |
| |
| if err != nil { |
| return nil, err |
| } |
| defer resp.Body.Close() |
| var container Container |
| if err := json.NewDecoder(resp.Body).Decode(&container); err != nil { |
| return nil, err |
| } |
| |
| container.Name = opts.Name |
| |
| return &container, nil |
| } |
| |
| // KeyValuePair is a type for generic key/value pairs as used in the Lxc |
| // configuration |
| type KeyValuePair struct { |
| Key string `json:"Key,omitempty" yaml:"Key,omitempty"` |
| Value string `json:"Value,omitempty" yaml:"Value,omitempty"` |
| } |
| |
| // RestartPolicy represents the policy for automatically restarting a container. |
| // |
| // Possible values are: |
| // |
| // - always: the docker daemon will always restart the container |
| // - on-failure: the docker daemon will restart the container on failures, at |
| // most MaximumRetryCount times |
| // - no: the docker daemon will not restart the container automatically |
| type RestartPolicy struct { |
| Name string `json:"Name,omitempty" yaml:"Name,omitempty"` |
| MaximumRetryCount int `json:"MaximumRetryCount,omitempty" yaml:"MaximumRetryCount,omitempty"` |
| } |
| |
| // AlwaysRestart returns a restart policy that tells the Docker daemon to |
| // always restart the container. |
| func AlwaysRestart() RestartPolicy { |
| return RestartPolicy{Name: "always"} |
| } |
| |
| // RestartOnFailure returns a restart policy that tells the Docker daemon to |
| // restart the container on failures, trying at most maxRetry times. |
| func RestartOnFailure(maxRetry int) RestartPolicy { |
| return RestartPolicy{Name: "on-failure", MaximumRetryCount: maxRetry} |
| } |
| |
| // NeverRestart returns a restart policy that tells the Docker daemon to never |
| // restart the container on failures. |
| func NeverRestart() RestartPolicy { |
| return RestartPolicy{Name: "no"} |
| } |
| |
| // Device represents a device mapping between the Docker host and the |
| // container. |
| type Device struct { |
| PathOnHost string `json:"PathOnHost,omitempty" yaml:"PathOnHost,omitempty"` |
| PathInContainer string `json:"PathInContainer,omitempty" yaml:"PathInContainer,omitempty"` |
| CgroupPermissions string `json:"CgroupPermissions,omitempty" yaml:"CgroupPermissions,omitempty"` |
| } |
| |
| // BlockWeight represents a relative device weight for an individual device inside |
| // of a container |
| // |
| // See https://goo.gl/FSdP0H for more details. |
| type BlockWeight struct { |
| Path string `json:"Path,omitempty"` |
| Weight string `json:"Weight,omitempty"` |
| } |
| |
| // BlockLimit represents a read/write limit in IOPS or Bandwidth for a device |
| // inside of a container |
| // |
| // See https://goo.gl/FSdP0H for more details. |
| type BlockLimit struct { |
| Path string `json:"Path,omitempty"` |
| Rate string `json:"Rate,omitempty"` |
| } |
| |
| // HostConfig contains the container options related to starting a container on |
| // a given host |
| type HostConfig struct { |
| Binds []string `json:"Binds,omitempty" yaml:"Binds,omitempty"` |
| CapAdd []string `json:"CapAdd,omitempty" yaml:"CapAdd,omitempty"` |
| CapDrop []string `json:"CapDrop,omitempty" yaml:"CapDrop,omitempty"` |
| GroupAdd []string `json:"GroupAdd,omitempty" yaml:"GroupAdd,omitempty"` |
| ContainerIDFile string `json:"ContainerIDFile,omitempty" yaml:"ContainerIDFile,omitempty"` |
| LxcConf []KeyValuePair `json:"LxcConf,omitempty" yaml:"LxcConf,omitempty"` |
| Privileged bool `json:"Privileged,omitempty" yaml:"Privileged,omitempty"` |
| PortBindings map[Port][]PortBinding `json:"PortBindings,omitempty" yaml:"PortBindings,omitempty"` |
| Links []string `json:"Links,omitempty" yaml:"Links,omitempty"` |
| PublishAllPorts bool `json:"PublishAllPorts,omitempty" yaml:"PublishAllPorts,omitempty"` |
| DNS []string `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.10 and above only |
| DNSOptions []string `json:"DnsOptions,omitempty" yaml:"DnsOptions,omitempty"` |
| DNSSearch []string `json:"DnsSearch,omitempty" yaml:"DnsSearch,omitempty"` |
| ExtraHosts []string `json:"ExtraHosts,omitempty" yaml:"ExtraHosts,omitempty"` |
| VolumesFrom []string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"` |
| UsernsMode string `json:"UsernsMode,omitempty" yaml:"UsernsMode,omitempty"` |
| NetworkMode string `json:"NetworkMode,omitempty" yaml:"NetworkMode,omitempty"` |
| IpcMode string `json:"IpcMode,omitempty" yaml:"IpcMode,omitempty"` |
| PidMode string `json:"PidMode,omitempty" yaml:"PidMode,omitempty"` |
| UTSMode string `json:"UTSMode,omitempty" yaml:"UTSMode,omitempty"` |
| RestartPolicy RestartPolicy `json:"RestartPolicy,omitempty" yaml:"RestartPolicy,omitempty"` |
| Devices []Device `json:"Devices,omitempty" yaml:"Devices,omitempty"` |
| LogConfig LogConfig `json:"LogConfig,omitempty" yaml:"LogConfig,omitempty"` |
| ReadonlyRootfs bool `json:"ReadonlyRootfs,omitempty" yaml:"ReadonlyRootfs,omitempty"` |
| SecurityOpt []string `json:"SecurityOpt,omitempty" yaml:"SecurityOpt,omitempty"` |
| CgroupParent string `json:"CgroupParent,omitempty" yaml:"CgroupParent,omitempty"` |
| Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty"` |
| MemorySwap int64 `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty"` |
| MemorySwappiness int64 `json:"MemorySwappiness,omitempty" yaml:"MemorySwappiness,omitempty"` |
| OOMKillDisable bool `json:"OomKillDisable,omitempty" yaml:"OomKillDisable"` |
| CPUShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty"` |
| CPUSet string `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty"` |
| CPUSetCPUs string `json:"CpusetCpus,omitempty" yaml:"CpusetCpus,omitempty"` |
| CPUSetMEMs string `json:"CpusetMems,omitempty" yaml:"CpusetMems,omitempty"` |
| CPUQuota int64 `json:"CpuQuota,omitempty" yaml:"CpuQuota,omitempty"` |
| CPUPeriod int64 `json:"CpuPeriod,omitempty" yaml:"CpuPeriod,omitempty"` |
| BlkioWeight int64 `json:"BlkioWeight,omitempty" yaml:"BlkioWeight"` |
| BlkioWeightDevice []BlockWeight `json:"BlkioWeightDevice,omitempty" yaml:"BlkioWeightDevice"` |
| BlkioDeviceReadBps []BlockLimit `json:"BlkioDeviceReadBps,omitempty" yaml:"BlkioDeviceReadBps"` |
| BlkioDeviceReadIOps []BlockLimit `json:"BlkioDeviceReadIOps,omitempty" yaml:"BlkioDeviceReadIOps"` |
| BlkioDeviceWriteBps []BlockLimit `json:"BlkioDeviceWriteBps,omitempty" yaml:"BlkioDeviceWriteBps"` |
| BlkioDeviceWriteIOps []BlockLimit `json:"BlkioDeviceWriteIOps,omitempty" yaml:"BlkioDeviceWriteIOps"` |
| Ulimits []ULimit `json:"Ulimits,omitempty" yaml:"Ulimits,omitempty"` |
| VolumeDriver string `json:"VolumeDriver,omitempty" yaml:"VolumeDriver,omitempty"` |
| OomScoreAdj int `json:"OomScoreAdj,omitempty" yaml:"OomScoreAdj,omitempty"` |
| ShmSize int64 `json:"ShmSize,omitempty" yaml:"ShmSize,omitempty"` |
| } |
| |
| // StartContainer starts a container, returning an error in case of failure. |
| // |
| // See https://goo.gl/MrBAJv for more details. |
| func (c *Client) StartContainer(id string, hostConfig *HostConfig) error { |
| path := "/containers/" + id + "/start" |
| resp, err := c.do("POST", path, doOptions{data: hostConfig, forceJSON: true}) |
| if err != nil { |
| if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { |
| return &NoSuchContainer{ID: id, Err: err} |
| } |
| return err |
| } |
| if resp.StatusCode == http.StatusNotModified { |
| return &ContainerAlreadyRunning{ID: id} |
| } |
| resp.Body.Close() |
| return nil |
| } |
| |
| // StopContainer stops a container, killing it after the given timeout (in |
| // seconds). |
| // |
| // See https://goo.gl/USqsFt for more details. |
| func (c *Client) StopContainer(id string, timeout uint) error { |
| path := fmt.Sprintf("/containers/%s/stop?t=%d", id, timeout) |
| resp, err := c.do("POST", path, doOptions{}) |
| if err != nil { |
| if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { |
| return &NoSuchContainer{ID: id} |
| } |
| return err |
| } |
| if resp.StatusCode == http.StatusNotModified { |
| return &ContainerNotRunning{ID: id} |
| } |
| resp.Body.Close() |
| return nil |
| } |
| |
| // RestartContainer stops a container, killing it after the given timeout (in |
| // seconds), during the stop process. |
| // |
| // See https://goo.gl/QzsDnz for more details. |
| func (c *Client) RestartContainer(id string, timeout uint) error { |
| path := fmt.Sprintf("/containers/%s/restart?t=%d", id, timeout) |
| resp, err := c.do("POST", path, doOptions{}) |
| if err != nil { |
| if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { |
| return &NoSuchContainer{ID: id} |
| } |
| return err |
| } |
| resp.Body.Close() |
| return nil |
| } |
| |
| // PauseContainer pauses the given container. |
| // |
| // See https://goo.gl/OF7W9X for more details. |
| func (c *Client) PauseContainer(id string) error { |
| path := fmt.Sprintf("/containers/%s/pause", id) |
| resp, err := c.do("POST", path, doOptions{}) |
| if err != nil { |
| if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { |
| return &NoSuchContainer{ID: id} |
| } |
| return err |
| } |
| resp.Body.Close() |
| return nil |
| } |
| |
| // UnpauseContainer unpauses the given container. |
| // |
| // See https://goo.gl/7dwyPA for more details. |
| func (c *Client) UnpauseContainer(id string) error { |
| path := fmt.Sprintf("/containers/%s/unpause", id) |
| resp, err := c.do("POST", path, doOptions{}) |
| if err != nil { |
| if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { |
| return &NoSuchContainer{ID: id} |
| } |
| return err |
| } |
| resp.Body.Close() |
| return nil |
| } |
| |
| // TopResult represents the list of processes running in a container, as |
| // returned by /containers/<id>/top. |
| // |
| // See https://goo.gl/Rb46aY for more details. |
| type TopResult struct { |
| Titles []string |
| Processes [][]string |
| } |
| |
| // TopContainer returns processes running inside a container |
| // |
| // See https://goo.gl/Rb46aY for more details. |
| func (c *Client) TopContainer(id string, psArgs string) (TopResult, error) { |
| var args string |
| var result TopResult |
| if psArgs != "" { |
| args = fmt.Sprintf("?ps_args=%s", psArgs) |
| } |
| path := fmt.Sprintf("/containers/%s/top%s", id, args) |
| resp, err := c.do("GET", path, doOptions{}) |
| if err != nil { |
| if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { |
| return result, &NoSuchContainer{ID: id} |
| } |
| return result, err |
| } |
| defer resp.Body.Close() |
| if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { |
| return result, err |
| } |
| return result, nil |
| } |
| |
| // Stats represents container statistics, returned by /containers/<id>/stats. |
| // |
| // See https://goo.gl/GNmLHb for more details. |
| type Stats struct { |
| Read time.Time `json:"read,omitempty" yaml:"read,omitempty"` |
| PidsStats struct { |
| Current uint64 `json:"current,omitempty" yaml:"current,omitempty"` |
| } `json:"pids_stats,omitempty" yaml:"pids_stats,omitempty"` |
| Network NetworkStats `json:"network,omitempty" yaml:"network,omitempty"` |
| Networks map[string]NetworkStats `json:"networks,omitempty" yaml:"networks,omitempty"` |
| MemoryStats struct { |
| Stats struct { |
| TotalPgmafault uint64 `json:"total_pgmafault,omitempty" yaml:"total_pgmafault,omitempty"` |
| Cache uint64 `json:"cache,omitempty" yaml:"cache,omitempty"` |
| MappedFile uint64 `json:"mapped_file,omitempty" yaml:"mapped_file,omitempty"` |
| TotalInactiveFile uint64 `json:"total_inactive_file,omitempty" yaml:"total_inactive_file,omitempty"` |
| Pgpgout uint64 `json:"pgpgout,omitempty" yaml:"pgpgout,omitempty"` |
| Rss uint64 `json:"rss,omitempty" yaml:"rss,omitempty"` |
| TotalMappedFile uint64 `json:"total_mapped_file,omitempty" yaml:"total_mapped_file,omitempty"` |
| Writeback uint64 `json:"writeback,omitempty" yaml:"writeback,omitempty"` |
| Unevictable uint64 `json:"unevictable,omitempty" yaml:"unevictable,omitempty"` |
| Pgpgin uint64 `json:"pgpgin,omitempty" yaml:"pgpgin,omitempty"` |
| TotalUnevictable uint64 `json:"total_unevictable,omitempty" yaml:"total_unevictable,omitempty"` |
| Pgmajfault uint64 `json:"pgmajfault,omitempty" yaml:"pgmajfault,omitempty"` |
| TotalRss uint64 `json:"total_rss,omitempty" yaml:"total_rss,omitempty"` |
| TotalRssHuge uint64 `json:"total_rss_huge,omitempty" yaml:"total_rss_huge,omitempty"` |
| TotalWriteback uint64 `json:"total_writeback,omitempty" yaml:"total_writeback,omitempty"` |
| TotalInactiveAnon uint64 `json:"total_inactive_anon,omitempty" yaml:"total_inactive_anon,omitempty"` |
| RssHuge uint64 `json:"rss_huge,omitempty" yaml:"rss_huge,omitempty"` |
| HierarchicalMemoryLimit uint64 `json:"hierarchical_memory_limit,omitempty" yaml:"hierarchical_memory_limit,omitempty"` |
| TotalPgfault uint64 `json:"total_pgfault,omitempty" yaml:"total_pgfault,omitempty"` |
| TotalActiveFile uint64 `json:"total_active_file,omitempty" yaml:"total_active_file,omitempty"` |
| ActiveAnon uint64 `json:"active_anon,omitempty" yaml:"active_anon,omitempty"` |
| TotalActiveAnon uint64 `json:"total_active_anon,omitempty" yaml:"total_active_anon,omitempty"` |
| TotalPgpgout uint64 `json:"total_pgpgout,omitempty" yaml:"total_pgpgout,omitempty"` |
| TotalCache uint64 `json:"total_cache,omitempty" yaml:"total_cache,omitempty"` |
| InactiveAnon uint64 `json:"inactive_anon,omitempty" yaml:"inactive_anon,omitempty"` |
| ActiveFile uint64 `json:"active_file,omitempty" yaml:"active_file,omitempty"` |
| Pgfault uint64 `json:"pgfault,omitempty" yaml:"pgfault,omitempty"` |
| InactiveFile uint64 `json:"inactive_file,omitempty" yaml:"inactive_file,omitempty"` |
| TotalPgpgin uint64 `json:"total_pgpgin,omitempty" yaml:"total_pgpgin,omitempty"` |
| HierarchicalMemswLimit uint64 `json:"hierarchical_memsw_limit,omitempty" yaml:"hierarchical_memsw_limit,omitempty"` |
| Swap uint64 `json:"swap,omitempty" yaml:"swap,omitempty"` |
| } `json:"stats,omitempty" yaml:"stats,omitempty"` |
| MaxUsage uint64 `json:"max_usage,omitempty" yaml:"max_usage,omitempty"` |
| Usage uint64 `json:"usage,omitempty" yaml:"usage,omitempty"` |
| Failcnt uint64 `json:"failcnt,omitempty" yaml:"failcnt,omitempty"` |
| Limit uint64 `json:"limit,omitempty" yaml:"limit,omitempty"` |
| } `json:"memory_stats,omitempty" yaml:"memory_stats,omitempty"` |
| BlkioStats struct { |
| IOServiceBytesRecursive []BlkioStatsEntry `json:"io_service_bytes_recursive,omitempty" yaml:"io_service_bytes_recursive,omitempty"` |
| IOServicedRecursive []BlkioStatsEntry `json:"io_serviced_recursive,omitempty" yaml:"io_serviced_recursive,omitempty"` |
| IOQueueRecursive []BlkioStatsEntry `json:"io_queue_recursive,omitempty" yaml:"io_queue_recursive,omitempty"` |
| IOServiceTimeRecursive []BlkioStatsEntry `json:"io_service_time_recursive,omitempty" yaml:"io_service_time_recursive,omitempty"` |
| IOWaitTimeRecursive []BlkioStatsEntry `json:"io_wait_time_recursive,omitempty" yaml:"io_wait_time_recursive,omitempty"` |
| IOMergedRecursive []BlkioStatsEntry `json:"io_merged_recursive,omitempty" yaml:"io_merged_recursive,omitempty"` |
| IOTimeRecursive []BlkioStatsEntry `json:"io_time_recursive,omitempty" yaml:"io_time_recursive,omitempty"` |
| SectorsRecursive []BlkioStatsEntry `json:"sectors_recursive,omitempty" yaml:"sectors_recursive,omitempty"` |
| } `json:"blkio_stats,omitempty" yaml:"blkio_stats,omitempty"` |
| CPUStats CPUStats `json:"cpu_stats,omitempty" yaml:"cpu_stats,omitempty"` |
| PreCPUStats CPUStats `json:"precpu_stats,omitempty"` |
| } |
| |
| // NetworkStats is a stats entry for network stats |
| type NetworkStats struct { |
| RxDropped uint64 `json:"rx_dropped,omitempty" yaml:"rx_dropped,omitempty"` |
| RxBytes uint64 `json:"rx_bytes,omitempty" yaml:"rx_bytes,omitempty"` |
| RxErrors uint64 `json:"rx_errors,omitempty" yaml:"rx_errors,omitempty"` |
| TxPackets uint64 `json:"tx_packets,omitempty" yaml:"tx_packets,omitempty"` |
| TxDropped uint64 `json:"tx_dropped,omitempty" yaml:"tx_dropped,omitempty"` |
| RxPackets uint64 `json:"rx_packets,omitempty" yaml:"rx_packets,omitempty"` |
| TxErrors uint64 `json:"tx_errors,omitempty" yaml:"tx_errors,omitempty"` |
| TxBytes uint64 `json:"tx_bytes,omitempty" yaml:"tx_bytes,omitempty"` |
| } |
| |
| // CPUStats is a stats entry for cpu stats |
| type CPUStats struct { |
| CPUUsage struct { |
| PercpuUsage []uint64 `json:"percpu_usage,omitempty" yaml:"percpu_usage,omitempty"` |
| UsageInUsermode uint64 `json:"usage_in_usermode,omitempty" yaml:"usage_in_usermode,omitempty"` |
| TotalUsage uint64 `json:"total_usage,omitempty" yaml:"total_usage,omitempty"` |
| UsageInKernelmode uint64 `json:"usage_in_kernelmode,omitempty" yaml:"usage_in_kernelmode,omitempty"` |
| } `json:"cpu_usage,omitempty" yaml:"cpu_usage,omitempty"` |
| SystemCPUUsage uint64 `json:"system_cpu_usage,omitempty" yaml:"system_cpu_usage,omitempty"` |
| ThrottlingData struct { |
| Periods uint64 `json:"periods,omitempty"` |
| ThrottledPeriods uint64 `json:"throttled_periods,omitempty"` |
| ThrottledTime uint64 `json:"throttled_time,omitempty"` |
| } `json:"throttling_data,omitempty" yaml:"throttling_data,omitempty"` |
| } |
| |
| // BlkioStatsEntry is a stats entry for blkio_stats |
| type BlkioStatsEntry struct { |
| Major uint64 `json:"major,omitempty" yaml:"major,omitempty"` |
| Minor uint64 `json:"minor,omitempty" yaml:"minor,omitempty"` |
| Op string `json:"op,omitempty" yaml:"op,omitempty"` |
| Value uint64 `json:"value,omitempty" yaml:"value,omitempty"` |
| } |
| |
| // StatsOptions specify parameters to the Stats function. |
| // |
| // See https://goo.gl/GNmLHb for more details. |
| type StatsOptions struct { |
| ID string |
| Stats chan<- *Stats |
| Stream bool |
| // A flag that enables stopping the stats operation |
| Done <-chan bool |
| // Initial connection timeout |
| Timeout time.Duration |
| // Timeout with no data is received, it's reset every time new data |
| // arrives |
| InactivityTimeout time.Duration `qs:"-"` |
| } |
| |
| // Stats sends container statistics for the given container to the given channel. |
| // |
| // This function is blocking, similar to a streaming call for logs, and should be run |
| // on a separate goroutine from the caller. Note that this function will block until |
| // the given container is removed, not just exited. When finished, this function |
| // will close the given channel. Alternatively, function can be stopped by |
| // signaling on the Done channel. |
| // |
| // See https://goo.gl/GNmLHb for more details. |
| func (c *Client) Stats(opts StatsOptions) (retErr error) { |
| errC := make(chan error, 1) |
| readCloser, writeCloser := io.Pipe() |
| |
| defer func() { |
| close(opts.Stats) |
| |
| select { |
| case err := <-errC: |
| if err != nil && retErr == nil { |
| retErr = err |
| } |
| default: |
| // No errors |
| } |
| |
| if err := readCloser.Close(); err != nil && retErr == nil { |
| retErr = err |
| } |
| }() |
| |
| go func() { |
| err := c.stream("GET", fmt.Sprintf("/containers/%s/stats?stream=%v", opts.ID, opts.Stream), streamOptions{ |
| rawJSONStream: true, |
| useJSONDecoder: true, |
| stdout: writeCloser, |
| timeout: opts.Timeout, |
| inactivityTimeout: opts.InactivityTimeout, |
| }) |
| if err != nil { |
| dockerError, ok := err.(*Error) |
| if ok { |
| if dockerError.Status == http.StatusNotFound { |
| err = &NoSuchContainer{ID: opts.ID} |
| } |
| } |
| } |
| if closeErr := writeCloser.Close(); closeErr != nil && err == nil { |
| err = closeErr |
| } |
| errC <- err |
| close(errC) |
| }() |
| |
| quit := make(chan struct{}) |
| defer close(quit) |
| go func() { |
| // block here waiting for the signal to stop function |
| select { |
| case <-opts.Done: |
| readCloser.Close() |
| case <-quit: |
| return |
| } |
| }() |
| |
| decoder := json.NewDecoder(readCloser) |
| stats := new(Stats) |
| for err := decoder.Decode(stats); err != io.EOF; err = decoder.Decode(stats) { |
| if err != nil { |
| return err |
| } |
| opts.Stats <- stats |
| stats = new(Stats) |
| } |
| return nil |
| } |
| |
| // KillContainerOptions represents the set of options that can be used in a |
| // call to KillContainer. |
| // |
| // See https://goo.gl/hkS9i8 for more details. |
| type KillContainerOptions struct { |
| // The ID of the container. |
| ID string `qs:"-"` |
| |
| // The signal to send to the container. When omitted, Docker server |
| // will assume SIGKILL. |
| Signal Signal |
| } |
| |
| // KillContainer sends a signal to a container, returning an error in case of |
| // failure. |
| // |
| // See https://goo.gl/hkS9i8 for more details. |
| func (c *Client) KillContainer(opts KillContainerOptions) error { |
| path := "/containers/" + opts.ID + "/kill" + "?" + queryString(opts) |
| resp, err := c.do("POST", path, doOptions{}) |
| if err != nil { |
| if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { |
| return &NoSuchContainer{ID: opts.ID} |
| } |
| return err |
| } |
| resp.Body.Close() |
| return nil |
| } |
| |
| // RemoveContainerOptions encapsulates options to remove a container. |
| // |
| // See https://goo.gl/RQyX62 for more details. |
| type RemoveContainerOptions struct { |
| // The ID of the container. |
| ID string `qs:"-"` |
| |
| // A flag that indicates whether Docker should remove the volumes |
| // associated to the container. |
| RemoveVolumes bool `qs:"v"` |
| |
| // A flag that indicates whether Docker should remove the container |
| // even if it is currently running. |
| Force bool |
| } |
| |
| // RemoveContainer removes a container, returning an error in case of failure. |
| // |
| // See https://goo.gl/RQyX62 for more details. |
| func (c *Client) RemoveContainer(opts RemoveContainerOptions) error { |
| path := "/containers/" + opts.ID + "?" + queryString(opts) |
| resp, err := c.do("DELETE", path, doOptions{}) |
| if err != nil { |
| if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { |
| return &NoSuchContainer{ID: opts.ID} |
| } |
| return err |
| } |
| resp.Body.Close() |
| return nil |
| } |
| |
| // UploadToContainerOptions is the set of options that can be used when |
| // uploading an archive into a container. |
| // |
| // See https://goo.gl/Ss97HW for more details. |
| type UploadToContainerOptions struct { |
| InputStream io.Reader `json:"-" qs:"-"` |
| Path string `qs:"path"` |
| NoOverwriteDirNonDir bool `qs:"noOverwriteDirNonDir"` |
| } |
| |
| // UploadToContainer uploads a tar archive to be extracted to a path in the |
| // filesystem of the container. |
| // |
| // See https://goo.gl/Ss97HW for more details. |
| func (c *Client) UploadToContainer(id string, opts UploadToContainerOptions) error { |
| url := fmt.Sprintf("/containers/%s/archive?", id) + queryString(opts) |
| |
| return c.stream("PUT", url, streamOptions{ |
| in: opts.InputStream, |
| }) |
| } |
| |
| // DownloadFromContainerOptions is the set of options that can be used when |
| // downloading resources from a container. |
| // |
| // See https://goo.gl/KnZJDX for more details. |
| type DownloadFromContainerOptions struct { |
| OutputStream io.Writer `json:"-" qs:"-"` |
| Path string `qs:"path"` |
| InactivityTimeout time.Duration `qs:"-"` |
| } |
| |
| // DownloadFromContainer downloads a tar archive of files or folders in a container. |
| // |
| // See https://goo.gl/KnZJDX for more details. |
| func (c *Client) DownloadFromContainer(id string, opts DownloadFromContainerOptions) error { |
| url := fmt.Sprintf("/containers/%s/archive?", id) + queryString(opts) |
| |
| return c.stream("GET", url, streamOptions{ |
| setRawTerminal: true, |
| stdout: opts.OutputStream, |
| inactivityTimeout: opts.InactivityTimeout, |
| }) |
| } |
| |
| // CopyFromContainerOptions has been DEPRECATED, please use DownloadFromContainerOptions along with DownloadFromContainer. |
| // |
| // See https://goo.gl/R2jevW for more details. |
| type CopyFromContainerOptions struct { |
| OutputStream io.Writer `json:"-"` |
| Container string `json:"-"` |
| Resource string |
| } |
| |
| // CopyFromContainer has been DEPRECATED, please use DownloadFromContainerOptions along with DownloadFromContainer. |
| // |
| // See https://goo.gl/R2jevW for more details. |
| func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error { |
| if opts.Container == "" { |
| return &NoSuchContainer{ID: opts.Container} |
| } |
| url := fmt.Sprintf("/containers/%s/copy", opts.Container) |
| resp, err := c.do("POST", url, doOptions{data: opts}) |
| if err != nil { |
| if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { |
| return &NoSuchContainer{ID: opts.Container} |
| } |
| return err |
| } |
| defer resp.Body.Close() |
| _, err = io.Copy(opts.OutputStream, resp.Body) |
| return err |
| } |
| |
| // WaitContainer blocks until the given container stops, return the exit code |
| // of the container status. |
| // |
| // See https://goo.gl/Gc1rge for more details. |
| func (c *Client) WaitContainer(id string) (int, error) { |
| resp, err := c.do("POST", "/containers/"+id+"/wait", doOptions{}) |
| if err != nil { |
| if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { |
| return 0, &NoSuchContainer{ID: id} |
| } |
| return 0, err |
| } |
| defer resp.Body.Close() |
| var r struct{ StatusCode int } |
| if err := json.NewDecoder(resp.Body).Decode(&r); err != nil { |
| return 0, err |
| } |
| return r.StatusCode, nil |
| } |
| |
| // CommitContainerOptions aggregates parameters to the CommitContainer method. |
| // |
| // See https://goo.gl/mqfoCw for more details. |
| type CommitContainerOptions struct { |
| Container string |
| Repository string `qs:"repo"` |
| Tag string |
| Message string `qs:"comment"` |
| Author string |
| Run *Config `qs:"-"` |
| } |
| |
| // CommitContainer creates a new image from a container's changes. |
| // |
| // See https://goo.gl/mqfoCw for more details. |
| func (c *Client) CommitContainer(opts CommitContainerOptions) (*Image, error) { |
| path := "/commit?" + queryString(opts) |
| resp, err := c.do("POST", path, doOptions{data: opts.Run}) |
| if err != nil { |
| if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { |
| return nil, &NoSuchContainer{ID: opts.Container} |
| } |
| return nil, err |
| } |
| defer resp.Body.Close() |
| var image Image |
| if err := json.NewDecoder(resp.Body).Decode(&image); err != nil { |
| return nil, err |
| } |
| return &image, nil |
| } |
| |
| // AttachToContainerOptions is the set of options that can be used when |
| // attaching to a container. |
| // |
| // See https://goo.gl/NKpkFk for more details. |
| type AttachToContainerOptions struct { |
| Container string `qs:"-"` |
| InputStream io.Reader `qs:"-"` |
| OutputStream io.Writer `qs:"-"` |
| ErrorStream io.Writer `qs:"-"` |
| |
| // Get container logs, sending it to OutputStream. |
| Logs bool |
| |
| // Stream the response? |
| Stream bool |
| |
| // Attach to stdin, and use InputStream. |
| Stdin bool |
| |
| // Attach to stdout, and use OutputStream. |
| Stdout bool |
| |
| // Attach to stderr, and use ErrorStream. |
| Stderr bool |
| |
| // If set, after a successful connect, a sentinel will be sent and then the |
| // client will block on receive before continuing. |
| // |
| // It must be an unbuffered channel. Using a buffered channel can lead |
| // to unexpected behavior. |
| Success chan struct{} |
| |
| // Use raw terminal? Usually true when the container contains a TTY. |
| RawTerminal bool `qs:"-"` |
| } |
| |
| // AttachToContainer attaches to a container, using the given options. |
| // |
| // See https://goo.gl/NKpkFk for more details. |
| func (c *Client) AttachToContainer(opts AttachToContainerOptions) error { |
| cw, err := c.AttachToContainerNonBlocking(opts) |
| if err != nil { |
| return err |
| } |
| return cw.Wait() |
| } |
| |
| // AttachToContainerNonBlocking attaches to a container, using the given options. |
| // This function does not block. |
| // |
| // See https://goo.gl/NKpkFk for more details. |
| func (c *Client) AttachToContainerNonBlocking(opts AttachToContainerOptions) (CloseWaiter, error) { |
| if opts.Container == "" { |
| return nil, &NoSuchContainer{ID: opts.Container} |
| } |
| path := "/containers/" + opts.Container + "/attach?" + queryString(opts) |
| return c.hijack("POST", path, hijackOptions{ |
| success: opts.Success, |
| setRawTerminal: opts.RawTerminal, |
| in: opts.InputStream, |
| stdout: opts.OutputStream, |
| stderr: opts.ErrorStream, |
| }) |
| } |
| |
| // LogsOptions represents the set of options used when getting logs from a |
| // container. |
| // |
| // See https://goo.gl/yl8PGm for more details. |
| type LogsOptions struct { |
| Container string `qs:"-"` |
| OutputStream io.Writer `qs:"-"` |
| ErrorStream io.Writer `qs:"-"` |
| InactivityTimeout time.Duration `qs:"-"` |
| Follow bool |
| Stdout bool |
| Stderr bool |
| Since int64 |
| Timestamps bool |
| Tail string |
| |
| // Use raw terminal? Usually true when the container contains a TTY. |
| RawTerminal bool `qs:"-"` |
| } |
| |
| // Logs gets stdout and stderr logs from the specified container. |
| // |
| // See https://goo.gl/yl8PGm for more details. |
| func (c *Client) Logs(opts LogsOptions) error { |
| if opts.Container == "" { |
| return &NoSuchContainer{ID: opts.Container} |
| } |
| if opts.Tail == "" { |
| opts.Tail = "all" |
| } |
| path := "/containers/" + opts.Container + "/logs?" + queryString(opts) |
| return c.stream("GET", path, streamOptions{ |
| setRawTerminal: opts.RawTerminal, |
| stdout: opts.OutputStream, |
| stderr: opts.ErrorStream, |
| inactivityTimeout: opts.InactivityTimeout, |
| }) |
| } |
| |
| // ResizeContainerTTY resizes the terminal to the given height and width. |
| // |
| // See https://goo.gl/xERhCc for more details. |
| func (c *Client) ResizeContainerTTY(id string, height, width int) error { |
| params := make(url.Values) |
| params.Set("h", strconv.Itoa(height)) |
| params.Set("w", strconv.Itoa(width)) |
| resp, err := c.do("POST", "/containers/"+id+"/resize?"+params.Encode(), doOptions{}) |
| if err != nil { |
| return err |
| } |
| resp.Body.Close() |
| return nil |
| } |
| |
| // ExportContainerOptions is the set of parameters to the ExportContainer |
| // method. |
| // |
| // See https://goo.gl/dOkTyk for more details. |
| type ExportContainerOptions struct { |
| ID string |
| OutputStream io.Writer |
| InactivityTimeout time.Duration `qs:"-"` |
| } |
| |
| // ExportContainer export the contents of container id as tar archive |
| // and prints the exported contents to stdout. |
| // |
| // See https://goo.gl/dOkTyk for more details. |
| func (c *Client) ExportContainer(opts ExportContainerOptions) error { |
| if opts.ID == "" { |
| return &NoSuchContainer{ID: opts.ID} |
| } |
| url := fmt.Sprintf("/containers/%s/export", opts.ID) |
| return c.stream("GET", url, streamOptions{ |
| setRawTerminal: true, |
| stdout: opts.OutputStream, |
| inactivityTimeout: opts.InactivityTimeout, |
| }) |
| } |
| |
| // NoSuchContainer is the error returned when a given container does not exist. |
| type NoSuchContainer struct { |
| ID string |
| Err error |
| } |
| |
| func (err *NoSuchContainer) Error() string { |
| if err.Err != nil { |
| return err.Err.Error() |
| } |
| return "No such container: " + err.ID |
| } |
| |
| // ContainerAlreadyRunning is the error returned when a given container is |
| // already running. |
| type ContainerAlreadyRunning struct { |
| ID string |
| } |
| |
| func (err *ContainerAlreadyRunning) Error() string { |
| return "Container already running: " + err.ID |
| } |
| |
| // ContainerNotRunning is the error returned when a given container is not |
| // running. |
| type ContainerNotRunning struct { |
| ID string |
| } |
| |
| func (err *ContainerNotRunning) Error() string { |
| return "Container not running: " + err.ID |
| } |