bump(fsouza/go-dockerclient): 76fd6c6

This commit is contained in:
Andy Goldstein
2015-08-28 16:17:14 -04:00
parent cbce3a1ab8
commit 2bf984a5ab
17 changed files with 610 additions and 226 deletions

2
Godeps/Godeps.json generated
View File

@@ -224,7 +224,7 @@
}, },
{ {
"ImportPath": "github.com/fsouza/go-dockerclient", "ImportPath": "github.com/fsouza/go-dockerclient",
"Rev": "42d06e2b125654477366c320dcea99107a86e9c2" "Rev": "76fd6c68cf24c48ee6a2b25def997182a29f940e"
}, },
{ {
"ImportPath": "github.com/garyburd/redigo/internal", "ImportPath": "github.com/garyburd/redigo/internal",

View File

@@ -3,6 +3,7 @@ sudo: false
go: go:
- 1.3.1 - 1.3.1
- 1.4 - 1.4
- 1.5
- tip - tip
env: env:
- GOARCH=amd64 - GOARCH=amd64

View File

@@ -13,6 +13,7 @@ Brian Lalor <blalor@bravo5.org>
Brian Palmer <brianp@instructure.com> Brian Palmer <brianp@instructure.com>
Burke Libbey <burke@libbey.me> Burke Libbey <burke@libbey.me>
Carlos Diaz-Padron <cpadron@mozilla.com> Carlos Diaz-Padron <cpadron@mozilla.com>
Cesar Wong <cewong@redhat.com>
Cezar Sa Espinola <cezar.sa@corp.globo.com> Cezar Sa Espinola <cezar.sa@corp.globo.com>
Cheah Chu Yeow <chuyeow@gmail.com> Cheah Chu Yeow <chuyeow@gmail.com>
cheneydeng <cheneydeng@qq.com> cheneydeng <cheneydeng@qq.com>
@@ -33,6 +34,7 @@ Fabio Rehm <fgrehm@gmail.com>
Fatih Arslan <ftharsln@gmail.com> Fatih Arslan <ftharsln@gmail.com>
Flavia Missi <flaviamissi@gmail.com> Flavia Missi <flaviamissi@gmail.com>
Francisco Souza <f@souza.cc> Francisco Souza <f@souza.cc>
Grégoire Delattre <gregoire.delattre@gmail.com>
Guillermo Álvarez Fernández <guillermo@cientifico.net> Guillermo Álvarez Fernández <guillermo@cientifico.net>
He Simei <hesimei@zju.edu.cn> He Simei <hesimei@zju.edu.cn>
Ivan Mikushin <i.mikushin@gmail.com> Ivan Mikushin <i.mikushin@gmail.com>
@@ -66,12 +68,14 @@ Paul Morie <pmorie@gmail.com>
Paul Weil <pweil@redhat.com> Paul Weil <pweil@redhat.com>
Peter Edge <peter.edge@gmail.com> Peter Edge <peter.edge@gmail.com>
Peter Jihoon Kim <raingrove@gmail.com> Peter Jihoon Kim <raingrove@gmail.com>
Phil Lu <lu@stackengine.com>
Philippe Lafoucrière <philippe.lafoucriere@tech-angels.com> Philippe Lafoucrière <philippe.lafoucriere@tech-angels.com>
Rafe Colton <rafael.colton@gmail.com> Rafe Colton <rafael.colton@gmail.com>
Rob Miller <rob@kalistra.com> Rob Miller <rob@kalistra.com>
Robert Williamson <williamson.robert@gmail.com> Robert Williamson <williamson.robert@gmail.com>
Salvador Gironès <salvadorgirones@gmail.com> Salvador Gironès <salvadorgirones@gmail.com>
Sam Rijs <srijs@airpost.net> Sam Rijs <srijs@airpost.net>
Samuel Karp <skarp@amazon.com>
Simon Eskildsen <sirup@sirupsen.com> Simon Eskildsen <sirup@sirupsen.com>
Simon Menke <simon.menke@gmail.com> Simon Menke <simon.menke@gmail.com>
Skolos <skolos@gopherlab.com> Skolos <skolos@gopherlab.com>

View File

@@ -16,7 +16,8 @@ import (
"strings" "strings"
) )
var AuthParseError error = errors.New("Failed to read authentication from dockercfg") // ErrCannotParseDockercfg is the error returned by NewAuthConfigurations when the dockercfg cannot be parsed.
var ErrCannotParseDockercfg = errors.New("Failed to read authentication from dockercfg")
// AuthConfiguration represents authentication options to use in the PushImage // AuthConfiguration represents authentication options to use in the PushImage
// method. It represents the authentication in the Docker index server. // method. It represents the authentication in the Docker index server.
@@ -33,6 +34,10 @@ type AuthConfigurations struct {
Configs map[string]AuthConfiguration `json:"configs"` Configs map[string]AuthConfiguration `json:"configs"`
} }
// AuthConfigurations119 is used to serialize a set of AuthConfigurations
// for Docker API >= 1.19.
type AuthConfigurations119 map[string]AuthConfiguration
// dockerConfig represents a registry authentation configuration from the // dockerConfig represents a registry authentation configuration from the
// .dockercfg file. // .dockercfg file.
type dockerConfig struct { type dockerConfig struct {
@@ -103,7 +108,7 @@ func authConfigs(confs map[string]dockerConfig) (*AuthConfigurations, error) {
} }
userpass := strings.Split(string(data), ":") userpass := strings.Split(string(data), ":")
if len(userpass) != 2 { if len(userpass) != 2 {
return nil, AuthParseError return nil, ErrCannotParseDockercfg
} }
c.Configs[reg] = AuthConfiguration{ c.Configs[reg] = AuthConfiguration{
Email: conf.Email, Email: conf.Email,
@@ -117,7 +122,7 @@ func authConfigs(confs map[string]dockerConfig) (*AuthConfigurations, error) {
// AuthCheck validates the given credentials. It returns nil if successful. // AuthCheck validates the given credentials. It returns nil if successful.
// //
// See https://goo.gl/vPoEfJ for more details. // See https://goo.gl/m2SleN for more details.
func (c *Client) AuthCheck(conf *AuthConfiguration) error { func (c *Client) AuthCheck(conf *AuthConfiguration) error {
if conf == nil { if conf == nil {
return fmt.Errorf("conf is nil") return fmt.Errorf("conf is nil")

View File

@@ -41,7 +41,7 @@ func TestAuthBadConfig(t *testing.T) {
auth := base64.StdEncoding.EncodeToString([]byte("userpass")) auth := base64.StdEncoding.EncodeToString([]byte("userpass"))
read := strings.NewReader(fmt.Sprintf(`{"docker.io":{"auth":"%s","email":"user@example.com"}}`, auth)) read := strings.NewReader(fmt.Sprintf(`{"docker.io":{"auth":"%s","email":"user@example.com"}}`, auth))
ac, err := NewAuthConfigurations(read) ac, err := NewAuthConfigurations(read)
if err != AuthParseError { if err != ErrCannotParseDockercfg {
t.Errorf("Incorrect error returned %v\n", err) t.Errorf("Incorrect error returned %v\n", err)
} }
if ac != nil { if ac != nil {

View File

@@ -23,7 +23,7 @@ const (
// Change represents a change in a container. // Change represents a change in a container.
// //
// See http://goo.gl/QkW9sH for more details. // See https://goo.gl/9GsTIF for more details.
type Change struct { type Change struct {
Path string Path string
Kind ChangeType Kind ChangeType

View File

@@ -4,7 +4,7 @@
// Package docker provides a client for the Docker remote API. // Package docker provides a client for the Docker remote API.
// //
// See http://goo.gl/G3plxW for more details on the remote API. // See https://goo.gl/G3plxW for more details on the remote API.
package docker package docker
import ( import (
@@ -45,6 +45,8 @@ var (
ErrConnectionRefused = errors.New("cannot connect to Docker endpoint") ErrConnectionRefused = errors.New("cannot connect to Docker endpoint")
apiVersion112, _ = NewAPIVersion("1.12") apiVersion112, _ = NewAPIVersion("1.12")
apiVersion119, _ = NewAPIVersion("1.19")
) )
// APIVersion is an internal representation of a version of the Remote API. // APIVersion is an internal representation of a version of the Remote API.
@@ -326,7 +328,7 @@ func (c *Client) checkAPIVersion() error {
// Ping pings the docker server // Ping pings the docker server
// //
// See http://goo.gl/stJENm for more details. // See https://goo.gl/kQCfJj for more details.
func (c *Client) Ping() error { func (c *Client) Ping() error {
path := "/_ping" path := "/_ping"
body, status, err := c.do("GET", path, doOptions{}) body, status, err := c.do("GET", path, doOptions{})
@@ -462,9 +464,13 @@ func (c *Client) stream(method, path string, streamOptions streamOptions) error
address := c.endpointURL.Path address := c.endpointURL.Path
if streamOptions.stdout == nil { if streamOptions.stdout == nil {
streamOptions.stdout = ioutil.Discard streamOptions.stdout = ioutil.Discard
} else if t, ok := streamOptions.stdout.(io.Closer); ok {
defer t.Close()
} }
if streamOptions.stderr == nil { if streamOptions.stderr == nil {
streamOptions.stderr = ioutil.Discard streamOptions.stderr = ioutil.Discard
} else if t, ok := streamOptions.stderr.(io.Closer); ok {
defer t.Close()
} }
if protocol == "unix" { if protocol == "unix" {
dial, err := net.Dial(protocol, address) dial, err := net.Dial(protocol, address)
@@ -583,6 +589,8 @@ func (c *Client) hijack(method, path string, hijackOptions hijackOptions) error
return err return err
} }
req.Header.Set("Content-Type", "plain/text") req.Header.Set("Content-Type", "plain/text")
req.Header.Set("Connection", "Upgrade")
req.Header.Set("Upgrade", "tcp")
protocol := c.endpointURL.Scheme protocol := c.endpointURL.Scheme
address := c.endpointURL.Path address := c.endpointURL.Path
if protocol != "unix" { if protocol != "unix" {
@@ -612,13 +620,16 @@ func (c *Client) hijack(method, path string, hijackOptions hijackOptions) error
defer rwc.Close() defer rwc.Close()
errChanOut := make(chan error, 1) errChanOut := make(chan error, 1)
errChanIn := make(chan error, 1) errChanIn := make(chan error, 1)
exit := make(chan bool)
go func() { go func() {
defer close(exit) defer func() {
defer close(errChanOut) if hijackOptions.in != nil {
if closer, ok := hijackOptions.in.(io.Closer); ok {
closer.Close()
}
}
}()
var err error var err error
if hijackOptions.setRawTerminal { if hijackOptions.setRawTerminal {
// When TTY is ON, use regular copy
_, err = io.Copy(hijackOptions.stdout, br) _, err = io.Copy(hijackOptions.stdout, br)
} else { } else {
_, err = stdcopy.StdCopy(hijackOptions.stdout, hijackOptions.stderr, br) _, err = stdcopy.StdCopy(hijackOptions.stdout, hijackOptions.stderr, br)
@@ -626,17 +637,15 @@ func (c *Client) hijack(method, path string, hijackOptions hijackOptions) error
errChanOut <- err errChanOut <- err
}() }()
go func() { go func() {
var err error
if hijackOptions.in != nil { if hijackOptions.in != nil {
_, err := io.Copy(rwc, hijackOptions.in) _, err = io.Copy(rwc, hijackOptions.in)
errChanIn <- err
} else {
errChanIn <- nil
} }
errChanIn <- err
rwc.(interface { rwc.(interface {
CloseWrite() error CloseWrite() error
}).CloseWrite() }).CloseWrite()
}() }()
<-exit
errIn := <-errChanIn errIn := <-errChanIn
errOut := <-errChanOut errOut := <-errChanOut
if errIn != nil { if errIn != nil {

View File

@@ -23,7 +23,7 @@ var ErrContainerAlreadyExists = errors.New("container already exists")
// ListContainersOptions specify parameters to the ListContainers function. // ListContainersOptions specify parameters to the ListContainers function.
// //
// See http://goo.gl/6Y4Gz7 for more details. // See https://goo.gl/47a6tO for more details.
type ListContainersOptions struct { type ListContainersOptions struct {
All bool All bool
Size bool Size bool
@@ -41,24 +41,24 @@ type APIPort struct {
IP string `json:"IP,omitempty" yaml:"IP,omitempty"` IP string `json:"IP,omitempty" yaml:"IP,omitempty"`
} }
// APIContainers represents a container. // APIContainers represents each container in the list returned by
// // ListContainers.
// See http://goo.gl/QeFH7U for more details.
type APIContainers struct { type APIContainers struct {
ID string `json:"Id" yaml:"Id"` ID string `json:"Id" yaml:"Id"`
Image string `json:"Image,omitempty" yaml:"Image,omitempty"` Image string `json:"Image,omitempty" yaml:"Image,omitempty"`
Command string `json:"Command,omitempty" yaml:"Command,omitempty"` Command string `json:"Command,omitempty" yaml:"Command,omitempty"`
Created int64 `json:"Created,omitempty" yaml:"Created,omitempty"` Created int64 `json:"Created,omitempty" yaml:"Created,omitempty"`
Status string `json:"Status,omitempty" yaml:"Status,omitempty"` Status string `json:"Status,omitempty" yaml:"Status,omitempty"`
Ports []APIPort `json:"Ports,omitempty" yaml:"Ports,omitempty"` Ports []APIPort `json:"Ports,omitempty" yaml:"Ports,omitempty"`
SizeRw int64 `json:"SizeRw,omitempty" yaml:"SizeRw,omitempty"` SizeRw int64 `json:"SizeRw,omitempty" yaml:"SizeRw,omitempty"`
SizeRootFs int64 `json:"SizeRootFs,omitempty" yaml:"SizeRootFs,omitempty"` SizeRootFs int64 `json:"SizeRootFs,omitempty" yaml:"SizeRootFs,omitempty"`
Names []string `json:"Names,omitempty" yaml:"Names,omitempty"` Names []string `json:"Names,omitempty" yaml:"Names,omitempty"`
Labels map[string]string `json:"Labels,omitempty" yaml:"Labels, omitempty"`
} }
// ListContainers returns a slice of containers matching the given criteria. // ListContainers returns a slice of containers matching the given criteria.
// //
// See http://goo.gl/6Y4Gz7 for more details. // See https://goo.gl/47a6tO for more details.
func (c *Client) ListContainers(opts ListContainersOptions) ([]APIContainers, error) { func (c *Client) ListContainers(opts ListContainersOptions) ([]APIContainers, error) {
path := "/containers/json?" + queryString(opts) path := "/containers/json?" + queryString(opts)
body, _, err := c.do("GET", path, doOptions{}) body, _, err := c.do("GET", path, doOptions{})
@@ -213,9 +213,21 @@ type Config struct {
NetworkDisabled bool `json:"NetworkDisabled,omitempty" yaml:"NetworkDisabled,omitempty"` NetworkDisabled bool `json:"NetworkDisabled,omitempty" yaml:"NetworkDisabled,omitempty"`
SecurityOpts []string `json:"SecurityOpts,omitempty" yaml:"SecurityOpts,omitempty"` SecurityOpts []string `json:"SecurityOpts,omitempty" yaml:"SecurityOpts,omitempty"`
OnBuild []string `json:"OnBuild,omitempty" yaml:"OnBuild,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"` 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 {
Source string
Destination string
Mode string
RW bool
}
// LogConfig defines the log driver type and the configuration for it. // LogConfig defines the log driver type and the configuration for it.
type LogConfig struct { type LogConfig struct {
Type string `json:"Type,omitempty" yaml:"Type,omitempty"` Type string `json:"Type,omitempty" yaml:"Type,omitempty"`
@@ -279,7 +291,7 @@ type Container struct {
// RenameContainerOptions specify parameters to the RenameContainer function. // RenameContainerOptions specify parameters to the RenameContainer function.
// //
// See http://goo.gl/L00hoj for more details. // See https://goo.gl/laSOIy for more details.
type RenameContainerOptions struct { type RenameContainerOptions struct {
// ID of container to rename // ID of container to rename
ID string `qs:"-"` ID string `qs:"-"`
@@ -290,7 +302,7 @@ type RenameContainerOptions struct {
// RenameContainer updates and existing containers name // RenameContainer updates and existing containers name
// //
// See http://goo.gl/L00hoj for more details. // See https://goo.gl/laSOIy for more details.
func (c *Client) RenameContainer(opts RenameContainerOptions) error { func (c *Client) RenameContainer(opts RenameContainerOptions) error {
_, _, err := c.do("POST", fmt.Sprintf("/containers/"+opts.ID+"/rename?%s", queryString(opts)), doOptions{}) _, _, err := c.do("POST", fmt.Sprintf("/containers/"+opts.ID+"/rename?%s", queryString(opts)), doOptions{})
return err return err
@@ -298,7 +310,7 @@ func (c *Client) RenameContainer(opts RenameContainerOptions) error {
// InspectContainer returns information about a container by its ID. // InspectContainer returns information about a container by its ID.
// //
// See http://goo.gl/CxVuJ5 for more details. // See https://goo.gl/RdIq0b for more details.
func (c *Client) InspectContainer(id string) (*Container, error) { func (c *Client) InspectContainer(id string) (*Container, error) {
path := "/containers/" + id + "/json" path := "/containers/" + id + "/json"
body, status, err := c.do("GET", path, doOptions{}) body, status, err := c.do("GET", path, doOptions{})
@@ -318,7 +330,7 @@ func (c *Client) InspectContainer(id string) (*Container, error) {
// ContainerChanges returns changes in the filesystem of the given container. // ContainerChanges returns changes in the filesystem of the given container.
// //
// See http://goo.gl/QkW9sH for more details. // See https://goo.gl/9GsTIF for more details.
func (c *Client) ContainerChanges(id string) ([]Change, error) { func (c *Client) ContainerChanges(id string) ([]Change, error) {
path := "/containers/" + id + "/changes" path := "/containers/" + id + "/changes"
body, status, err := c.do("GET", path, doOptions{}) body, status, err := c.do("GET", path, doOptions{})
@@ -338,7 +350,7 @@ func (c *Client) ContainerChanges(id string) ([]Change, error) {
// CreateContainerOptions specify parameters to the CreateContainer function. // CreateContainerOptions specify parameters to the CreateContainer function.
// //
// See http://goo.gl/2xxQQK for more details. // See https://goo.gl/WxQzrr for more details.
type CreateContainerOptions struct { type CreateContainerOptions struct {
Name string Name string
Config *Config `qs:"-"` Config *Config `qs:"-"`
@@ -348,7 +360,7 @@ type CreateContainerOptions struct {
// CreateContainer creates a new container, returning the container instance, // CreateContainer creates a new container, returning the container instance,
// or an error in case of failure. // or an error in case of failure.
// //
// See http://goo.gl/mErxNp for more details. // See https://goo.gl/WxQzrr for more details.
func (c *Client) CreateContainer(opts CreateContainerOptions) (*Container, error) { func (c *Client) CreateContainer(opts CreateContainerOptions) (*Container, error) {
path := "/containers/create?" + queryString(opts) path := "/containers/create?" + queryString(opts)
body, status, err := c.do( body, status, err := c.do(
@@ -434,41 +446,46 @@ type Device struct {
// HostConfig contains the container options related to starting a container on // HostConfig contains the container options related to starting a container on
// a given host // a given host
type HostConfig struct { type HostConfig struct {
Binds []string `json:"Binds,omitempty" yaml:"Binds,omitempty"` Binds []string `json:"Binds,omitempty" yaml:"Binds,omitempty"`
CapAdd []string `json:"CapAdd,omitempty" yaml:"CapAdd,omitempty"` CapAdd []string `json:"CapAdd,omitempty" yaml:"CapAdd,omitempty"`
CapDrop []string `json:"CapDrop,omitempty" yaml:"CapDrop,omitempty"` CapDrop []string `json:"CapDrop,omitempty" yaml:"CapDrop,omitempty"`
ContainerIDFile string `json:"ContainerIDFile,omitempty" yaml:"ContainerIDFile,omitempty"` ContainerIDFile string `json:"ContainerIDFile,omitempty" yaml:"ContainerIDFile,omitempty"`
LxcConf []KeyValuePair `json:"LxcConf,omitempty" yaml:"LxcConf,omitempty"` LxcConf []KeyValuePair `json:"LxcConf,omitempty" yaml:"LxcConf,omitempty"`
Privileged bool `json:"Privileged,omitempty" yaml:"Privileged,omitempty"` Privileged bool `json:"Privileged,omitempty" yaml:"Privileged,omitempty"`
PortBindings map[Port][]PortBinding `json:"PortBindings,omitempty" yaml:"PortBindings,omitempty"` PortBindings map[Port][]PortBinding `json:"PortBindings,omitempty" yaml:"PortBindings,omitempty"`
Links []string `json:"Links,omitempty" yaml:"Links,omitempty"` Links []string `json:"Links,omitempty" yaml:"Links,omitempty"`
PublishAllPorts bool `json:"PublishAllPorts,omitempty" yaml:"PublishAllPorts,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 DNS []string `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.10 and above only
DNSSearch []string `json:"DnsSearch,omitempty" yaml:"DnsSearch,omitempty"` DNSSearch []string `json:"DnsSearch,omitempty" yaml:"DnsSearch,omitempty"`
ExtraHosts []string `json:"ExtraHosts,omitempty" yaml:"ExtraHosts,omitempty"` ExtraHosts []string `json:"ExtraHosts,omitempty" yaml:"ExtraHosts,omitempty"`
VolumesFrom []string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"` VolumesFrom []string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"`
NetworkMode string `json:"NetworkMode,omitempty" yaml:"NetworkMode,omitempty"` NetworkMode string `json:"NetworkMode,omitempty" yaml:"NetworkMode,omitempty"`
IpcMode string `json:"IpcMode,omitempty" yaml:"IpcMode,omitempty"` IpcMode string `json:"IpcMode,omitempty" yaml:"IpcMode,omitempty"`
PidMode string `json:"PidMode,omitempty" yaml:"PidMode,omitempty"` PidMode string `json:"PidMode,omitempty" yaml:"PidMode,omitempty"`
UTSMode string `json:"UTSMode,omitempty" yaml:"UTSMode,omitempty"` UTSMode string `json:"UTSMode,omitempty" yaml:"UTSMode,omitempty"`
RestartPolicy RestartPolicy `json:"RestartPolicy,omitempty" yaml:"RestartPolicy,omitempty"` RestartPolicy RestartPolicy `json:"RestartPolicy,omitempty" yaml:"RestartPolicy,omitempty"`
Devices []Device `json:"Devices,omitempty" yaml:"Devices,omitempty"` Devices []Device `json:"Devices,omitempty" yaml:"Devices,omitempty"`
LogConfig LogConfig `json:"LogConfig,omitempty" yaml:"LogConfig,omitempty"` LogConfig LogConfig `json:"LogConfig,omitempty" yaml:"LogConfig,omitempty"`
ReadonlyRootfs bool `json:"ReadonlyRootfs,omitempty" yaml:"ReadonlyRootfs,omitempty"` ReadonlyRootfs bool `json:"ReadonlyRootfs,omitempty" yaml:"ReadonlyRootfs,omitempty"`
SecurityOpt []string `json:"SecurityOpt,omitempty" yaml:"SecurityOpt,omitempty"` SecurityOpt []string `json:"SecurityOpt,omitempty" yaml:"SecurityOpt,omitempty"`
CgroupParent string `json:"CgroupParent,omitempty" yaml:"CgroupParent,omitempty"` CgroupParent string `json:"CgroupParent,omitempty" yaml:"CgroupParent,omitempty"`
Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty"` Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty"`
MemorySwap int64 `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty"` MemorySwap int64 `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty"`
CPUShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty"` MemorySwappiness int64 `json:"MemorySwappiness,omitempty" yaml:"MemorySwappiness,omitempty"`
CPUSet string `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty"` OOMKillDisable bool `json:"OomKillDisable,omitempty" yaml:"OomKillDisable"`
CPUQuota int64 `json:"CpuQuota,omitempty" yaml:"CpuQuota,omitempty"` CPUShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty"`
CPUPeriod int64 `json:"CpuPeriod,omitempty" yaml:"CpuPeriod,omitempty"` CPUSet string `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty"`
Ulimits []ULimit `json:"Ulimits,omitempty" yaml:"Ulimits,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"`
Ulimits []ULimit `json:"Ulimits,omitempty" yaml:"Ulimits,omitempty"`
} }
// StartContainer starts a container, returning an error in case of failure. // StartContainer starts a container, returning an error in case of failure.
// //
// See http://goo.gl/iM5GYs for more details. // See https://goo.gl/MrBAJv for more details.
func (c *Client) StartContainer(id string, hostConfig *HostConfig) error { func (c *Client) StartContainer(id string, hostConfig *HostConfig) error {
path := "/containers/" + id + "/start" path := "/containers/" + id + "/start"
_, status, err := c.do("POST", path, doOptions{data: hostConfig, forceJSON: true}) _, status, err := c.do("POST", path, doOptions{data: hostConfig, forceJSON: true})
@@ -487,7 +504,7 @@ func (c *Client) StartContainer(id string, hostConfig *HostConfig) error {
// StopContainer stops a container, killing it after the given timeout (in // StopContainer stops a container, killing it after the given timeout (in
// seconds). // seconds).
// //
// See http://goo.gl/EbcpXt for more details. // See https://goo.gl/USqsFt for more details.
func (c *Client) StopContainer(id string, timeout uint) error { func (c *Client) StopContainer(id string, timeout uint) error {
path := fmt.Sprintf("/containers/%s/stop?t=%d", id, timeout) path := fmt.Sprintf("/containers/%s/stop?t=%d", id, timeout)
_, status, err := c.do("POST", path, doOptions{}) _, status, err := c.do("POST", path, doOptions{})
@@ -506,7 +523,7 @@ func (c *Client) StopContainer(id string, timeout uint) error {
// RestartContainer stops a container, killing it after the given timeout (in // RestartContainer stops a container, killing it after the given timeout (in
// seconds), during the stop process. // seconds), during the stop process.
// //
// See http://goo.gl/VOzR2n for more details. // See https://goo.gl/QzsDnz for more details.
func (c *Client) RestartContainer(id string, timeout uint) error { func (c *Client) RestartContainer(id string, timeout uint) error {
path := fmt.Sprintf("/containers/%s/restart?t=%d", id, timeout) path := fmt.Sprintf("/containers/%s/restart?t=%d", id, timeout)
_, status, err := c.do("POST", path, doOptions{}) _, status, err := c.do("POST", path, doOptions{})
@@ -521,7 +538,7 @@ func (c *Client) RestartContainer(id string, timeout uint) error {
// PauseContainer pauses the given container. // PauseContainer pauses the given container.
// //
// See http://goo.gl/AM5t42 for more details. // See https://goo.gl/OF7W9X for more details.
func (c *Client) PauseContainer(id string) error { func (c *Client) PauseContainer(id string) error {
path := fmt.Sprintf("/containers/%s/pause", id) path := fmt.Sprintf("/containers/%s/pause", id)
_, status, err := c.do("POST", path, doOptions{}) _, status, err := c.do("POST", path, doOptions{})
@@ -536,7 +553,7 @@ func (c *Client) PauseContainer(id string) error {
// UnpauseContainer unpauses the given container. // UnpauseContainer unpauses the given container.
// //
// See http://goo.gl/eBrNSL for more details. // See https://goo.gl/7dwyPA for more details.
func (c *Client) UnpauseContainer(id string) error { func (c *Client) UnpauseContainer(id string) error {
path := fmt.Sprintf("/containers/%s/unpause", id) path := fmt.Sprintf("/containers/%s/unpause", id)
_, status, err := c.do("POST", path, doOptions{}) _, status, err := c.do("POST", path, doOptions{})
@@ -552,7 +569,7 @@ func (c *Client) UnpauseContainer(id string) error {
// TopResult represents the list of processes running in a container, as // TopResult represents the list of processes running in a container, as
// returned by /containers/<id>/top. // returned by /containers/<id>/top.
// //
// See http://goo.gl/qu4gse for more details. // See https://goo.gl/Rb46aY for more details.
type TopResult struct { type TopResult struct {
Titles []string Titles []string
Processes [][]string Processes [][]string
@@ -560,7 +577,7 @@ type TopResult struct {
// TopContainer returns processes running inside a container // TopContainer returns processes running inside a container
// //
// See http://goo.gl/qu4gse for more details. // See https://goo.gl/Rb46aY for more details.
func (c *Client) TopContainer(id string, psArgs string) (TopResult, error) { func (c *Client) TopContainer(id string, psArgs string) (TopResult, error) {
var args string var args string
var result TopResult var result TopResult
@@ -584,7 +601,7 @@ func (c *Client) TopContainer(id string, psArgs string) (TopResult, error) {
// Stats represents container statistics, returned by /containers/<id>/stats. // Stats represents container statistics, returned by /containers/<id>/stats.
// //
// See http://goo.gl/DFMiYD for more details. // See https://goo.gl/GNmLHb for more details.
type Stats struct { type Stats struct {
Read time.Time `json:"read,omitempty" yaml:"read,omitempty"` Read time.Time `json:"read,omitempty" yaml:"read,omitempty"`
Network struct { Network struct {
@@ -674,7 +691,7 @@ type BlkioStatsEntry struct {
// StatsOptions specify parameters to the Stats function. // StatsOptions specify parameters to the Stats function.
// //
// See http://goo.gl/DFMiYD for more details. // See https://goo.gl/GNmLHb for more details.
type StatsOptions struct { type StatsOptions struct {
ID string ID string
Stats chan<- *Stats Stats chan<- *Stats
@@ -690,9 +707,10 @@ type StatsOptions struct {
// This function is blocking, similar to a streaming call for logs, and should be run // 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 // 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 // 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 // will close the given channel. Alternatively, function can be stopped by
// signaling on the Done channel.
// //
// See http://goo.gl/DFMiYD for more details. // See https://goo.gl/GNmLHb for more details.
func (c *Client) Stats(opts StatsOptions) (retErr error) { func (c *Client) Stats(opts StatsOptions) (retErr error) {
errC := make(chan error, 1) errC := make(chan error, 1)
readCloser, writeCloser := io.Pipe() readCloser, writeCloser := io.Pipe()
@@ -763,7 +781,7 @@ func (c *Client) Stats(opts StatsOptions) (retErr error) {
// KillContainerOptions represents the set of options that can be used in a // KillContainerOptions represents the set of options that can be used in a
// call to KillContainer. // call to KillContainer.
// //
// See http://goo.gl/TFkECx for more details. // See https://goo.gl/hkS9i8 for more details.
type KillContainerOptions struct { type KillContainerOptions struct {
// The ID of the container. // The ID of the container.
ID string `qs:"-"` ID string `qs:"-"`
@@ -773,9 +791,10 @@ type KillContainerOptions struct {
Signal Signal Signal Signal
} }
// KillContainer kills a container, returning an error in case of failure. // KillContainer sends a signal to a container, returning an error in case of
// failure.
// //
// See http://goo.gl/TFkECx for more details. // See https://goo.gl/hkS9i8 for more details.
func (c *Client) KillContainer(opts KillContainerOptions) error { func (c *Client) KillContainer(opts KillContainerOptions) error {
path := "/containers/" + opts.ID + "/kill" + "?" + queryString(opts) path := "/containers/" + opts.ID + "/kill" + "?" + queryString(opts)
_, status, err := c.do("POST", path, doOptions{}) _, status, err := c.do("POST", path, doOptions{})
@@ -790,7 +809,7 @@ func (c *Client) KillContainer(opts KillContainerOptions) error {
// RemoveContainerOptions encapsulates options to remove a container. // RemoveContainerOptions encapsulates options to remove a container.
// //
// See http://goo.gl/ZB83ji for more details. // See https://goo.gl/RQyX62 for more details.
type RemoveContainerOptions struct { type RemoveContainerOptions struct {
// The ID of the container. // The ID of the container.
ID string `qs:"-"` ID string `qs:"-"`
@@ -806,7 +825,7 @@ type RemoveContainerOptions struct {
// RemoveContainer removes a container, returning an error in case of failure. // RemoveContainer removes a container, returning an error in case of failure.
// //
// See http://goo.gl/ZB83ji for more details. // See https://goo.gl/RQyX62 for more details.
func (c *Client) RemoveContainer(opts RemoveContainerOptions) error { func (c *Client) RemoveContainer(opts RemoveContainerOptions) error {
path := "/containers/" + opts.ID + "?" + queryString(opts) path := "/containers/" + opts.ID + "?" + queryString(opts)
_, status, err := c.do("DELETE", path, doOptions{}) _, status, err := c.do("DELETE", path, doOptions{})
@@ -822,7 +841,7 @@ func (c *Client) RemoveContainer(opts RemoveContainerOptions) error {
// CopyFromContainerOptions is the set of options that can be used when copying // CopyFromContainerOptions is the set of options that can be used when copying
// files or folders from a container. // files or folders from a container.
// //
// See http://goo.gl/rINMlw for more details. // See https://goo.gl/4L7b07 for more details.
type CopyFromContainerOptions struct { type CopyFromContainerOptions struct {
OutputStream io.Writer `json:"-"` OutputStream io.Writer `json:"-"`
Container string `json:"-"` Container string `json:"-"`
@@ -832,7 +851,7 @@ type CopyFromContainerOptions struct {
// CopyFromContainer copy files or folders from a container, using a given // CopyFromContainer copy files or folders from a container, using a given
// resource. // resource.
// //
// See http://goo.gl/rINMlw for more details. // See https://goo.gl/4L7b07 for more details.
func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error { func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error {
if opts.Container == "" { if opts.Container == "" {
return &NoSuchContainer{ID: opts.Container} return &NoSuchContainer{ID: opts.Container}
@@ -852,7 +871,7 @@ func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error {
// WaitContainer blocks until the given container stops, return the exit code // WaitContainer blocks until the given container stops, return the exit code
// of the container status. // of the container status.
// //
// See http://goo.gl/J88DHU for more details. // See https://goo.gl/Gc1rge for more details.
func (c *Client) WaitContainer(id string) (int, error) { func (c *Client) WaitContainer(id string) (int, error) {
body, status, err := c.do("POST", "/containers/"+id+"/wait", doOptions{}) body, status, err := c.do("POST", "/containers/"+id+"/wait", doOptions{})
if status == http.StatusNotFound { if status == http.StatusNotFound {
@@ -871,7 +890,7 @@ func (c *Client) WaitContainer(id string) (int, error) {
// CommitContainerOptions aggregates parameters to the CommitContainer method. // CommitContainerOptions aggregates parameters to the CommitContainer method.
// //
// See http://goo.gl/Jn8pe8 for more details. // See https://goo.gl/mqfoCw for more details.
type CommitContainerOptions struct { type CommitContainerOptions struct {
Container string Container string
Repository string `qs:"repo"` Repository string `qs:"repo"`
@@ -883,7 +902,7 @@ type CommitContainerOptions struct {
// CommitContainer creates a new image from a container's changes. // CommitContainer creates a new image from a container's changes.
// //
// See http://goo.gl/Jn8pe8 for more details. // See https://goo.gl/mqfoCw for more details.
func (c *Client) CommitContainer(opts CommitContainerOptions) (*Image, error) { func (c *Client) CommitContainer(opts CommitContainerOptions) (*Image, error) {
path := "/commit?" + queryString(opts) path := "/commit?" + queryString(opts)
body, status, err := c.do("POST", path, doOptions{data: opts.Run}) body, status, err := c.do("POST", path, doOptions{data: opts.Run})
@@ -904,7 +923,7 @@ func (c *Client) CommitContainer(opts CommitContainerOptions) (*Image, error) {
// AttachToContainerOptions is the set of options that can be used when // AttachToContainerOptions is the set of options that can be used when
// attaching to a container. // attaching to a container.
// //
// See http://goo.gl/RRAhws for more details. // See https://goo.gl/NKpkFk for more details.
type AttachToContainerOptions struct { type AttachToContainerOptions struct {
Container string `qs:"-"` Container string `qs:"-"`
InputStream io.Reader `qs:"-"` InputStream io.Reader `qs:"-"`
@@ -939,7 +958,7 @@ type AttachToContainerOptions struct {
// AttachToContainer attaches to a container, using the given options. // AttachToContainer attaches to a container, using the given options.
// //
// See http://goo.gl/RRAhws for more details. // See https://goo.gl/NKpkFk for more details.
func (c *Client) AttachToContainer(opts AttachToContainerOptions) error { func (c *Client) AttachToContainer(opts AttachToContainerOptions) error {
if opts.Container == "" { if opts.Container == "" {
return &NoSuchContainer{ID: opts.Container} return &NoSuchContainer{ID: opts.Container}
@@ -957,7 +976,7 @@ func (c *Client) AttachToContainer(opts AttachToContainerOptions) error {
// LogsOptions represents the set of options used when getting logs from a // LogsOptions represents the set of options used when getting logs from a
// container. // container.
// //
// See http://goo.gl/rLhKSU for more details. // See https://goo.gl/yl8PGm for more details.
type LogsOptions struct { type LogsOptions struct {
Container string `qs:"-"` Container string `qs:"-"`
OutputStream io.Writer `qs:"-"` OutputStream io.Writer `qs:"-"`
@@ -975,7 +994,7 @@ type LogsOptions struct {
// Logs gets stdout and stderr logs from the specified container. // Logs gets stdout and stderr logs from the specified container.
// //
// See http://goo.gl/rLhKSU for more details. // See https://goo.gl/yl8PGm for more details.
func (c *Client) Logs(opts LogsOptions) error { func (c *Client) Logs(opts LogsOptions) error {
if opts.Container == "" { if opts.Container == "" {
return &NoSuchContainer{ID: opts.Container} return &NoSuchContainer{ID: opts.Container}
@@ -992,6 +1011,8 @@ func (c *Client) Logs(opts LogsOptions) error {
} }
// ResizeContainerTTY resizes the terminal to the given height and width. // 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 { func (c *Client) ResizeContainerTTY(id string, height, width int) error {
params := make(url.Values) params := make(url.Values)
params.Set("h", strconv.Itoa(height)) params.Set("h", strconv.Itoa(height))
@@ -1003,7 +1024,7 @@ func (c *Client) ResizeContainerTTY(id string, height, width int) error {
// ExportContainerOptions is the set of parameters to the ExportContainer // ExportContainerOptions is the set of parameters to the ExportContainer
// method. // method.
// //
// See http://goo.gl/hnzE62 for more details. // See https://goo.gl/dOkTyk for more details.
type ExportContainerOptions struct { type ExportContainerOptions struct {
ID string ID string
OutputStream io.Writer OutputStream io.Writer
@@ -1012,7 +1033,7 @@ type ExportContainerOptions struct {
// ExportContainer export the contents of container id as tar archive // ExportContainer export the contents of container id as tar archive
// and prints the exported contents to stdout. // and prints the exported contents to stdout.
// //
// See http://goo.gl/hnzE62 for more details. // See https://goo.gl/dOkTyk for more details.
func (c *Client) ExportContainer(opts ExportContainerOptions) error { func (c *Client) ExportContainer(opts ExportContainerOptions) error {
if opts.ID == "" { if opts.ID == "" {
return &NoSuchContainer{ID: opts.ID} return &NoSuchContainer{ID: opts.ID}

View File

@@ -1122,10 +1122,7 @@ func TestAttachToContainerRawTerminalFalse(t *testing.T) {
Stream: true, Stream: true,
RawTerminal: false, RawTerminal: false,
} }
err := client.AttachToContainer(opts) client.AttachToContainer(opts)
if err != nil {
t.Fatal(err)
}
expected := map[string][]string{ expected := map[string][]string{
"stdin": {"1"}, "stdin": {"1"},
"stdout": {"1"}, "stdout": {"1"},

View File

@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Docs can currently be found at https://github.com/docker/docker/blob/master/docs/sources/reference/api/docker_remote_api_v1.15.md#exec-create
package docker package docker
import ( import (
@@ -15,9 +13,15 @@ import (
"strconv" "strconv"
) )
// Exec is the type representing a `docker exec` instance and containing the
// instance ID
type Exec struct {
ID string `json:"Id,omitempty" yaml:"Id,omitempty"`
}
// CreateExecOptions specify parameters to the CreateExecContainer function. // CreateExecOptions specify parameters to the CreateExecContainer function.
// //
// See http://goo.gl/8izrzI for more details // See https://goo.gl/1KSIb7 for more details
type CreateExecOptions struct { type CreateExecOptions struct {
AttachStdin bool `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty"` AttachStdin bool `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty"`
AttachStdout bool `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty"` AttachStdout bool `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty"`
@@ -28,9 +32,31 @@ type CreateExecOptions struct {
User string `json:"User,omitempty" yaml:"User,omitempty"` User string `json:"User,omitempty" yaml:"User,omitempty"`
} }
// CreateExec sets up an exec instance in a running container `id`, returning the exec
// instance, or an error in case of failure.
//
// See https://goo.gl/1KSIb7 for more details
func (c *Client) CreateExec(opts CreateExecOptions) (*Exec, error) {
path := fmt.Sprintf("/containers/%s/exec", opts.Container)
body, status, err := c.do("POST", path, doOptions{data: opts})
if status == http.StatusNotFound {
return nil, &NoSuchContainer{ID: opts.Container}
}
if err != nil {
return nil, err
}
var exec Exec
err = json.Unmarshal(body, &exec)
if err != nil {
return nil, err
}
return &exec, nil
}
// StartExecOptions specify parameters to the StartExecContainer function. // StartExecOptions specify parameters to the StartExecContainer function.
// //
// See http://goo.gl/JW8Lxl for more details // See https://goo.gl/iQCnto for more details
type StartExecOptions struct { type StartExecOptions struct {
Detach bool `json:"Detach,omitempty" yaml:"Detach,omitempty"` Detach bool `json:"Detach,omitempty" yaml:"Detach,omitempty"`
@@ -51,67 +77,11 @@ type StartExecOptions struct {
Success chan struct{} `json:"-"` Success chan struct{} `json:"-"`
} }
// Exec is the type representing a `docker exec` instance and containing the
// instance ID
type Exec struct {
ID string `json:"Id,omitempty" yaml:"Id,omitempty"`
}
// ExecProcessConfig is a type describing the command associated to a Exec
// instance. It's used in the ExecInspect type.
//
// See http://goo.gl/ypQULN for more details
type ExecProcessConfig struct {
Privileged bool `json:"privileged,omitempty" yaml:"privileged,omitempty"`
User string `json:"user,omitempty" yaml:"user,omitempty"`
Tty bool `json:"tty,omitempty" yaml:"tty,omitempty"`
EntryPoint string `json:"entrypoint,omitempty" yaml:"entrypoint,omitempty"`
Arguments []string `json:"arguments,omitempty" yaml:"arguments,omitempty"`
}
// ExecInspect is a type with details about a exec instance, including the
// exit code if the command has finished running. It's returned by a api
// call to /exec/(id)/json
//
// See http://goo.gl/ypQULN for more details
type ExecInspect struct {
ID string `json:"ID,omitempty" yaml:"ID,omitempty"`
Running bool `json:"Running,omitempty" yaml:"Running,omitempty"`
ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty"`
OpenStdin bool `json:"OpenStdin,omitempty" yaml:"OpenStdin,omitempty"`
OpenStderr bool `json:"OpenStderr,omitempty" yaml:"OpenStderr,omitempty"`
OpenStdout bool `json:"OpenStdout,omitempty" yaml:"OpenStdout,omitempty"`
ProcessConfig ExecProcessConfig `json:"ProcessConfig,omitempty" yaml:"ProcessConfig,omitempty"`
Container Container `json:"Container,omitempty" yaml:"Container,omitempty"`
}
// CreateExec sets up an exec instance in a running container `id`, returning the exec
// instance, or an error in case of failure.
//
// See http://goo.gl/8izrzI for more details
func (c *Client) CreateExec(opts CreateExecOptions) (*Exec, error) {
path := fmt.Sprintf("/containers/%s/exec", opts.Container)
body, status, err := c.do("POST", path, doOptions{data: opts})
if status == http.StatusNotFound {
return nil, &NoSuchContainer{ID: opts.Container}
}
if err != nil {
return nil, err
}
var exec Exec
err = json.Unmarshal(body, &exec)
if err != nil {
return nil, err
}
return &exec, nil
}
// StartExec starts a previously set up exec instance id. If opts.Detach is // StartExec starts a previously set up exec instance id. If opts.Detach is
// true, it returns after starting the exec command. Otherwise, it sets up an // true, it returns after starting the exec command. Otherwise, it sets up an
// interactive session with the exec command. // interactive session with the exec command.
// //
// See http://goo.gl/JW8Lxl for more details // See https://goo.gl/iQCnto for more details
func (c *Client) StartExec(id string, opts StartExecOptions) error { func (c *Client) StartExec(id string, opts StartExecOptions) error {
if id == "" { if id == "" {
return &NoSuchExec{ID: id} return &NoSuchExec{ID: id}
@@ -144,7 +114,7 @@ func (c *Client) StartExec(id string, opts StartExecOptions) error {
// is valid only if Tty was specified as part of creating and starting the exec // is valid only if Tty was specified as part of creating and starting the exec
// command. // command.
// //
// See http://goo.gl/YDSx1f for more details // See https://goo.gl/e1JpsA for more details
func (c *Client) ResizeExecTTY(id string, height, width int) error { func (c *Client) ResizeExecTTY(id string, height, width int) error {
params := make(url.Values) params := make(url.Values)
params.Set("h", strconv.Itoa(height)) params.Set("h", strconv.Itoa(height))
@@ -155,9 +125,35 @@ func (c *Client) ResizeExecTTY(id string, height, width int) error {
return err return err
} }
// ExecProcessConfig is a type describing the command associated to a Exec
// instance. It's used in the ExecInspect type.
type ExecProcessConfig struct {
Privileged bool `json:"privileged,omitempty" yaml:"privileged,omitempty"`
User string `json:"user,omitempty" yaml:"user,omitempty"`
Tty bool `json:"tty,omitempty" yaml:"tty,omitempty"`
EntryPoint string `json:"entrypoint,omitempty" yaml:"entrypoint,omitempty"`
Arguments []string `json:"arguments,omitempty" yaml:"arguments,omitempty"`
}
// ExecInspect is a type with details about a exec instance, including the
// exit code if the command has finished running. It's returned by a api
// call to /exec/(id)/json
//
// See https://goo.gl/gPtX9R for more details
type ExecInspect struct {
ID string `json:"ID,omitempty" yaml:"ID,omitempty"`
Running bool `json:"Running,omitempty" yaml:"Running,omitempty"`
ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty"`
OpenStdin bool `json:"OpenStdin,omitempty" yaml:"OpenStdin,omitempty"`
OpenStderr bool `json:"OpenStderr,omitempty" yaml:"OpenStderr,omitempty"`
OpenStdout bool `json:"OpenStdout,omitempty" yaml:"OpenStdout,omitempty"`
ProcessConfig ExecProcessConfig `json:"ProcessConfig,omitempty" yaml:"ProcessConfig,omitempty"`
Container Container `json:"Container,omitempty" yaml:"Container,omitempty"`
}
// InspectExec returns low-level information about the exec command id. // InspectExec returns low-level information about the exec command id.
// //
// See http://goo.gl/ypQULN for more details // See https://goo.gl/gPtX9R for more details
func (c *Client) InspectExec(id string) (*ExecInspect, error) { func (c *Client) InspectExec(id string) (*ExecInspect, error) {
path := fmt.Sprintf("/exec/%s/json", id) path := fmt.Sprintf("/exec/%s/json", id)
body, status, err := c.do("GET", path, doOptions{}) body, status, err := c.do("GET", path, doOptions{})

View File

@@ -11,7 +11,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
@@ -46,16 +45,6 @@ type Image struct {
VirtualSize int64 `json:"VirtualSize,omitempty" yaml:"VirtualSize,omitempty"` VirtualSize int64 `json:"VirtualSize,omitempty" yaml:"VirtualSize,omitempty"`
} }
// ImageHistory represent a layer in an image's history returned by the
// ImageHistory call.
type ImageHistory struct {
ID string `json:"Id" yaml:"Id"`
Tags []string `json:"Tags,omitempty" yaml:"Tags,omitempty"`
Created int64 `json:"Created,omitempty" yaml:"Created,omitempty"`
CreatedBy string `json:"CreatedBy,omitempty" yaml:"CreatedBy,omitempty"`
Size int64 `json:"Size,omitempty" yaml:"Size,omitempty"`
}
// ImagePre012 serves the same purpose as the Image type except that it is for // ImagePre012 serves the same purpose as the Image type except that it is for
// earlier versions of the Docker API (pre-012 to be specific) // earlier versions of the Docker API (pre-012 to be specific)
type ImagePre012 struct { type ImagePre012 struct {
@@ -72,15 +61,6 @@ type ImagePre012 struct {
Size int64 `json:"size,omitempty"` Size int64 `json:"size,omitempty"`
} }
// ListImagesOptions specify parameters to the ListImages function.
//
// See http://goo.gl/HRVN1Z for more details.
type ListImagesOptions struct {
All bool
Filters map[string][]string
Digests bool
}
var ( var (
// ErrNoSuchImage is the error returned when the image does not exist. // ErrNoSuchImage is the error returned when the image does not exist.
ErrNoSuchImage = errors.New("no such image") ErrNoSuchImage = errors.New("no such image")
@@ -102,9 +82,18 @@ var (
ErrMustSpecifyNames = errors.New("must specify at least one name to export") ErrMustSpecifyNames = errors.New("must specify at least one name to export")
) )
// ListImagesOptions specify parameters to the ListImages function.
//
// See https://goo.gl/xBe1u3 for more details.
type ListImagesOptions struct {
All bool
Filters map[string][]string
Digests bool
}
// ListImages returns the list of available images in the server. // ListImages returns the list of available images in the server.
// //
// See http://goo.gl/HRVN1Z for more details. // See https://goo.gl/xBe1u3 for more details.
func (c *Client) ListImages(opts ListImagesOptions) ([]APIImages, error) { func (c *Client) ListImages(opts ListImagesOptions) ([]APIImages, error) {
path := "/images/json?" + queryString(opts) path := "/images/json?" + queryString(opts)
body, _, err := c.do("GET", path, doOptions{}) body, _, err := c.do("GET", path, doOptions{})
@@ -119,9 +108,19 @@ func (c *Client) ListImages(opts ListImagesOptions) ([]APIImages, error) {
return images, nil return images, nil
} }
// ImageHistory represent a layer in an image's history returned by the
// ImageHistory call.
type ImageHistory struct {
ID string `json:"Id" yaml:"Id"`
Tags []string `json:"Tags,omitempty" yaml:"Tags,omitempty"`
Created int64 `json:"Created,omitempty" yaml:"Created,omitempty"`
CreatedBy string `json:"CreatedBy,omitempty" yaml:"CreatedBy,omitempty"`
Size int64 `json:"Size,omitempty" yaml:"Size,omitempty"`
}
// ImageHistory returns the history of the image by its name or ID. // ImageHistory returns the history of the image by its name or ID.
// //
// See http://goo.gl/2oJmNs for more details. // See https://goo.gl/8bnTId for more details.
func (c *Client) ImageHistory(name string) ([]ImageHistory, error) { func (c *Client) ImageHistory(name string) ([]ImageHistory, error) {
body, status, err := c.do("GET", "/images/"+name+"/history", doOptions{}) body, status, err := c.do("GET", "/images/"+name+"/history", doOptions{})
if status == http.StatusNotFound { if status == http.StatusNotFound {
@@ -140,7 +139,7 @@ func (c *Client) ImageHistory(name string) ([]ImageHistory, error) {
// RemoveImage removes an image by its name or ID. // RemoveImage removes an image by its name or ID.
// //
// See http://goo.gl/znj0wM for more details. // See https://goo.gl/V3ZWnK for more details.
func (c *Client) RemoveImage(name string) error { func (c *Client) RemoveImage(name string) error {
_, status, err := c.do("DELETE", "/images/"+name, doOptions{}) _, status, err := c.do("DELETE", "/images/"+name, doOptions{})
if status == http.StatusNotFound { if status == http.StatusNotFound {
@@ -152,7 +151,7 @@ func (c *Client) RemoveImage(name string) error {
// RemoveImageOptions present the set of options available for removing an image // RemoveImageOptions present the set of options available for removing an image
// from a registry. // from a registry.
// //
// See http://goo.gl/6V48bF for more details. // See https://goo.gl/V3ZWnK for more details.
type RemoveImageOptions struct { type RemoveImageOptions struct {
Force bool `qs:"force"` Force bool `qs:"force"`
NoPrune bool `qs:"noprune"` NoPrune bool `qs:"noprune"`
@@ -161,7 +160,7 @@ type RemoveImageOptions struct {
// RemoveImageExtended removes an image by its name or ID. // RemoveImageExtended removes an image by its name or ID.
// Extra params can be passed, see RemoveImageOptions // Extra params can be passed, see RemoveImageOptions
// //
// See http://goo.gl/znj0wM for more details. // See https://goo.gl/V3ZWnK for more details.
func (c *Client) RemoveImageExtended(name string, opts RemoveImageOptions) error { func (c *Client) RemoveImageExtended(name string, opts RemoveImageOptions) error {
uri := fmt.Sprintf("/images/%s?%s", name, queryString(&opts)) uri := fmt.Sprintf("/images/%s?%s", name, queryString(&opts))
_, status, err := c.do("DELETE", uri, doOptions{}) _, status, err := c.do("DELETE", uri, doOptions{})
@@ -173,7 +172,7 @@ func (c *Client) RemoveImageExtended(name string, opts RemoveImageOptions) error
// InspectImage returns an image by its name or ID. // InspectImage returns an image by its name or ID.
// //
// See http://goo.gl/Q112NY for more details. // See https://goo.gl/jHPcg6 for more details.
func (c *Client) InspectImage(name string) (*Image, error) { func (c *Client) InspectImage(name string) (*Image, error) {
body, status, err := c.do("GET", "/images/"+name+"/json", doOptions{}) body, status, err := c.do("GET", "/images/"+name+"/json", doOptions{})
if status == http.StatusNotFound { if status == http.StatusNotFound {
@@ -216,7 +215,7 @@ func (c *Client) InspectImage(name string) (*Image, error) {
// PushImageOptions represents options to use in the PushImage method. // PushImageOptions represents options to use in the PushImage method.
// //
// See http://goo.gl/pN8A3P for more details. // See https://goo.gl/zPtZaT for more details.
type PushImageOptions struct { type PushImageOptions struct {
// Name of the image // Name of the image
Name string Name string
@@ -236,7 +235,7 @@ type PushImageOptions struct {
// An empty instance of AuthConfiguration may be used for unauthenticated // An empty instance of AuthConfiguration may be used for unauthenticated
// pushes. // pushes.
// //
// See http://goo.gl/pN8A3P for more details. // See https://goo.gl/zPtZaT for more details.
func (c *Client) PushImage(opts PushImageOptions, auth AuthConfiguration) error { func (c *Client) PushImage(opts PushImageOptions, auth AuthConfiguration) error {
if opts.Name == "" { if opts.Name == "" {
return ErrNoSuchImage return ErrNoSuchImage
@@ -259,7 +258,7 @@ func (c *Client) PushImage(opts PushImageOptions, auth AuthConfiguration) error
// PullImageOptions present the set of options available for pulling an image // PullImageOptions present the set of options available for pulling an image
// from a registry. // from a registry.
// //
// See http://goo.gl/ACyYNS for more details. // See https://goo.gl/iJkZjD for more details.
type PullImageOptions struct { type PullImageOptions struct {
Repository string `qs:"fromImage"` Repository string `qs:"fromImage"`
Registry string Registry string
@@ -268,9 +267,10 @@ type PullImageOptions struct {
RawJSONStream bool `qs:"-"` RawJSONStream bool `qs:"-"`
} }
// PullImage pulls an image from a remote registry, logging progress to opts.OutputStream. // PullImage pulls an image from a remote registry, logging progress to
// opts.OutputStream.
// //
// See http://goo.gl/ACyYNS for more details. // See https://goo.gl/iJkZjD for more details.
func (c *Client) PullImage(opts PullImageOptions, auth AuthConfiguration) error { func (c *Client) PullImage(opts PullImageOptions, auth AuthConfiguration) error {
if opts.Repository == "" { if opts.Repository == "" {
return ErrNoSuchImage return ErrNoSuchImage
@@ -296,14 +296,14 @@ func (c *Client) createImage(qs string, headers map[string]string, in io.Reader,
// LoadImageOptions represents the options for LoadImage Docker API Call // LoadImageOptions represents the options for LoadImage Docker API Call
// //
// See http://goo.gl/Y8NNCq for more details. // See https://goo.gl/JyClMX for more details.
type LoadImageOptions struct { type LoadImageOptions struct {
InputStream io.Reader InputStream io.Reader
} }
// LoadImage imports a tarball docker image // LoadImage imports a tarball docker image
// //
// See http://goo.gl/Y8NNCq for more details. // See https://goo.gl/JyClMX for more details.
func (c *Client) LoadImage(opts LoadImageOptions) error { func (c *Client) LoadImage(opts LoadImageOptions) error {
return c.stream("POST", "/images/load", streamOptions{ return c.stream("POST", "/images/load", streamOptions{
setRawTerminal: true, setRawTerminal: true,
@@ -311,17 +311,17 @@ func (c *Client) LoadImage(opts LoadImageOptions) error {
}) })
} }
// ExportImageOptions represent the options for ExportImage Docker API call // ExportImageOptions represent the options for ExportImage Docker API call.
// //
// See http://goo.gl/mi6kvk for more details. // See https://goo.gl/le7vK8 for more details.
type ExportImageOptions struct { type ExportImageOptions struct {
Name string Name string
OutputStream io.Writer OutputStream io.Writer
} }
// ExportImage exports an image (as a tar file) into the stream // ExportImage exports an image (as a tar file) into the stream.
// //
// See http://goo.gl/mi6kvk for more details. // See https://goo.gl/le7vK8 for more details.
func (c *Client) ExportImage(opts ExportImageOptions) error { func (c *Client) ExportImage(opts ExportImageOptions) error {
return c.stream("GET", fmt.Sprintf("/images/%s/get", opts.Name), streamOptions{ return c.stream("GET", fmt.Sprintf("/images/%s/get", opts.Name), streamOptions{
setRawTerminal: true, setRawTerminal: true,
@@ -331,7 +331,7 @@ func (c *Client) ExportImage(opts ExportImageOptions) error {
// ExportImagesOptions represent the options for ExportImages Docker API call // ExportImagesOptions represent the options for ExportImages Docker API call
// //
// See http://goo.gl/YeZzQK for more details. // See https://goo.gl/huC7HA for more details.
type ExportImagesOptions struct { type ExportImagesOptions struct {
Names []string Names []string
OutputStream io.Writer `qs:"-"` OutputStream io.Writer `qs:"-"`
@@ -339,7 +339,7 @@ type ExportImagesOptions struct {
// ExportImages exports one or more images (as a tar file) into the stream // ExportImages exports one or more images (as a tar file) into the stream
// //
// See http://goo.gl/YeZzQK for more details. // See https://goo.gl/huC7HA for more details.
func (c *Client) ExportImages(opts ExportImagesOptions) error { func (c *Client) ExportImages(opts ExportImagesOptions) error {
if opts.Names == nil || len(opts.Names) == 0 { if opts.Names == nil || len(opts.Names) == 0 {
return ErrMustSpecifyNames return ErrMustSpecifyNames
@@ -353,7 +353,7 @@ func (c *Client) ExportImages(opts ExportImagesOptions) error {
// ImportImageOptions present the set of informations available for importing // ImportImageOptions present the set of informations available for importing
// an image from a source file or the stdin. // an image from a source file or the stdin.
// //
// See http://goo.gl/PhBKnS for more details. // See https://goo.gl/iJkZjD for more details.
type ImportImageOptions struct { type ImportImageOptions struct {
Repository string `qs:"repo"` Repository string `qs:"repo"`
Source string `qs:"fromSrc"` Source string `qs:"fromSrc"`
@@ -366,7 +366,7 @@ type ImportImageOptions struct {
// ImportImage imports an image from a url, a file or stdin // ImportImage imports an image from a url, a file or stdin
// //
// See http://goo.gl/PhBKnS for more details. // See https://goo.gl/iJkZjD for more details.
func (c *Client) ImportImage(opts ImportImageOptions) error { func (c *Client) ImportImage(opts ImportImageOptions) error {
if opts.Repository == "" { if opts.Repository == "" {
return ErrNoSuchImage return ErrNoSuchImage
@@ -379,8 +379,7 @@ func (c *Client) ImportImage(opts ImportImageOptions) error {
if err != nil { if err != nil {
return err return err
} }
b, err := ioutil.ReadAll(f) opts.InputStream = f
opts.InputStream = bytes.NewBuffer(b)
opts.Source = "-" opts.Source = "-"
} }
return c.createImage(queryString(&opts), nil, opts.InputStream, opts.OutputStream, opts.RawJSONStream) return c.createImage(queryString(&opts), nil, opts.InputStream, opts.OutputStream, opts.RawJSONStream)
@@ -415,12 +414,12 @@ type BuildImageOptions struct {
// BuildImage builds an image from a tarball's url or a Dockerfile in the input // BuildImage builds an image from a tarball's url or a Dockerfile in the input
// stream. // stream.
// //
// See http://goo.gl/7nuGXa for more details. // See https://goo.gl/xySxCe for more details.
func (c *Client) BuildImage(opts BuildImageOptions) error { func (c *Client) BuildImage(opts BuildImageOptions) error {
if opts.OutputStream == nil { if opts.OutputStream == nil {
return ErrMissingOutputStream return ErrMissingOutputStream
} }
headers, err := headersWithAuth(opts.Auth, opts.AuthConfigs) headers, err := headersWithAuth(opts.Auth, c.versionedAuthConfigs(opts.AuthConfigs))
if err != nil { if err != nil {
return err return err
} }
@@ -452,9 +451,19 @@ func (c *Client) BuildImage(opts BuildImageOptions) error {
}) })
} }
func (c *Client) versionedAuthConfigs(authConfigs AuthConfigurations) interface{} {
if c.serverAPIVersion == nil {
c.checkAPIVersion()
}
if c.serverAPIVersion != nil && c.serverAPIVersion.GreaterThanOrEqualTo(apiVersion119) {
return AuthConfigurations119(authConfigs.Configs)
}
return authConfigs
}
// TagImageOptions present the set of options to tag an image. // TagImageOptions present the set of options to tag an image.
// //
// See http://goo.gl/5g6qFy for more details. // See https://goo.gl/98ZzkU for more details.
type TagImageOptions struct { type TagImageOptions struct {
Repo string Repo string
Tag string Tag string
@@ -463,7 +472,7 @@ type TagImageOptions struct {
// TagImage adds a tag to the image identified by the given name. // TagImage adds a tag to the image identified by the given name.
// //
// See http://goo.gl/5g6qFy for more details. // See https://goo.gl/98ZzkU for more details.
func (c *Client) TagImage(name string, opts TagImageOptions) error { func (c *Client) TagImage(name string, opts TagImageOptions) error {
if name == "" { if name == "" {
return ErrNoSuchImage return ErrNoSuchImage
@@ -497,7 +506,7 @@ func headersWithAuth(auths ...interface{}) (map[string]string, error) {
return nil, err return nil, err
} }
headers["X-Registry-Auth"] = base64.URLEncoding.EncodeToString(buf.Bytes()) headers["X-Registry-Auth"] = base64.URLEncoding.EncodeToString(buf.Bytes())
case AuthConfigurations: case AuthConfigurations, AuthConfigurations119:
var buf bytes.Buffer var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(auth); err != nil { if err := json.NewEncoder(&buf).Encode(auth); err != nil {
return nil, err return nil, err
@@ -509,9 +518,9 @@ func headersWithAuth(auths ...interface{}) (map[string]string, error) {
return headers, nil return headers, nil
} }
// APIImageSearch reflect the result of a search on the dockerHub // APIImageSearch reflect the result of a search on the Docker Hub.
// //
// See http://goo.gl/xI5lLZ for more details. // See https://goo.gl/AYjyrF for more details.
type APIImageSearch struct { type APIImageSearch struct {
Description string `json:"description,omitempty" yaml:"description,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"`
IsOfficial bool `json:"is_official,omitempty" yaml:"is_official,omitempty"` IsOfficial bool `json:"is_official,omitempty" yaml:"is_official,omitempty"`
@@ -522,7 +531,7 @@ type APIImageSearch struct {
// SearchImages search the docker hub with a specific given term. // SearchImages search the docker hub with a specific given term.
// //
// See http://goo.gl/xI5lLZ for more details. // See https://goo.gl/AYjyrF for more details.
func (c *Client) SearchImages(term string) ([]APIImageSearch, error) { func (c *Client) SearchImages(term string) ([]APIImageSearch, error) {
body, _, err := c.do("GET", "/images/search?term="+term, doOptions{}) body, _, err := c.do("GET", "/images/search?term="+term, doOptions{})
if err != nil { if err != nil {

View File

@@ -21,11 +21,13 @@ import (
func newTestClient(rt *FakeRoundTripper) Client { func newTestClient(rt *FakeRoundTripper) Client {
endpoint := "http://localhost:4243" endpoint := "http://localhost:4243"
u, _ := parseEndpoint("http://localhost:4243", false) u, _ := parseEndpoint("http://localhost:4243", false)
testAPIVersion, _ := NewAPIVersion("1.17")
client := Client{ client := Client{
HTTPClient: &http.Client{Transport: rt}, HTTPClient: &http.Client{Transport: rt},
endpoint: endpoint, endpoint: endpoint,
endpointURL: u, endpointURL: u,
SkipServerVersionCheck: true, SkipServerVersionCheck: true,
serverAPIVersion: testAPIVersion,
} }
return client return client
} }

View File

@@ -11,7 +11,7 @@ import (
// Version returns version information about the docker server. // Version returns version information about the docker server.
// //
// See http://goo.gl/BOZrF5 for more details. // See https://goo.gl/ND9R8L for more details.
func (c *Client) Version() (*Env, error) { func (c *Client) Version() (*Env, error) {
body, _, err := c.do("GET", "/version", doOptions{}) body, _, err := c.do("GET", "/version", doOptions{})
if err != nil { if err != nil {
@@ -26,7 +26,7 @@ func (c *Client) Version() (*Env, error) {
// Info returns system-wide information about the Docker server. // Info returns system-wide information about the Docker server.
// //
// See http://goo.gl/wmqZsW for more details. // See https://goo.gl/ElTHi2 for more details.
func (c *Client) Info() (*Env, error) { func (c *Client) Info() (*Env, error) {
body, _, err := c.do("GET", "/info", doOptions{}) body, _, err := c.do("GET", "/info", doOptions{})
if err != nil { if err != nil {

View File

@@ -12,6 +12,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io/ioutil"
mathrand "math/rand" mathrand "math/rand"
"net" "net"
"net/http" "net/http"
@@ -532,7 +533,7 @@ func (s *DockerServer) startContainer(w http.ResponseWriter, r *http.Request) {
} }
container.HostConfig = &hostConfig container.HostConfig = &hostConfig
if container.State.Running { if container.State.Running {
http.Error(w, "Container already running", http.StatusBadRequest) http.Error(w, "", http.StatusNotModified)
return return
} }
container.State.Running = true container.State.Running = true
@@ -610,14 +611,34 @@ func (s *DockerServer) attachContainer(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
wg := sync.WaitGroup{}
if r.URL.Query().Get("stdin") == "1" {
wg.Add(1)
go func() {
ioutil.ReadAll(conn)
wg.Done()
}()
}
outStream := stdcopy.NewStdWriter(conn, stdcopy.Stdout) outStream := stdcopy.NewStdWriter(conn, stdcopy.Stdout)
if container.State.Running { if container.State.Running {
fmt.Fprintf(outStream, "Container %q is running\n", container.ID) fmt.Fprintf(outStream, "Container is running\n")
} else { } else {
fmt.Fprintf(outStream, "Container %q is not running\n", container.ID) fmt.Fprintf(outStream, "Container is not running\n")
} }
fmt.Fprintln(outStream, "What happened?") fmt.Fprintln(outStream, "What happened?")
fmt.Fprintln(outStream, "Something happened") fmt.Fprintln(outStream, "Something happened")
wg.Wait()
if r.URL.Query().Get("stream") == "1" {
for {
time.Sleep(1e6)
s.cMut.RLock()
if !container.State.Running {
s.cMut.RUnlock()
break
}
s.cMut.RUnlock()
}
}
conn.Close() conn.Close()
} }

View File

@@ -5,9 +5,11 @@
package testing package testing
import ( import (
"bufio"
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil"
"math/rand" "math/rand"
"net" "net"
"net/http" "net/http"
@@ -624,8 +626,8 @@ func TestStartContainerAlreadyRunning(t *testing.T) {
path := fmt.Sprintf("/containers/%s/start", server.containers[0].ID) path := fmt.Sprintf("/containers/%s/start", server.containers[0].ID)
request, _ := http.NewRequest("POST", path, bytes.NewBuffer([]byte("null"))) request, _ := http.NewRequest("POST", path, bytes.NewBuffer([]byte("null")))
server.ServeHTTP(recorder, request) server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusBadRequest { if recorder.Code != http.StatusNotModified {
t.Errorf("StartContainer: wrong status code. Want %d. Got %d.", http.StatusBadRequest, recorder.Code) t.Errorf("StartContainer: wrong status code. Want %d. Got %d.", http.StatusNotModified, recorder.Code)
} }
} }
@@ -845,22 +847,41 @@ func TestWaitContainerNotFound(t *testing.T) {
} }
} }
type HijackableResponseRecorder struct {
httptest.ResponseRecorder
readCh chan []byte
}
func (r *HijackableResponseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
myConn, otherConn := net.Pipe()
r.readCh = make(chan []byte)
go func() {
data, _ := ioutil.ReadAll(myConn)
r.readCh <- data
}()
return otherConn, nil, nil
}
func (r *HijackableResponseRecorder) HijackBuffer() string {
return string(<-r.readCh)
}
func TestAttachContainer(t *testing.T) { func TestAttachContainer(t *testing.T) {
server := DockerServer{} server := DockerServer{}
addContainers(&server, 1) addContainers(&server, 1)
server.containers[0].State.Running = true server.containers[0].State.Running = true
server.buildMuxer() server.buildMuxer()
recorder := httptest.NewRecorder() recorder := &HijackableResponseRecorder{}
path := fmt.Sprintf("/containers/%s/attach?logs=1", server.containers[0].ID) path := fmt.Sprintf("/containers/%s/attach?logs=1", server.containers[0].ID)
request, _ := http.NewRequest("POST", path, nil) request, _ := http.NewRequest("POST", path, nil)
server.ServeHTTP(recorder, request) server.ServeHTTP(recorder, request)
lines := []string{ lines := []string{
fmt.Sprintf("\x01\x00\x00\x00\x03\x00\x00\x00Container %q is running", server.containers[0].ID), "\x01\x00\x00\x00\x00\x00\x00\x15Container is running",
"What happened?", "\x01\x00\x00\x00\x00\x00\x00\x0fWhat happened?",
"Something happened", "\x01\x00\x00\x00\x00\x00\x00\x13Something happened",
} }
expected := strings.Join(lines, "\n") + "\n" expected := strings.Join(lines, "\n") + "\n"
if body := recorder.Body.String(); body == expected { if body := recorder.HijackBuffer(); body != expected {
t.Errorf("AttachContainer: wrong body. Want %q. Got %q.", expected, body) t.Errorf("AttachContainer: wrong body. Want %q. Got %q.", expected, body)
} }
} }
@@ -868,7 +889,7 @@ func TestAttachContainer(t *testing.T) {
func TestAttachContainerNotFound(t *testing.T) { func TestAttachContainerNotFound(t *testing.T) {
server := DockerServer{} server := DockerServer{}
server.buildMuxer() server.buildMuxer()
recorder := httptest.NewRecorder() recorder := &HijackableResponseRecorder{}
path := "/containers/abc123/attach?logs=1" path := "/containers/abc123/attach?logs=1"
request, _ := http.NewRequest("POST", path, nil) request, _ := http.NewRequest("POST", path, nil)
server.ServeHTTP(recorder, request) server.ServeHTTP(recorder, request)
@@ -877,6 +898,44 @@ func TestAttachContainerNotFound(t *testing.T) {
} }
} }
func TestAttachContainerWithStreamBlocks(t *testing.T) {
server := DockerServer{}
addContainers(&server, 1)
server.containers[0].State.Running = true
server.buildMuxer()
path := fmt.Sprintf("/containers/%s/attach?logs=1&stdout=1&stream=1", server.containers[0].ID)
request, _ := http.NewRequest("POST", path, nil)
done := make(chan string)
go func() {
recorder := &HijackableResponseRecorder{}
server.ServeHTTP(recorder, request)
done <- recorder.HijackBuffer()
}()
select {
case <-done:
t.Fatalf("attach stream returned before container is stopped")
case <-time.After(500 * time.Millisecond):
}
server.cMut.Lock()
server.containers[0].State.Running = false
server.cMut.Unlock()
var body string
select {
case body = <-done:
case <-time.After(5 * time.Second):
t.Fatalf("timed out waiting for attach to finish")
}
lines := []string{
"\x01\x00\x00\x00\x00\x00\x00\x15Container is running",
"\x01\x00\x00\x00\x00\x00\x00\x0fWhat happened?",
"\x01\x00\x00\x00\x00\x00\x00\x13Something happened",
}
expected := strings.Join(lines, "\n") + "\n"
if body != expected {
t.Errorf("AttachContainer: wrong body. Want %q. Got %q.", expected, body)
}
}
func TestRemoveContainer(t *testing.T) { func TestRemoveContainer(t *testing.T) {
server := DockerServer{} server := DockerServer{}
addContainers(&server, 1) addContainers(&server, 1)
@@ -1690,7 +1749,7 @@ func addNetworks(server *DockerServer, n int) {
ID: fmt.Sprintf("%x", rand.Int()%10000), ID: fmt.Sprintf("%x", rand.Int()%10000),
Type: "bridge", Type: "bridge",
Endpoints: []*docker.Endpoint{ Endpoints: []*docker.Endpoint{
&docker.Endpoint{ {
Name: "blah", Name: "blah",
ID: fmt.Sprintf("%x", rand.Int()%10000), ID: fmt.Sprintf("%x", rand.Int()%10000),
Network: netid, Network: netid,

View File

@@ -0,0 +1,118 @@
// 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"
"net/http"
)
var (
// ErrNoSuchVolume is the error returned when the volume does not exist.
ErrNoSuchVolume = errors.New("no such volume")
// ErrVolumeInUse is the error returned when the volume requested to be removed is still in use.
ErrVolumeInUse = errors.New("volume in use and cannot be removed")
)
// Volume represents a volume.
//
// See https://goo.gl/FZA4BK for more details.
type Volume struct {
Name string `json:"Name" yaml:"Name"`
Driver string `json:"Driver,omitempty" yaml:"Driver,omitempty"`
Mountpoint string `json:"Mountpoint,omitempty" yaml:"Mountpoint,omitempty"`
}
// ListVolumesOptions specify parameters to the ListVolumes function.
//
// See https://goo.gl/FZA4BK for more details.
type ListVolumesOptions struct {
Filters map[string][]string
}
// ListVolumes returns a list of available volumes in the server.
//
// See https://goo.gl/FZA4BK for more details.
func (c *Client) ListVolumes(opts ListVolumesOptions) ([]Volume, error) {
body, _, err := c.do("GET", "/volumes?"+queryString(opts), doOptions{})
if err != nil {
return nil, err
}
m := make(map[string]interface{})
if err := json.Unmarshal(body, &m); err != nil {
return nil, err
}
var volumes []Volume
volumesJSON, ok := m["Volumes"]
if !ok {
return volumes, nil
}
data, err := json.Marshal(volumesJSON)
if err != nil {
return nil, err
}
if err := json.Unmarshal(data, &volumes); err != nil {
return nil, err
}
return volumes, nil
}
// CreateVolumeOptions specify parameters to the CreateVolume function.
//
// See https://goo.gl/pBUbZ9 for more details.
type CreateVolumeOptions struct {
Name string
Driver string
DriverOpts map[string]string
}
// CreateVolume creates a volume on the server.
//
// See https://goo.gl/pBUbZ9 for more details.
func (c *Client) CreateVolume(opts CreateVolumeOptions) (*Volume, error) {
body, _, err := c.do("POST", "/volumes", doOptions{data: opts})
if err != nil {
return nil, err
}
var volume Volume
if err := json.Unmarshal(body, &volume); err != nil {
return nil, err
}
return &volume, nil
}
// InspectVolume returns a volume by its name.
//
// See https://goo.gl/0g9A6i for more details.
func (c *Client) InspectVolume(name string) (*Volume, error) {
body, status, err := c.do("GET", "/volumes/"+name, doOptions{})
if status == http.StatusNotFound {
return nil, ErrNoSuchVolume
}
if err != nil {
return nil, err
}
var volume Volume
if err := json.Unmarshal(body, &volume); err != nil {
return nil, err
}
return &volume, nil
}
// RemoveVolume removes a volume by its name.
//
// See https://goo.gl/79GNQz for more details.
func (c *Client) RemoveVolume(name string) error {
_, status, err := c.do("DELETE", "/volumes/"+name, doOptions{})
if status == http.StatusNotFound {
return ErrNoSuchVolume
}
if status == http.StatusConflict {
return ErrVolumeInUse
}
return err
}

View File

@@ -0,0 +1,142 @@
// 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"
"net/http"
"net/url"
"reflect"
"testing"
)
func TestListVolumes(t *testing.T) {
volumesData := `[
{
"Name": "tardis",
"Driver": "local",
"Mountpoint": "/var/lib/docker/volumes/tardis"
},
{
"Name": "foo",
"Driver": "bar",
"Mountpoint": "/var/lib/docker/volumes/bar"
}
]`
body := `{ "Volumes": ` + volumesData + ` }`
var expected []Volume
if err := json.Unmarshal([]byte(volumesData), &expected); err != nil {
t.Fatal(err)
}
client := newTestClient(&FakeRoundTripper{message: body, status: http.StatusOK})
volumes, err := client.ListVolumes(ListVolumesOptions{})
if err != nil {
t.Error(err)
}
if !reflect.DeepEqual(volumes, expected) {
t.Errorf("ListVolumes: Wrong return value. Want %#v. Got %#v.", expected, volumes)
}
}
func TestCreateVolume(t *testing.T) {
body := `{
"Name": "tardis",
"Driver": "local",
"Mountpoint": "/var/lib/docker/volumes/tardis"
}`
var expected Volume
if err := json.Unmarshal([]byte(body), &expected); err != nil {
t.Fatal(err)
}
fakeRT := &FakeRoundTripper{message: body, status: http.StatusOK}
client := newTestClient(fakeRT)
volume, err := client.CreateVolume(
CreateVolumeOptions{
Name: "tardis",
Driver: "local",
DriverOpts: map[string]string{
"foo": "bar",
},
},
)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(volume, &expected) {
t.Errorf("CreateVolume: Wrong return value. Want %#v. Got %#v.", expected, volume)
}
req := fakeRT.requests[0]
expectedMethod := "POST"
if req.Method != expectedMethod {
t.Errorf("CreateVolume(): Wrong HTTP method. Want %s. Got %s.", expectedMethod, req.Method)
}
u, _ := url.Parse(client.getURL("/volumes"))
if req.URL.Path != u.Path {
t.Errorf("CreateVolume(): Wrong request path. Want %q. Got %q.", u.Path, req.URL.Path)
}
}
func TestInspectVolume(t *testing.T) {
body := `{
"Name": "tardis",
"Driver": "local",
"Mountpoint": "/var/lib/docker/volumes/tardis"
}`
var expected Volume
if err := json.Unmarshal([]byte(body), &expected); err != nil {
t.Fatal(err)
}
fakeRT := &FakeRoundTripper{message: body, status: http.StatusOK}
client := newTestClient(fakeRT)
name := "tardis"
volume, err := client.InspectVolume(name)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(volume, &expected) {
t.Errorf("InspectVolume: Wrong return value. Want %#v. Got %#v.", expected, volume)
}
req := fakeRT.requests[0]
expectedMethod := "GET"
if req.Method != expectedMethod {
t.Errorf("InspectVolume(%q): Wrong HTTP method. Want %s. Got %s.", name, expectedMethod, req.Method)
}
u, _ := url.Parse(client.getURL("/volumes/" + name))
if req.URL.Path != u.Path {
t.Errorf("CreateVolume(%q): Wrong request path. Want %q. Got %q.", name, u.Path, req.URL.Path)
}
}
func TestRemoveVolume(t *testing.T) {
name := "test"
fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent}
client := newTestClient(fakeRT)
if err := client.RemoveVolume(name); err != nil {
t.Fatal(err)
}
req := fakeRT.requests[0]
expectedMethod := "DELETE"
if req.Method != expectedMethod {
t.Errorf("RemoveVolume(%q): Wrong HTTP method. Want %s. Got %s.", name, expectedMethod, req.Method)
}
u, _ := url.Parse(client.getURL("/volumes/" + name))
if req.URL.Path != u.Path {
t.Errorf("RemoveVolume(%q): Wrong request path. Want %q. Got %q.", name, u.Path, req.URL.Path)
}
}
func TestRemoveVolumeNotFound(t *testing.T) {
client := newTestClient(&FakeRoundTripper{message: "no such volume", status: http.StatusNotFound})
if err := client.RemoveVolume("test:"); err != ErrNoSuchVolume {
t.Errorf("RemoveVolume: wrong error. Want %#v. Got %#v.", ErrNoSuchVolume, err)
}
}
func TestRemoveVolumeInUse(t *testing.T) {
client := newTestClient(&FakeRoundTripper{message: "volume in use and cannot be removed", status: http.StatusConflict})
if err := client.RemoveVolume("test:"); err != ErrVolumeInUse {
t.Errorf("RemoveVolume: wrong error. Want %#v. Got %#v.", ErrVolumeInUse, err)
}
}