mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-30 10:47:14 -06:00
1b6abe6f43
Downgrade go-dockerclient to before it used the docker packages. Remove new docker packages from vendor. Remove gotty client from vendor, which can't build on solaris at all.
1326 lines
52 KiB
Go
1326 lines
52 KiB
Go
// 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
|
|
}
|