Update godep of go-dockerclient for Label access
This commit is contained in:
parent
12420db4b9
commit
1d741cbfc5
|
@ -60,7 +60,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/fsouza/go-dockerclient",
|
"ImportPath": "github.com/fsouza/go-dockerclient",
|
||||||
"Rev": "42d06e2b125654477366c320dcea99107a86e9c2"
|
"Rev": "af9789bbd78acf3e279274caa54682185eb7ed33"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/go-sql-driver/mysql",
|
"ImportPath": "github.com/go-sql-driver/mysql",
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
# temporary symlink for testing
|
||||||
|
testing/data/symlink
|
|
@ -1,8 +1,9 @@
|
||||||
language: go
|
language: go
|
||||||
sudo: false
|
sudo: false
|
||||||
go:
|
go:
|
||||||
- 1.3.1
|
- 1.3.3
|
||||||
- 1.4
|
- 1.4.2
|
||||||
|
- 1.5.1
|
||||||
- tip
|
- tip
|
||||||
env:
|
env:
|
||||||
- GOARCH=amd64
|
- GOARCH=amd64
|
||||||
|
|
|
@ -11,8 +11,10 @@ Ben McCann <benmccann.com>
|
||||||
Brendan Fosberry <brendan@codeship.com>
|
Brendan Fosberry <brendan@codeship.com>
|
||||||
Brian Lalor <blalor@bravo5.org>
|
Brian Lalor <blalor@bravo5.org>
|
||||||
Brian Palmer <brianp@instructure.com>
|
Brian Palmer <brianp@instructure.com>
|
||||||
|
Bryan Boreham <bjboreham@gmail.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>
|
||||||
|
@ -27,12 +29,14 @@ David Huie <dahuie@gmail.com>
|
||||||
Dawn Chen <dawnchen@google.com>
|
Dawn Chen <dawnchen@google.com>
|
||||||
Dinesh Subhraveti <dinesh@gemini-systems.net>
|
Dinesh Subhraveti <dinesh@gemini-systems.net>
|
||||||
Ed <edrocksit@gmail.com>
|
Ed <edrocksit@gmail.com>
|
||||||
|
Erez Horev <erez.horev@elastifile.com>
|
||||||
Eric Anderson <anderson@copperegg.com>
|
Eric Anderson <anderson@copperegg.com>
|
||||||
Ewout Prangsma <ewout@prangsma.net>
|
Ewout Prangsma <ewout@prangsma.net>
|
||||||
Fabio Rehm <fgrehm@gmail.com>
|
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>
|
||||||
|
@ -43,6 +47,7 @@ Jawher Moussa <jawher.moussa@gmail.com>
|
||||||
Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
|
Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
|
||||||
Jeff Mitchell <jeffrey.mitchell@gmail.com>
|
Jeff Mitchell <jeffrey.mitchell@gmail.com>
|
||||||
Jeffrey Hulten <jhulten@gmail.com>
|
Jeffrey Hulten <jhulten@gmail.com>
|
||||||
|
Jen Andre <jandre@gmail.com>
|
||||||
Johan Euphrosine <proppy@google.com>
|
Johan Euphrosine <proppy@google.com>
|
||||||
Kamil Domanski <kamil@domanski.co>
|
Kamil Domanski <kamil@domanski.co>
|
||||||
Karan Misra <kidoman@gmail.com>
|
Karan Misra <kidoman@gmail.com>
|
||||||
|
@ -50,6 +55,7 @@ Kim, Hirokuni <hirokuni.kim@kvh.co.jp>
|
||||||
Kyle Allan <kallan357@gmail.com>
|
Kyle Allan <kallan357@gmail.com>
|
||||||
Liron Levin <levinlir@gmail.com>
|
Liron Levin <levinlir@gmail.com>
|
||||||
Liu Peng <vslene@gmail.com>
|
Liu Peng <vslene@gmail.com>
|
||||||
|
Lorenz Leutgeb <lorenz.leutgeb@gmail.com>
|
||||||
Lucas Clemente <lucas@clemente.io>
|
Lucas Clemente <lucas@clemente.io>
|
||||||
Lucas Weiblen <lucasweiblen@gmail.com>
|
Lucas Weiblen <lucasweiblen@gmail.com>
|
||||||
Mantas Matelis <mmatelis@coursera.org>
|
Mantas Matelis <mmatelis@coursera.org>
|
||||||
|
@ -66,12 +72,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>
|
||||||
|
|
|
@ -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,17 +122,15 @@ 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")
|
||||||
}
|
}
|
||||||
body, statusCode, err := c.do("POST", "/auth", doOptions{data: conf})
|
resp, err := c.do("POST", "/auth", doOptions{data: conf})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if statusCode > 400 {
|
resp.Body.Close()
|
||||||
return fmt.Errorf("auth error (%d): %s", statusCode, body)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -35,6 +35,16 @@ func TestBuildImageMultipleContextsError(t *testing.T) {
|
||||||
func TestBuildImageContextDirDockerignoreParsing(t *testing.T) {
|
func TestBuildImageContextDirDockerignoreParsing(t *testing.T) {
|
||||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||||
client := newTestClient(fakeRT)
|
client := newTestClient(fakeRT)
|
||||||
|
|
||||||
|
if err := os.Symlink("doesnotexist", "testing/data/symlink"); err != nil {
|
||||||
|
t.Errorf("error creating symlink on demand: %s", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := os.Remove("testing/data/symlink"); err != nil {
|
||||||
|
t.Errorf("error removing symlink on demand: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
opts := BuildImageOptions{
|
opts := BuildImageOptions{
|
||||||
Name: "testImage",
|
Name: "testImage",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
@ -128,6 +130,7 @@ type Client struct {
|
||||||
SkipServerVersionCheck bool
|
SkipServerVersionCheck bool
|
||||||
HTTPClient *http.Client
|
HTTPClient *http.Client
|
||||||
TLSConfig *tls.Config
|
TLSConfig *tls.Config
|
||||||
|
Dialer *net.Dialer
|
||||||
|
|
||||||
endpoint string
|
endpoint string
|
||||||
endpointURL *url.URL
|
endpointURL *url.URL
|
||||||
|
@ -135,6 +138,7 @@ type Client struct {
|
||||||
requestedAPIVersion APIVersion
|
requestedAPIVersion APIVersion
|
||||||
serverAPIVersion APIVersion
|
serverAPIVersion APIVersion
|
||||||
expectedAPIVersion APIVersion
|
expectedAPIVersion APIVersion
|
||||||
|
unixHTTPClient *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient returns a Client instance ready for communication with the given
|
// NewClient returns a Client instance ready for communication with the given
|
||||||
|
@ -189,6 +193,7 @@ func NewVersionedClient(endpoint string, apiVersionString string) (*Client, erro
|
||||||
}
|
}
|
||||||
return &Client{
|
return &Client{
|
||||||
HTTPClient: http.DefaultClient,
|
HTTPClient: http.DefaultClient,
|
||||||
|
Dialer: &net.Dialer{},
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
endpointURL: u,
|
endpointURL: u,
|
||||||
eventMonitor: new(eventMonitoringState),
|
eventMonitor: new(eventMonitoringState),
|
||||||
|
@ -300,6 +305,7 @@ func NewVersionedTLSClientFromBytes(endpoint string, certPEMBlock, keyPEMBlock,
|
||||||
return &Client{
|
return &Client{
|
||||||
HTTPClient: &http.Client{Transport: tr},
|
HTTPClient: &http.Client{Transport: tr},
|
||||||
TLSConfig: tlsConfig,
|
TLSConfig: tlsConfig,
|
||||||
|
Dialer: &net.Dialer{},
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
endpointURL: u,
|
endpointURL: u,
|
||||||
eventMonitor: new(eventMonitoringState),
|
eventMonitor: new(eventMonitoringState),
|
||||||
|
@ -324,32 +330,40 @@ func (c *Client) checkAPIVersion() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Endpoint returns the current endpoint. It's useful for getting the endpoint
|
||||||
|
// when using functions that get this data from the environment (like
|
||||||
|
// NewClientFromEnv.
|
||||||
|
func (c *Client) Endpoint() string {
|
||||||
|
return c.endpoint
|
||||||
|
}
|
||||||
|
|
||||||
// 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{})
|
resp, err := c.do("GET", path, doOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if status != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return newError(status, body)
|
return newError(resp)
|
||||||
}
|
}
|
||||||
|
resp.Body.Close()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) getServerAPIVersionString() (version string, err error) {
|
func (c *Client) getServerAPIVersionString() (version string, err error) {
|
||||||
body, status, err := c.do("GET", "/version", doOptions{})
|
resp, err := c.do("GET", "/version", doOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if status != http.StatusOK {
|
defer resp.Body.Close()
|
||||||
return "", fmt.Errorf("Received unexpected status %d while trying to retrieve the server version", status)
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return "", fmt.Errorf("Received unexpected status %d while trying to retrieve the server version", resp.StatusCode)
|
||||||
}
|
}
|
||||||
var versionResponse map[string]interface{}
|
var versionResponse map[string]interface{}
|
||||||
err = json.Unmarshal(body, &versionResponse)
|
if err := json.NewDecoder(resp.Body).Decode(&versionResponse); err != nil {
|
||||||
if err != nil {
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if version, ok := (versionResponse["ApiVersion"]).(string); ok {
|
if version, ok := (versionResponse["ApiVersion"]).(string); ok {
|
||||||
|
@ -363,24 +377,35 @@ type doOptions struct {
|
||||||
forceJSON bool
|
forceJSON bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) do(method, path string, doOptions doOptions) ([]byte, int, error) {
|
func (c *Client) do(method, path string, doOptions doOptions) (*http.Response, error) {
|
||||||
var params io.Reader
|
var params io.Reader
|
||||||
if doOptions.data != nil || doOptions.forceJSON {
|
if doOptions.data != nil || doOptions.forceJSON {
|
||||||
buf, err := json.Marshal(doOptions.data)
|
buf, err := json.Marshal(doOptions.data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, -1, err
|
return nil, err
|
||||||
}
|
}
|
||||||
params = bytes.NewBuffer(buf)
|
params = bytes.NewBuffer(buf)
|
||||||
}
|
}
|
||||||
if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil {
|
if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil {
|
||||||
err := c.checkAPIVersion()
|
err := c.checkAPIVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, -1, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
req, err := http.NewRequest(method, c.getURL(path), params)
|
|
||||||
|
httpClient := c.HTTPClient
|
||||||
|
protocol := c.endpointURL.Scheme
|
||||||
|
var u string
|
||||||
|
if protocol == "unix" {
|
||||||
|
httpClient = c.unixClient()
|
||||||
|
u = c.getFakeUnixURL(path)
|
||||||
|
} else {
|
||||||
|
u = c.getURL(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(method, u, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, -1, err
|
return nil, err
|
||||||
}
|
}
|
||||||
req.Header.Set("User-Agent", userAgent)
|
req.Header.Set("User-Agent", userAgent)
|
||||||
if doOptions.data != nil {
|
if doOptions.data != nil {
|
||||||
|
@ -388,40 +413,19 @@ func (c *Client) do(method, path string, doOptions doOptions) ([]byte, int, erro
|
||||||
} else if method == "POST" {
|
} else if method == "POST" {
|
||||||
req.Header.Set("Content-Type", "plain/text")
|
req.Header.Set("Content-Type", "plain/text")
|
||||||
}
|
}
|
||||||
var resp *http.Response
|
|
||||||
protocol := c.endpointURL.Scheme
|
resp, err := httpClient.Do(req)
|
||||||
address := c.endpointURL.Path
|
|
||||||
if protocol == "unix" {
|
|
||||||
var dial net.Conn
|
|
||||||
dial, err = net.Dial(protocol, address)
|
|
||||||
if err != nil {
|
|
||||||
return nil, -1, err
|
|
||||||
}
|
|
||||||
defer dial.Close()
|
|
||||||
breader := bufio.NewReader(dial)
|
|
||||||
err = req.Write(dial)
|
|
||||||
if err != nil {
|
|
||||||
return nil, -1, err
|
|
||||||
}
|
|
||||||
resp, err = http.ReadResponse(breader, req)
|
|
||||||
} else {
|
|
||||||
resp, err = c.HTTPClient.Do(req)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "connection refused") {
|
if strings.Contains(err.Error(), "connection refused") {
|
||||||
return nil, -1, ErrConnectionRefused
|
return nil, ErrConnectionRefused
|
||||||
}
|
}
|
||||||
return nil, -1, err
|
return nil, err
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, -1, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
|
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
|
||||||
return nil, resp.StatusCode, newError(resp.StatusCode, body)
|
return nil, newError(resp)
|
||||||
}
|
}
|
||||||
return body, resp.StatusCode, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type streamOptions struct {
|
type streamOptions struct {
|
||||||
|
@ -462,12 +466,16 @@ 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 := c.Dialer.Dial(protocol, address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -503,11 +511,7 @@ func (c *Client) stream(method, path string, streamOptions streamOptions) error
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
|
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
return newError(resp)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return newError(resp.StatusCode, body)
|
|
||||||
}
|
}
|
||||||
if streamOptions.useJSONDecoder || resp.Header.Get("Content-Type") == "application/json" {
|
if streamOptions.useJSONDecoder || resp.Header.Get("Content-Type") == "application/json" {
|
||||||
// if we want to get raw json stream, just copy it back to output
|
// if we want to get raw json stream, just copy it back to output
|
||||||
|
@ -583,6 +587,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" {
|
||||||
|
@ -591,12 +597,12 @@ func (c *Client) hijack(method, path string, hijackOptions hijackOptions) error
|
||||||
}
|
}
|
||||||
var dial net.Conn
|
var dial net.Conn
|
||||||
if c.TLSConfig != nil && protocol != "unix" {
|
if c.TLSConfig != nil && protocol != "unix" {
|
||||||
dial, err = tlsDial(protocol, address, c.TLSConfig)
|
dial, err = tlsDialWithDialer(c.Dialer, protocol, address, c.TLSConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dial, err = net.Dial(protocol, address)
|
dial, err = c.Dialer.Dial(protocol, address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -612,13 +618,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 +635,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 {
|
||||||
|
@ -657,6 +664,41 @@ func (c *Client) getURL(path string) string {
|
||||||
return fmt.Sprintf("%s%s", urlStr, path)
|
return fmt.Sprintf("%s%s", urlStr, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getFakeUnixURL returns the URL needed to make an HTTP request over a UNIX
|
||||||
|
// domain socket to the given path.
|
||||||
|
func (c *Client) getFakeUnixURL(path string) string {
|
||||||
|
u := *c.endpointURL // Copy.
|
||||||
|
|
||||||
|
// Override URL so that net/http will not complain.
|
||||||
|
u.Scheme = "http"
|
||||||
|
u.Host = "unix.sock" // Doesn't matter what this is - it's not used.
|
||||||
|
u.Path = ""
|
||||||
|
|
||||||
|
urlStr := strings.TrimRight(u.String(), "/")
|
||||||
|
|
||||||
|
if c.requestedAPIVersion != nil {
|
||||||
|
return fmt.Sprintf("%s/v%s%s", urlStr, c.requestedAPIVersion, path)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s%s", urlStr, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) unixClient() *http.Client {
|
||||||
|
if c.unixHTTPClient != nil {
|
||||||
|
return c.unixHTTPClient
|
||||||
|
}
|
||||||
|
|
||||||
|
socketPath := c.endpointURL.Path
|
||||||
|
c.unixHTTPClient = &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
Dial: func(network, addr string) (net.Conn, error) {
|
||||||
|
return c.Dialer.Dial("unix", socketPath)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.unixHTTPClient
|
||||||
|
}
|
||||||
|
|
||||||
type jsonMessage struct {
|
type jsonMessage struct {
|
||||||
Status string `json:"status,omitempty"`
|
Status string `json:"status,omitempty"`
|
||||||
Progress string `json:"progress,omitempty"`
|
Progress string `json:"progress,omitempty"`
|
||||||
|
@ -738,8 +780,13 @@ type Error struct {
|
||||||
Message string
|
Message string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newError(status int, body []byte) *Error {
|
func newError(resp *http.Response) *Error {
|
||||||
return &Error{Status: status, Message: string(body)}
|
defer resp.Body.Close()
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return &Error{Status: resp.StatusCode, Message: fmt.Sprintf("cannot read body, err: %v", err)}
|
||||||
|
}
|
||||||
|
return &Error{Status: resp.StatusCode, Message: string(data)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Error) Error() string {
|
func (e *Error) Error() string {
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
package docker
|
package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
|
@ -161,6 +162,16 @@ func TestNewTLSClient(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEndpoint(t *testing.T) {
|
||||||
|
client, err := NewVersionedClient("http://localhost:4243", "1.12")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if endpoint := client.Endpoint(); endpoint != client.endpoint {
|
||||||
|
t.Errorf("Client.Endpoint(): want %q. Got %q", client.endpoint, endpoint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetURL(t *testing.T) {
|
func TestGetURL(t *testing.T) {
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
endpoint string
|
endpoint string
|
||||||
|
@ -185,8 +196,34 @@ func TestGetURL(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetFakeUnixURL(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
endpoint string
|
||||||
|
path string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"unix://var/run/docker.sock", "/", "http://unix.sock/"},
|
||||||
|
{"unix://var/run/docker.socket", "/", "http://unix.sock/"},
|
||||||
|
{"unix://var/run/docker.sock", "/containers/ps", "http://unix.sock/containers/ps"},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
client, _ := NewClient(tt.endpoint)
|
||||||
|
client.endpoint = tt.endpoint
|
||||||
|
client.SkipServerVersionCheck = true
|
||||||
|
got := client.getFakeUnixURL(tt.path)
|
||||||
|
if got != tt.expected {
|
||||||
|
t.Errorf("getURL(%q): Got %s. Want %s.", tt.path, got, tt.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestError(t *testing.T) {
|
func TestError(t *testing.T) {
|
||||||
err := newError(400, []byte("bad parameter"))
|
fakeBody := ioutil.NopCloser(bytes.NewBufferString("bad parameter"))
|
||||||
|
resp := &http.Response{
|
||||||
|
StatusCode: 400,
|
||||||
|
Body: fakeBody,
|
||||||
|
}
|
||||||
|
err := newError(resp)
|
||||||
expected := Error{Status: 400, Message: "bad parameter"}
|
expected := Error{Status: 400, Message: "bad parameter"}
|
||||||
if !reflect.DeepEqual(expected, *err) {
|
if !reflect.DeepEqual(expected, *err) {
|
||||||
t.Errorf("Wrong error type. Want %#v. Got %#v.", expected, *err)
|
t.Errorf("Wrong error type. Want %#v. Got %#v.", expected, *err)
|
||||||
|
@ -334,7 +371,7 @@ func TestPingErrorWithUnixSocket(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer li.Close()
|
defer li.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected to get listner, but failed: %#v", err)
|
t.Fatalf("Expected to get listener, but failed: %#v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fd, err := li.Accept()
|
fd, err := li.Accept()
|
||||||
|
@ -345,7 +382,7 @@ func TestPingErrorWithUnixSocket(t *testing.T) {
|
||||||
buf := make([]byte, 512)
|
buf := make([]byte, 512)
|
||||||
nr, err := fd.Read(buf)
|
nr, err := fd.Read(buf)
|
||||||
|
|
||||||
// Create invalid response message to occur error
|
// Create invalid response message to trigger error.
|
||||||
data := buf[0:nr]
|
data := buf[0:nr]
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
data[i] = 63
|
data[i] = 63
|
||||||
|
@ -366,6 +403,7 @@ func TestPingErrorWithUnixSocket(t *testing.T) {
|
||||||
u, _ := parseEndpoint(endpoint, false)
|
u, _ := parseEndpoint(endpoint, false)
|
||||||
client := Client{
|
client := Client{
|
||||||
HTTPClient: http.DefaultClient,
|
HTTPClient: http.DefaultClient,
|
||||||
|
Dialer: &net.Dialer{},
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
endpointURL: u,
|
endpointURL: u,
|
||||||
SkipServerVersionCheck: true,
|
SkipServerVersionCheck: true,
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
package docker
|
package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -23,7 +22,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,9 +40,8 @@ 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"`
|
||||||
|
@ -54,20 +52,21 @@ type APIContainers struct {
|
||||||
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{})
|
resp, err := c.do("GET", path, doOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
var containers []APIContainers
|
var containers []APIContainers
|
||||||
err = json.Unmarshal(body, &containers)
|
if err := json.NewDecoder(resp.Body).Decode(&containers); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return containers, nil
|
return containers, nil
|
||||||
|
@ -206,6 +205,7 @@ type Config struct {
|
||||||
DNS []string `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.9 and below only
|
DNS []string `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.9 and below only
|
||||||
Image string `json:"Image,omitempty" yaml:"Image,omitempty"`
|
Image string `json:"Image,omitempty" yaml:"Image,omitempty"`
|
||||||
Volumes map[string]struct{} `json:"Volumes,omitempty" yaml:"Volumes,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"`
|
VolumesFrom string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"`
|
||||||
WorkingDir string `json:"WorkingDir,omitempty" yaml:"WorkingDir,omitempty"`
|
WorkingDir string `json:"WorkingDir,omitempty" yaml:"WorkingDir,omitempty"`
|
||||||
MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"`
|
MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"`
|
||||||
|
@ -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"`
|
||||||
|
@ -266,6 +278,7 @@ type Container struct {
|
||||||
LogPath string `json:"LogPath,omitempty" yaml:"LogPath,omitempty"`
|
LogPath string `json:"LogPath,omitempty" yaml:"LogPath,omitempty"`
|
||||||
Name string `json:"Name,omitempty" yaml:"Name,omitempty"`
|
Name string `json:"Name,omitempty" yaml:"Name,omitempty"`
|
||||||
Driver string `json:"Driver,omitempty" yaml:"Driver,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"`
|
Volumes map[string]string `json:"Volumes,omitempty" yaml:"Volumes,omitempty"`
|
||||||
VolumesRW map[string]bool `json:"VolumesRW,omitempty" yaml:"VolumesRW,omitempty"`
|
VolumesRW map[string]bool `json:"VolumesRW,omitempty" yaml:"VolumesRW,omitempty"`
|
||||||
|
@ -279,7 +292,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,27 +303,31 @@ 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{})
|
resp, err := c.do("POST", fmt.Sprintf("/containers/"+opts.ID+"/rename?%s", queryString(opts)), doOptions{})
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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{})
|
resp, err := c.do("GET", path, doOptions{})
|
||||||
if status == http.StatusNotFound {
|
if err != nil {
|
||||||
|
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
|
||||||
return nil, &NoSuchContainer{ID: id}
|
return nil, &NoSuchContainer{ID: id}
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
var container Container
|
var container Container
|
||||||
err = json.Unmarshal(body, &container)
|
if err := json.NewDecoder(resp.Body).Decode(&container); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &container, nil
|
return &container, nil
|
||||||
|
@ -318,19 +335,19 @@ 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{})
|
resp, err := c.do("GET", path, doOptions{})
|
||||||
if status == http.StatusNotFound {
|
if err != nil {
|
||||||
|
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
|
||||||
return nil, &NoSuchContainer{ID: id}
|
return nil, &NoSuchContainer{ID: id}
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
var changes []Change
|
var changes []Change
|
||||||
err = json.Unmarshal(body, &changes)
|
if err := json.NewDecoder(resp.Body).Decode(&changes); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return changes, nil
|
return changes, nil
|
||||||
|
@ -338,7 +355,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,10 +365,10 @@ 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(
|
resp, err := c.do(
|
||||||
"POST",
|
"POST",
|
||||||
path,
|
path,
|
||||||
doOptions{
|
doOptions{
|
||||||
|
@ -365,18 +382,21 @@ func (c *Client) CreateContainer(opts CreateContainerOptions) (*Container, error
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
if status == http.StatusNotFound {
|
if e, ok := err.(*Error); ok {
|
||||||
|
if e.Status == http.StatusNotFound {
|
||||||
return nil, ErrNoSuchImage
|
return nil, ErrNoSuchImage
|
||||||
}
|
}
|
||||||
if status == http.StatusConflict {
|
if e.Status == http.StatusConflict {
|
||||||
return nil, ErrContainerAlreadyExists
|
return nil, ErrContainerAlreadyExists
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
var container Container
|
var container Container
|
||||||
err = json.Unmarshal(body, &container)
|
if err := json.NewDecoder(resp.Body).Decode(&container); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -459,100 +479,110 @@ type HostConfig struct {
|
||||||
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"`
|
||||||
|
MemorySwappiness int64 `json:"MemorySwappiness,omitempty" yaml:"MemorySwappiness,omitempty"`
|
||||||
|
OOMKillDisable bool `json:"OomKillDisable,omitempty" yaml:"OomKillDisable"`
|
||||||
CPUShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty"`
|
CPUShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty"`
|
||||||
CPUSet string `json:"Cpuset,omitempty" yaml:"Cpuset,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"`
|
CPUQuota int64 `json:"CpuQuota,omitempty" yaml:"CpuQuota,omitempty"`
|
||||||
CPUPeriod int64 `json:"CpuPeriod,omitempty" yaml:"CpuPeriod,omitempty"`
|
CPUPeriod int64 `json:"CpuPeriod,omitempty" yaml:"CpuPeriod,omitempty"`
|
||||||
|
BlkioWeight int64 `json:"BlkioWeight,omitempty" yaml:"BlkioWeight"`
|
||||||
Ulimits []ULimit `json:"Ulimits,omitempty" yaml:"Ulimits,omitempty"`
|
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})
|
resp, err := c.do("POST", path, doOptions{data: hostConfig, forceJSON: true})
|
||||||
if status == http.StatusNotFound {
|
if err != nil {
|
||||||
|
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
|
||||||
return &NoSuchContainer{ID: id, Err: err}
|
return &NoSuchContainer{ID: id, Err: err}
|
||||||
}
|
}
|
||||||
if status == http.StatusNotModified {
|
|
||||||
return &ContainerAlreadyRunning{ID: id}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if resp.StatusCode == http.StatusNotModified {
|
||||||
|
return &ContainerAlreadyRunning{ID: id}
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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{})
|
resp, err := c.do("POST", path, doOptions{})
|
||||||
if status == http.StatusNotFound {
|
if err != nil {
|
||||||
|
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
|
||||||
return &NoSuchContainer{ID: id}
|
return &NoSuchContainer{ID: id}
|
||||||
}
|
}
|
||||||
if status == http.StatusNotModified {
|
|
||||||
return &ContainerNotRunning{ID: id}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if resp.StatusCode == http.StatusNotModified {
|
||||||
|
return &ContainerNotRunning{ID: id}
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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{})
|
resp, err := c.do("POST", path, doOptions{})
|
||||||
if status == http.StatusNotFound {
|
if err != nil {
|
||||||
|
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
|
||||||
return &NoSuchContainer{ID: id}
|
return &NoSuchContainer{ID: id}
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
resp.Body.Close()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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{})
|
resp, err := c.do("POST", path, doOptions{})
|
||||||
if status == http.StatusNotFound {
|
if err != nil {
|
||||||
|
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
|
||||||
return &NoSuchContainer{ID: id}
|
return &NoSuchContainer{ID: id}
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
resp.Body.Close()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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{})
|
resp, err := c.do("POST", path, doOptions{})
|
||||||
if status == http.StatusNotFound {
|
if err != nil {
|
||||||
|
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
|
||||||
return &NoSuchContainer{ID: id}
|
return &NoSuchContainer{ID: id}
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
resp.Body.Close()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 +590,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
|
||||||
|
@ -568,15 +598,15 @@ func (c *Client) TopContainer(id string, psArgs string) (TopResult, error) {
|
||||||
args = fmt.Sprintf("?ps_args=%s", psArgs)
|
args = fmt.Sprintf("?ps_args=%s", psArgs)
|
||||||
}
|
}
|
||||||
path := fmt.Sprintf("/containers/%s/top%s", id, args)
|
path := fmt.Sprintf("/containers/%s/top%s", id, args)
|
||||||
body, status, err := c.do("GET", path, doOptions{})
|
resp, err := c.do("GET", path, doOptions{})
|
||||||
if status == http.StatusNotFound {
|
if err != nil {
|
||||||
|
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
|
||||||
return result, &NoSuchContainer{ID: id}
|
return result, &NoSuchContainer{ID: id}
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
err = json.Unmarshal(body, &result)
|
defer resp.Body.Close()
|
||||||
if err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
|
@ -584,7 +614,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 +704,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 +720,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()
|
||||||
|
@ -750,7 +781,7 @@ func (c *Client) Stats(opts StatsOptions) (retErr error) {
|
||||||
|
|
||||||
decoder := json.NewDecoder(readCloser)
|
decoder := json.NewDecoder(readCloser)
|
||||||
stats := new(Stats)
|
stats := new(Stats)
|
||||||
for err := decoder.Decode(&stats); err != io.EOF; err = decoder.Decode(stats) {
|
for err := decoder.Decode(stats); err != io.EOF; err = decoder.Decode(stats) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -763,7 +794,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,24 +804,26 @@ 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{})
|
resp, err := c.do("POST", path, doOptions{})
|
||||||
if status == http.StatusNotFound {
|
if err != nil {
|
||||||
|
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
|
||||||
return &NoSuchContainer{ID: opts.ID}
|
return &NoSuchContainer{ID: opts.ID}
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
resp.Body.Close()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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,64 +839,107 @@ 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{})
|
resp, err := c.do("DELETE", path, doOptions{})
|
||||||
if status == http.StatusNotFound {
|
if err != nil {
|
||||||
|
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
|
||||||
return &NoSuchContainer{ID: opts.ID}
|
return &NoSuchContainer{ID: opts.ID}
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
resp.Body.Close()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopyFromContainerOptions is the set of options that can be used when copying
|
// UploadToContainerOptions is the set of options that can be used when
|
||||||
// files or folders from a container.
|
// uploading an archive into a container.
|
||||||
//
|
//
|
||||||
// See http://goo.gl/rINMlw for more details.
|
// 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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyFromContainerOptions has been DEPRECATED, please use DownloadFromContainerOptions along with DownloadFromContainer.
|
||||||
|
//
|
||||||
|
// See https://goo.gl/R2jevW for more details.
|
||||||
type CopyFromContainerOptions struct {
|
type CopyFromContainerOptions struct {
|
||||||
OutputStream io.Writer `json:"-"`
|
OutputStream io.Writer `json:"-"`
|
||||||
Container string `json:"-"`
|
Container string `json:"-"`
|
||||||
Resource string
|
Resource string
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopyFromContainer copy files or folders from a container, using a given
|
// CopyFromContainer has been DEPRECATED, please use DownloadFromContainerOptions along with DownloadFromContainer.
|
||||||
// resource.
|
|
||||||
//
|
//
|
||||||
// See http://goo.gl/rINMlw for more details.
|
// See https://goo.gl/R2jevW 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}
|
||||||
}
|
}
|
||||||
url := fmt.Sprintf("/containers/%s/copy", opts.Container)
|
url := fmt.Sprintf("/containers/%s/copy", opts.Container)
|
||||||
body, status, err := c.do("POST", url, doOptions{data: opts})
|
resp, err := c.do("POST", url, doOptions{data: opts})
|
||||||
if status == http.StatusNotFound {
|
if err != nil {
|
||||||
|
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
|
||||||
return &NoSuchContainer{ID: opts.Container}
|
return &NoSuchContainer{ID: opts.Container}
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = io.Copy(opts.OutputStream, bytes.NewBuffer(body))
|
defer resp.Body.Close()
|
||||||
|
_, err = io.Copy(opts.OutputStream, resp.Body)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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{})
|
resp, err := c.do("POST", "/containers/"+id+"/wait", doOptions{})
|
||||||
if status == http.StatusNotFound {
|
if err != nil {
|
||||||
|
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
|
||||||
return 0, &NoSuchContainer{ID: id}
|
return 0, &NoSuchContainer{ID: id}
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
var r struct{ StatusCode int }
|
var r struct{ StatusCode int }
|
||||||
err = json.Unmarshal(body, &r)
|
if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return r.StatusCode, nil
|
return r.StatusCode, nil
|
||||||
|
@ -871,7 +947,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,19 +959,19 @@ 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})
|
resp, err := c.do("POST", path, doOptions{data: opts.Run})
|
||||||
if status == http.StatusNotFound {
|
if err != nil {
|
||||||
|
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
|
||||||
return nil, &NoSuchContainer{ID: opts.Container}
|
return nil, &NoSuchContainer{ID: opts.Container}
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
var image Image
|
var image Image
|
||||||
err = json.Unmarshal(body, &image)
|
if err := json.NewDecoder(resp.Body).Decode(&image); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &image, nil
|
return &image, nil
|
||||||
|
@ -904,7 +980,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 +1015,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 +1033,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 +1051,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,18 +1068,24 @@ 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))
|
||||||
params.Set("w", strconv.Itoa(width))
|
params.Set("w", strconv.Itoa(width))
|
||||||
_, _, err := c.do("POST", "/containers/"+id+"/resize?"+params.Encode(), doOptions{})
|
resp, err := c.do("POST", "/containers/"+id+"/resize?"+params.Encode(), doOptions{})
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 +1094,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}
|
||||||
|
|
|
@ -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"},
|
||||||
|
@ -1374,6 +1371,7 @@ func TestExportContainerViaUnixSocket(t *testing.T) {
|
||||||
u, _ := parseEndpoint(endpoint, false)
|
u, _ := parseEndpoint(endpoint, false)
|
||||||
client := Client{
|
client := Client{
|
||||||
HTTPClient: http.DefaultClient,
|
HTTPClient: http.DefaultClient,
|
||||||
|
Dialer: &net.Dialer{},
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
endpointURL: u,
|
endpointURL: u,
|
||||||
SkipServerVersionCheck: true,
|
SkipServerVersionCheck: true,
|
||||||
|
@ -1429,17 +1427,60 @@ func TestExportContainerNoId(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUploadToContainer(t *testing.T) {
|
||||||
|
content := "File content"
|
||||||
|
in := stdinMock{bytes.NewBufferString(content)}
|
||||||
|
fakeRT := &FakeRoundTripper{status: http.StatusOK}
|
||||||
|
client := newTestClient(fakeRT)
|
||||||
|
opts := UploadToContainerOptions{
|
||||||
|
Path: "abc",
|
||||||
|
InputStream: in,
|
||||||
|
}
|
||||||
|
err := client.UploadToContainer("a123456", opts)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("UploadToContainer: caught error %#v while uploading archive to container, expected nil", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req := fakeRT.requests[0]
|
||||||
|
|
||||||
|
if req.Method != "PUT" {
|
||||||
|
t.Errorf("UploadToContainer{Path:abc}: Wrong HTTP method. Want PUT. Got %s", req.Method)
|
||||||
|
}
|
||||||
|
|
||||||
|
if pathParam := req.URL.Query().Get("path"); pathParam != "abc" {
|
||||||
|
t.Errorf("ListImages({Path:abc}): Wrong parameter. Want path=abc. Got path=%s", pathParam)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDownloadFromContainer(t *testing.T) {
|
||||||
|
filecontent := "File content"
|
||||||
|
client := newTestClient(&FakeRoundTripper{message: filecontent, status: http.StatusOK})
|
||||||
|
|
||||||
|
var out bytes.Buffer
|
||||||
|
opts := DownloadFromContainerOptions{
|
||||||
|
OutputStream: &out,
|
||||||
|
}
|
||||||
|
err := client.DownloadFromContainer("a123456", opts)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("DownloadFromContainer: caught error %#v while downloading from container, expected nil", err.Error())
|
||||||
|
}
|
||||||
|
if out.String() != filecontent {
|
||||||
|
t.Errorf("DownloadFromContainer: wrong stdout. Want %#v. Got %#v.", filecontent, out.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCopyFromContainer(t *testing.T) {
|
func TestCopyFromContainer(t *testing.T) {
|
||||||
content := "File content"
|
content := "File content"
|
||||||
out := stdoutMock{bytes.NewBufferString(content)}
|
out := stdoutMock{bytes.NewBufferString(content)}
|
||||||
client := newTestClient(&FakeRoundTripper{status: http.StatusOK})
|
client := newTestClient(&FakeRoundTripper{status: http.StatusOK})
|
||||||
opts := CopyFromContainerOptions{
|
opts := CopyFromContainerOptions{
|
||||||
Container: "a123456",
|
Container: "a123456",
|
||||||
OutputStream: out,
|
OutputStream: &out,
|
||||||
}
|
}
|
||||||
err := client.CopyFromContainer(opts)
|
err := client.CopyFromContainer(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("CopyFromContainer: caugh error %#v while copying from container, expected nil", err.Error())
|
t.Errorf("CopyFromContainer: caught error %#v while copying from container, expected nil", err.Error())
|
||||||
}
|
}
|
||||||
if out.String() != content {
|
if out.String() != content {
|
||||||
t.Errorf("CopyFromContainer: wrong stdout. Want %#v. Got %#v.", content, out.String())
|
t.Errorf("CopyFromContainer: wrong stdout. Want %#v. Got %#v.", content, out.String())
|
||||||
|
@ -1587,7 +1628,6 @@ func TestTopContainerWithPsArgs(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStatsTimeout(t *testing.T) {
|
func TestStatsTimeout(t *testing.T) {
|
||||||
|
|
||||||
l, err := net.Listen("unix", "/tmp/docker_test.sock")
|
l, err := net.Listen("unix", "/tmp/docker_test.sock")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -1597,7 +1637,7 @@ func TestStatsTimeout(t *testing.T) {
|
||||||
go func() {
|
go func() {
|
||||||
l.Accept()
|
l.Accept()
|
||||||
received = true
|
received = true
|
||||||
time.Sleep(time.Millisecond * 250)
|
time.Sleep(time.Second)
|
||||||
}()
|
}()
|
||||||
client, _ := NewClient("unix:///tmp/docker_test.sock")
|
client, _ := NewClient("unix:///tmp/docker_test.sock")
|
||||||
client.SkipServerVersionCheck = true
|
client.SkipServerVersionCheck = true
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
package docker
|
package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -260,9 +259,9 @@ func (c *Client) eventHijack(startTime int64, eventChan chan *APIEvents, errChan
|
||||||
var dial net.Conn
|
var dial net.Conn
|
||||||
var err error
|
var err error
|
||||||
if c.TLSConfig == nil {
|
if c.TLSConfig == nil {
|
||||||
dial, err = net.Dial(protocol, address)
|
dial, err = c.Dialer.Dial(protocol, address)
|
||||||
} else {
|
} else {
|
||||||
dial, err = tls.Dial(protocol, address, c.TLSConfig)
|
dial, err = tlsDialWithDialer(c.Dialer, protocol, address, c.TLSConfig)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -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)
|
||||||
|
resp, err := c.do("POST", path, doOptions{data: opts})
|
||||||
|
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 exec Exec
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&exec); 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}
|
||||||
|
@ -120,13 +90,14 @@ func (c *Client) StartExec(id string, opts StartExecOptions) error {
|
||||||
path := fmt.Sprintf("/exec/%s/start", id)
|
path := fmt.Sprintf("/exec/%s/start", id)
|
||||||
|
|
||||||
if opts.Detach {
|
if opts.Detach {
|
||||||
_, status, err := c.do("POST", path, doOptions{data: opts})
|
resp, err := c.do("POST", path, doOptions{data: opts})
|
||||||
if status == http.StatusNotFound {
|
if err != nil {
|
||||||
|
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
|
||||||
return &NoSuchExec{ID: id}
|
return &NoSuchExec{ID: id}
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,32 +115,62 @@ 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))
|
||||||
params.Set("w", strconv.Itoa(width))
|
params.Set("w", strconv.Itoa(width))
|
||||||
|
|
||||||
path := fmt.Sprintf("/exec/%s/resize?%s", id, params.Encode())
|
path := fmt.Sprintf("/exec/%s/resize?%s", id, params.Encode())
|
||||||
_, _, err := c.do("POST", path, doOptions{})
|
resp, err := c.do("POST", path, doOptions{})
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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{})
|
resp, err := c.do("GET", path, doOptions{})
|
||||||
if status == http.StatusNotFound {
|
if err != nil {
|
||||||
|
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
|
||||||
return nil, &NoSuchExec{ID: id}
|
return nil, &NoSuchExec{ID: id}
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
var exec ExecInspect
|
var exec ExecInspect
|
||||||
err = json.Unmarshal(body, &exec)
|
if err := json.NewDecoder(resp.Body).Decode(&exec); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &exec, nil
|
return &exec, nil
|
||||||
|
|
|
@ -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,37 +82,56 @@ 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{})
|
resp, err := c.do("GET", path, doOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
var images []APIImages
|
var images []APIImages
|
||||||
err = json.Unmarshal(body, &images)
|
if err := json.NewDecoder(resp.Body).Decode(&images); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
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{})
|
resp, err := c.do("GET", "/images/"+name+"/history", doOptions{})
|
||||||
if status == http.StatusNotFound {
|
if err != nil {
|
||||||
|
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
|
||||||
return nil, ErrNoSuchImage
|
return nil, ErrNoSuchImage
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
var history []ImageHistory
|
var history []ImageHistory
|
||||||
err = json.Unmarshal(body, &history)
|
if err := json.NewDecoder(resp.Body).Decode(&history); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return history, nil
|
return history, nil
|
||||||
|
@ -140,19 +139,23 @@ 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{})
|
resp, err := c.do("DELETE", "/images/"+name, doOptions{})
|
||||||
if status == http.StatusNotFound {
|
if err != nil {
|
||||||
|
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
|
||||||
return ErrNoSuchImage
|
return ErrNoSuchImage
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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,40 +164,43 @@ 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{})
|
resp, err := c.do("DELETE", uri, doOptions{})
|
||||||
if status == http.StatusNotFound {
|
if err != nil {
|
||||||
|
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
|
||||||
return ErrNoSuchImage
|
return ErrNoSuchImage
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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{})
|
resp, err := c.do("GET", "/images/"+name+"/json", doOptions{})
|
||||||
if status == http.StatusNotFound {
|
if err != nil {
|
||||||
|
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
|
||||||
return nil, ErrNoSuchImage
|
return nil, ErrNoSuchImage
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
var image Image
|
var image Image
|
||||||
|
|
||||||
// if the caller elected to skip checking the server's version, assume it's the latest
|
// if the caller elected to skip checking the server's version, assume it's the latest
|
||||||
if c.SkipServerVersionCheck || c.expectedAPIVersion.GreaterThanOrEqualTo(apiVersion112) {
|
if c.SkipServerVersionCheck || c.expectedAPIVersion.GreaterThanOrEqualTo(apiVersion112) {
|
||||||
err = json.Unmarshal(body, &image)
|
if err := json.NewDecoder(resp.Body).Decode(&image); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var imagePre012 ImagePre012
|
var imagePre012 ImagePre012
|
||||||
err = json.Unmarshal(body, &imagePre012)
|
if err := json.NewDecoder(resp.Body).Decode(&imagePre012); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,7 +222,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 +242,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 +265,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 +274,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 +303,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 +318,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 +338,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 +346,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 +360,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 +373,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 +386,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 +421,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 +458,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,15 +479,16 @@ 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
|
||||||
}
|
}
|
||||||
_, status, err := c.do("POST", fmt.Sprintf("/images/"+name+"/tag?%s",
|
resp, err := c.do("POST", fmt.Sprintf("/images/"+name+"/tag?%s",
|
||||||
queryString(&opts)), doOptions{})
|
queryString(&opts)), doOptions{})
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if status == http.StatusNotFound {
|
if resp.StatusCode == http.StatusNotFound {
|
||||||
return ErrNoSuchImage
|
return ErrNoSuchImage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -497,7 +514,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 +526,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,15 +539,15 @@ 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{})
|
resp, err := c.do("GET", "/images/search?term="+term, doOptions{})
|
||||||
|
defer resp.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var searchResult []APIImageSearch
|
var searchResult []APIImageSearch
|
||||||
err = json.Unmarshal(body, &searchResult)
|
if err := json.NewDecoder(resp.Body).Decode(&searchResult); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return searchResult, nil
|
return searchResult, nil
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
@ -21,11 +22,14 @@ 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},
|
||||||
|
Dialer: &net.Dialer{},
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
endpointURL: u,
|
endpointURL: u,
|
||||||
SkipServerVersionCheck: true,
|
SkipServerVersionCheck: true,
|
||||||
|
serverAPIVersion: testAPIVersion,
|
||||||
}
|
}
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,21 +4,19 @@
|
||||||
|
|
||||||
package docker
|
package docker
|
||||||
|
|
||||||
import (
|
import "strings"
|
||||||
"bytes"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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{})
|
resp, err := c.do("GET", "/version", doOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
var env Env
|
var env Env
|
||||||
if err := env.Decode(bytes.NewReader(body)); err != nil {
|
if err := env.Decode(resp.Body); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &env, nil
|
return &env, nil
|
||||||
|
@ -26,15 +24,15 @@ 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{})
|
resp, err := c.do("GET", "/info", doOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
var info Env
|
var info Env
|
||||||
err = info.Decode(bytes.NewReader(body))
|
if err := info.Decode(resp.Body); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &info, nil
|
return &info, nil
|
||||||
|
|
|
@ -38,12 +38,13 @@ type Endpoint struct {
|
||||||
//
|
//
|
||||||
// See https://goo.gl/4hCNtZ for more details.
|
// See https://goo.gl/4hCNtZ for more details.
|
||||||
func (c *Client) ListNetworks() ([]Network, error) {
|
func (c *Client) ListNetworks() ([]Network, error) {
|
||||||
body, _, err := c.do("GET", "/networks", doOptions{})
|
resp, err := c.do("GET", "/networks", doOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
var networks []Network
|
var networks []Network
|
||||||
if err := json.Unmarshal(body, &networks); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&networks); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return networks, nil
|
return networks, nil
|
||||||
|
@ -54,15 +55,16 @@ func (c *Client) ListNetworks() ([]Network, error) {
|
||||||
// See https://goo.gl/4hCNtZ for more details.
|
// See https://goo.gl/4hCNtZ for more details.
|
||||||
func (c *Client) NetworkInfo(id string) (*Network, error) {
|
func (c *Client) NetworkInfo(id string) (*Network, error) {
|
||||||
path := "/networks/" + id
|
path := "/networks/" + id
|
||||||
body, status, err := c.do("GET", path, doOptions{})
|
resp, err := c.do("GET", path, doOptions{})
|
||||||
if status == http.StatusNotFound {
|
if err != nil {
|
||||||
|
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
|
||||||
return nil, &NoSuchNetwork{ID: id}
|
return nil, &NoSuchNetwork{ID: id}
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
var network Network
|
var network Network
|
||||||
if err := json.Unmarshal(body, &network); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&network); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &network, nil
|
return &network, nil
|
||||||
|
@ -83,35 +85,34 @@ type CreateNetworkOptions struct {
|
||||||
//
|
//
|
||||||
// See http://goo.gl/mErxNp for more details.
|
// See http://goo.gl/mErxNp for more details.
|
||||||
func (c *Client) CreateNetwork(opts CreateNetworkOptions) (*Network, error) {
|
func (c *Client) CreateNetwork(opts CreateNetworkOptions) (*Network, error) {
|
||||||
body, status, err := c.do(
|
resp, err := c.do(
|
||||||
"POST",
|
"POST",
|
||||||
"/networks",
|
"/networks",
|
||||||
doOptions{
|
doOptions{
|
||||||
data: opts,
|
data: opts,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
if err != nil {
|
||||||
if status == http.StatusConflict {
|
if e, ok := err.(*Error); ok && e.Status == http.StatusConflict {
|
||||||
return nil, ErrNetworkAlreadyExists
|
return nil, ErrNetworkAlreadyExists
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
type createNetworkResponse struct {
|
type createNetworkResponse struct {
|
||||||
ID string
|
ID string
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
network Network
|
network Network
|
||||||
resp createNetworkResponse
|
cnr createNetworkResponse
|
||||||
)
|
)
|
||||||
err = json.Unmarshal(body, &resp)
|
if err := json.NewDecoder(resp.Body).Decode(&cnr); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
network.Name = opts.Name
|
network.Name = opts.Name
|
||||||
network.ID = resp.ID
|
network.ID = cnr.ID
|
||||||
network.Type = opts.NetworkType
|
network.Type = opts.NetworkType
|
||||||
|
|
||||||
return &network, nil
|
return &network, nil
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
doesnotexist
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -936,7 +957,7 @@ func (s *DockerServer) createExecContainer(w http.ResponseWriter, r *http.Reques
|
||||||
|
|
||||||
func (s *DockerServer) startExecContainer(w http.ResponseWriter, r *http.Request) {
|
func (s *DockerServer) startExecContainer(w http.ResponseWriter, r *http.Request) {
|
||||||
id := mux.Vars(r)["id"]
|
id := mux.Vars(r)["id"]
|
||||||
if exec, err := s.getExec(id); err == nil {
|
if exec, err := s.getExec(id, false); err == nil {
|
||||||
s.execMut.Lock()
|
s.execMut.Lock()
|
||||||
exec.Running = true
|
exec.Running = true
|
||||||
s.execMut.Unlock()
|
s.execMut.Unlock()
|
||||||
|
@ -958,7 +979,7 @@ func (s *DockerServer) startExecContainer(w http.ResponseWriter, r *http.Request
|
||||||
|
|
||||||
func (s *DockerServer) resizeExecContainer(w http.ResponseWriter, r *http.Request) {
|
func (s *DockerServer) resizeExecContainer(w http.ResponseWriter, r *http.Request) {
|
||||||
id := mux.Vars(r)["id"]
|
id := mux.Vars(r)["id"]
|
||||||
if _, err := s.getExec(id); err == nil {
|
if _, err := s.getExec(id, false); err == nil {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -967,7 +988,7 @@ func (s *DockerServer) resizeExecContainer(w http.ResponseWriter, r *http.Reques
|
||||||
|
|
||||||
func (s *DockerServer) inspectExecContainer(w http.ResponseWriter, r *http.Request) {
|
func (s *DockerServer) inspectExecContainer(w http.ResponseWriter, r *http.Request) {
|
||||||
id := mux.Vars(r)["id"]
|
id := mux.Vars(r)["id"]
|
||||||
if exec, err := s.getExec(id); err == nil {
|
if exec, err := s.getExec(id, true); err == nil {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(exec)
|
json.NewEncoder(w).Encode(exec)
|
||||||
|
@ -976,11 +997,15 @@ func (s *DockerServer) inspectExecContainer(w http.ResponseWriter, r *http.Reque
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerServer) getExec(id string) (*docker.ExecInspect, error) {
|
func (s *DockerServer) getExec(id string, copy bool) (*docker.ExecInspect, error) {
|
||||||
s.execMut.RLock()
|
s.execMut.RLock()
|
||||||
defer s.execMut.RUnlock()
|
defer s.execMut.RUnlock()
|
||||||
for _, exec := range s.execs {
|
for _, exec := range s.execs {
|
||||||
if exec.ID == id {
|
if exec.ID == id {
|
||||||
|
if copy {
|
||||||
|
cp := *exec
|
||||||
|
exec = &cp
|
||||||
|
}
|
||||||
return exec, nil
|
return exec, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
77
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/server_test.go
generated
vendored
77
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/server_test.go
generated
vendored
|
@ -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,
|
||||||
|
|
|
@ -94,7 +94,3 @@ func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Con
|
||||||
// wrapper which holds both the TLS and raw connections.
|
// wrapper which holds both the TLS and raw connections.
|
||||||
return &tlsClientCon{conn, rawConn}, nil
|
return &tlsClientCon{conn, rawConn}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func tlsDial(network, addr string, config *tls.Config) (net.Conn, error) {
|
|
||||||
return tlsDialWithDialer(new(net.Dialer), network, addr, config)
|
|
||||||
}
|
|
||||||
|
|
127
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/volume.go
generated
vendored
Normal file
127
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/volume.go
generated
vendored
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
// 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) {
|
||||||
|
resp, err := c.do("GET", "/volumes?"+queryString(opts), doOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
m := make(map[string]interface{})
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&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) {
|
||||||
|
resp, err := c.do("POST", "/volumes", doOptions{data: opts})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
var volume Volume
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&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) {
|
||||||
|
resp, err := c.do("GET", "/volumes/"+name, doOptions{})
|
||||||
|
if err != nil {
|
||||||
|
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
|
||||||
|
return nil, ErrNoSuchVolume
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
var volume Volume
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&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 {
|
||||||
|
resp, err := c.do("DELETE", "/volumes/"+name, doOptions{})
|
||||||
|
if err != nil {
|
||||||
|
if e, ok := err.(*Error); ok {
|
||||||
|
if e.Status == http.StatusNotFound {
|
||||||
|
return ErrNoSuchVolume
|
||||||
|
}
|
||||||
|
if e.Status == http.StatusConflict {
|
||||||
|
return ErrVolumeInUse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
return nil
|
||||||
|
}
|
142
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/volume_test.go
generated
vendored
Normal file
142
Godeps/_workspace/src/github.com/fsouza/go-dockerclient/volume_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue