Bump fsouza/go-dockerclient

pull/6/head
Sami Wagiaalla 2015-10-05 21:12:29 -04:00
parent 4856c7c033
commit 5bcf2324a9
21 changed files with 501 additions and 273 deletions

2
Godeps/Godeps.json generated
View File

@ -238,7 +238,7 @@
}, },
{ {
"ImportPath": "github.com/fsouza/go-dockerclient", "ImportPath": "github.com/fsouza/go-dockerclient",
"Rev": "76fd6c68cf24c48ee6a2b25def997182a29f940e" "Rev": "1399676f53e6ccf46e0bf00751b21bed329bc60e"
}, },
{ {
"ImportPath": "github.com/garyburd/redigo/internal", "ImportPath": "github.com/garyburd/redigo/internal",

View File

@ -0,0 +1,2 @@
# temporary symlink for testing
testing/data/symlink

View File

@ -1,9 +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.5.1
- tip - tip
env: env:
- GOARCH=amd64 - GOARCH=amd64

View File

@ -1,6 +1,7 @@
# This is the official list of go-dockerclient authors for copyright purposes. # This is the official list of go-dockerclient authors for copyright purposes.
Adam Bell-Hanssen <adamb@aller.no> Adam Bell-Hanssen <adamb@aller.no>
Adrien Kohlbecker <adrien.kohlbecker@gmail.com>
Aldrin Leal <aldrin@leal.eng.br> Aldrin Leal <aldrin@leal.eng.br>
Andreas Jaekle <andreas@jaekle.net> Andreas Jaekle <andreas@jaekle.net>
Andrews Medina <andrewsmedina@gmail.com> Andrews Medina <andrewsmedina@gmail.com>
@ -11,6 +12,7 @@ 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> Cesar Wong <cewong@redhat.com>
@ -28,6 +30,7 @@ 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>
@ -45,6 +48,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>
@ -52,6 +56,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>
@ -75,7 +80,9 @@ 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>
Sami Wagiaalla <swagiaal@redhat.com>
Samuel Karp <skarp@amazon.com> Samuel Karp <skarp@amazon.com>
Silas Sewell <silas@sewell.org>
Simon Eskildsen <sirup@sirupsen.com> Simon Eskildsen <sirup@sirupsen.com>
Simon Menke <simon.menke@gmail.com> Simon Menke <simon.menke@gmail.com>
Skolos <skolos@gopherlab.com> Skolos <skolos@gopherlab.com>

View File

@ -5,7 +5,7 @@
[![GoDoc](https://img.shields.io/badge/api-Godoc-blue.svg?style=flat-square)](https://godoc.org/github.com/fsouza/go-dockerclient) [![GoDoc](https://img.shields.io/badge/api-Godoc-blue.svg?style=flat-square)](https://godoc.org/github.com/fsouza/go-dockerclient)
This package presents a client for the Docker remote API. It also provides This package presents a client for the Docker remote API. It also provides
support for the extensions in the [Swarm API](https://docs.docker.com/swarm/API/). support for the extensions in the [Swarm API](https://docs.docker.com/swarm/api/swarm-api/).
This package also provides support for docker's network API, which is a simple This package also provides support for docker's network API, which is a simple
passthrough to the libnetwork remote API. Note that docker's network API is passthrough to the libnetwork remote API. Note that docker's network API is

View File

@ -127,12 +127,10 @@ 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
} }

View File

@ -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",

View File

@ -130,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
@ -137,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
@ -191,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),
@ -302,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),
@ -326,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 https://goo.gl/kQCfJj 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 {
@ -365,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 {
@ -390,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 {
@ -464,16 +466,12 @@ 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
} }
@ -509,11 +507,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
@ -599,12 +593,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
} }
@ -666,6 +660,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"`
@ -747,8 +776,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 {

View File

@ -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,

View File

@ -5,7 +5,6 @@
package docker package docker
import ( import (
"bytes"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -61,13 +60,13 @@ type APIContainers struct {
// See https://goo.gl/47a6tO 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"`
@ -278,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"`
@ -304,25 +305,29 @@ type RenameContainerOptions struct {
// //
// See https://goo.gl/laSOIy 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 https://goo.gl/RdIq0b 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
@ -333,16 +338,16 @@ func (c *Client) InspectContainer(id string) (*Container, error) {
// See https://goo.gl/9GsTIF 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
@ -363,7 +368,7 @@ type CreateContainerOptions struct {
// See https://goo.gl/WxQzrr 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{
@ -377,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
} }
@ -449,6 +457,7 @@ type HostConfig struct {
Binds []string `json:"Binds,omitempty" yaml:"Binds,omitempty"` Binds []string `json:"Binds,omitempty" yaml:"Binds,omitempty"`
CapAdd []string `json:"CapAdd,omitempty" yaml:"CapAdd,omitempty"` CapAdd []string `json:"CapAdd,omitempty" yaml:"CapAdd,omitempty"`
CapDrop []string `json:"CapDrop,omitempty" yaml:"CapDrop,omitempty"` CapDrop []string `json:"CapDrop,omitempty" yaml:"CapDrop,omitempty"`
GroupAdd []string `json:"GroupAdd,omitempty" yaml:"GroupAdd,omitempty"`
ContainerIDFile string `json:"ContainerIDFile,omitempty" yaml:"ContainerIDFile,omitempty"` ContainerIDFile string `json:"ContainerIDFile,omitempty" yaml:"ContainerIDFile,omitempty"`
LxcConf []KeyValuePair `json:"LxcConf,omitempty" yaml:"LxcConf,omitempty"` LxcConf []KeyValuePair `json:"LxcConf,omitempty" yaml:"LxcConf,omitempty"`
Privileged bool `json:"Privileged,omitempty" yaml:"Privileged,omitempty"` Privileged bool `json:"Privileged,omitempty" yaml:"Privileged,omitempty"`
@ -488,16 +497,17 @@ type HostConfig struct {
// See https://goo.gl/MrBAJv 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
} }
@ -507,16 +517,17 @@ func (c *Client) StartContainer(id string, hostConfig *HostConfig) error {
// See https://goo.gl/USqsFt 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
} }
@ -526,13 +537,14 @@ func (c *Client) StopContainer(id string, timeout uint) error {
// See https://goo.gl/QzsDnz 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
} }
@ -541,13 +553,14 @@ func (c *Client) RestartContainer(id string, timeout uint) error {
// See https://goo.gl/OF7W9X 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
} }
@ -556,13 +569,14 @@ func (c *Client) PauseContainer(id string) error {
// See https://goo.gl/7dwyPA 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
} }
@ -585,15 +599,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
@ -768,7 +782,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
} }
@ -797,13 +811,14 @@ type KillContainerOptions struct {
// See https://goo.gl/hkS9i8 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
} }
@ -828,43 +843,86 @@ type RemoveContainerOptions struct {
// See https://goo.gl/RQyX62 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 https://goo.gl/4L7b07 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 https://goo.gl/4L7b07 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
} }
@ -873,16 +931,16 @@ func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error {
// //
// See https://goo.gl/Gc1rge 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
@ -905,16 +963,16 @@ type CommitContainerOptions struct {
// See https://goo.gl/mqfoCw 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
@ -1017,9 +1075,13 @@ 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.

View File

@ -238,7 +238,8 @@ func TestInspectContainer(t *testing.T) {
"PublishAllPorts": false, "PublishAllPorts": false,
"CgroupParent": "/mesos", "CgroupParent": "/mesos",
"Memory": 17179869184, "Memory": 17179869184,
"MemorySwap": 34359738368 "MemorySwap": 34359738368,
"GroupAdd": ["fake", "12345"]
} }
}` }`
var expected Container var expected Container
@ -1371,6 +1372,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,
@ -1426,17 +1428,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())
@ -1584,7 +1629,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)
@ -1594,7 +1638,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

View File

@ -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

View File

@ -38,16 +38,16 @@ type CreateExecOptions struct {
// See https://goo.gl/1KSIb7 for more details // See https://goo.gl/1KSIb7 for more details
func (c *Client) CreateExec(opts CreateExecOptions) (*Exec, error) { func (c *Client) CreateExec(opts CreateExecOptions) (*Exec, error) {
path := fmt.Sprintf("/containers/%s/exec", opts.Container) path := fmt.Sprintf("/containers/%s/exec", opts.Container)
body, 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 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 exec Exec var exec Exec
err = json.Unmarshal(body, &exec) if err := json.NewDecoder(resp.Body).Decode(&exec); err != nil {
if err != nil {
return nil, err return nil, err
} }
@ -90,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
} }
@ -121,9 +122,13 @@ func (c *Client) ResizeExecTTY(id string, height, width int) error {
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 // ExecProcessConfig is a type describing the command associated to a Exec
// instance. It's used in the ExecInspect type. // instance. It's used in the ExecInspect type.
@ -156,16 +161,16 @@ type ExecInspect struct {
// See https://goo.gl/gPtX9R 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

View File

@ -96,13 +96,13 @@ type ListImagesOptions struct {
// See https://goo.gl/xBe1u3 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
@ -122,16 +122,16 @@ type ImageHistory struct {
// //
// See https://goo.gl/8bnTId 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
@ -141,12 +141,16 @@ func (c *Client) ImageHistory(name string) ([]ImageHistory, error) {
// //
// See https://goo.gl/V3ZWnK 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.
@ -163,37 +167,40 @@ type RemoveImageOptions struct {
// See https://goo.gl/V3ZWnK 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 https://goo.gl/jHPcg6 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
} }
@ -409,6 +416,7 @@ type BuildImageOptions struct {
Auth AuthConfiguration `qs:"-"` // for older docker X-Registry-Auth header Auth AuthConfiguration `qs:"-"` // for older docker X-Registry-Auth header
AuthConfigs AuthConfigurations `qs:"-"` // for newer docker X-Registry-Config header AuthConfigs AuthConfigurations `qs:"-"` // for newer docker X-Registry-Config header
ContextDir string `qs:"-"` ContextDir string `qs:"-"`
Ulimits []ULimit `qs:"-"`
} }
// 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
@ -442,7 +450,16 @@ func (c *Client) BuildImage(opts BuildImageOptions) error {
} }
} }
return c.stream("POST", fmt.Sprintf("/build?%s", queryString(&opts)), streamOptions{ qs := queryString(&opts)
if len(opts.Ulimits) > 0 {
if b, err := json.Marshal(opts.Ulimits); err == nil {
item := url.Values(map[string][]string{})
item.Add("ulimits", string(b))
qs = fmt.Sprintf("%s&%s", qs, item.Encode())
}
}
return c.stream("POST", fmt.Sprintf("/build?%s", qs), streamOptions{
setRawTerminal: true, setRawTerminal: true,
rawJSONStream: opts.RawJSONStream, rawJSONStream: opts.RawJSONStream,
headers: headers, headers: headers,
@ -477,10 +494,11 @@ 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
} }
@ -533,13 +551,13 @@ type APIImageSearch struct {
// //
// See https://goo.gl/AYjyrF 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

View File

@ -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"
@ -24,6 +25,7 @@ func newTestClient(rt *FakeRoundTripper) Client {
testAPIVersion, _ := NewAPIVersion("1.17") 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,
@ -680,6 +682,7 @@ func TestBuildImageParameters(t *testing.T) {
Memswap: 2048, Memswap: 2048,
CPUShares: 10, CPUShares: 10,
CPUSetCPUs: "0-3", CPUSetCPUs: "0-3",
Ulimits: []ULimit{ULimit{Name: "nofile", Soft: 100, Hard: 200}},
InputStream: &buf, InputStream: &buf,
OutputStream: &buf, OutputStream: &buf,
} }
@ -699,6 +702,7 @@ func TestBuildImageParameters(t *testing.T) {
"memswap": {"2048"}, "memswap": {"2048"},
"cpushares": {"10"}, "cpushares": {"10"},
"cpusetcpus": {"0-3"}, "cpusetcpus": {"0-3"},
"ulimits": {"[{\"Name\":\"nofile\",\"Soft\":100,\"Hard\":200}]"},
} }
got := map[string][]string(req.URL.Query()) got := map[string][]string(req.URL.Query())
if !reflect.DeepEqual(got, expected) { if !reflect.DeepEqual(got, expected) {

View File

@ -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 https://goo.gl/ND9R8L 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
@ -28,13 +26,13 @@ func (c *Client) Version() (*Env, error) {
// //
// See https://goo.gl/ElTHi2 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

View File

@ -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

View File

@ -1 +0,0 @@
doesnotexist

View File

@ -957,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()
@ -979,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
} }
@ -988,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)
@ -997,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
} }
} }

View File

@ -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)
}

View File

@ -38,12 +38,13 @@ type ListVolumesOptions struct {
// //
// See https://goo.gl/FZA4BK for more details. // See https://goo.gl/FZA4BK for more details.
func (c *Client) ListVolumes(opts ListVolumesOptions) ([]Volume, error) { func (c *Client) ListVolumes(opts ListVolumesOptions) ([]Volume, error) {
body, _, err := c.do("GET", "/volumes?"+queryString(opts), doOptions{}) resp, err := c.do("GET", "/volumes?"+queryString(opts), doOptions{})
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer resp.Body.Close()
m := make(map[string]interface{}) m := make(map[string]interface{})
if err := json.Unmarshal(body, &m); err != nil { if err := json.NewDecoder(resp.Body).Decode(&m); err != nil {
return nil, err return nil, err
} }
var volumes []Volume var volumes []Volume
@ -74,12 +75,13 @@ type CreateVolumeOptions struct {
// //
// See https://goo.gl/pBUbZ9 for more details. // See https://goo.gl/pBUbZ9 for more details.
func (c *Client) CreateVolume(opts CreateVolumeOptions) (*Volume, error) { func (c *Client) CreateVolume(opts CreateVolumeOptions) (*Volume, error) {
body, _, err := c.do("POST", "/volumes", doOptions{data: opts}) resp, err := c.do("POST", "/volumes", doOptions{data: opts})
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer resp.Body.Close()
var volume Volume var volume Volume
if err := json.Unmarshal(body, &volume); err != nil { if err := json.NewDecoder(resp.Body).Decode(&volume); err != nil {
return nil, err return nil, err
} }
return &volume, nil return &volume, nil
@ -89,15 +91,16 @@ func (c *Client) CreateVolume(opts CreateVolumeOptions) (*Volume, error) {
// //
// See https://goo.gl/0g9A6i for more details. // See https://goo.gl/0g9A6i for more details.
func (c *Client) InspectVolume(name string) (*Volume, error) { func (c *Client) InspectVolume(name string) (*Volume, error) {
body, status, err := c.do("GET", "/volumes/"+name, doOptions{}) resp, err := c.do("GET", "/volumes/"+name, doOptions{})
if status == http.StatusNotFound { if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return nil, ErrNoSuchVolume return nil, ErrNoSuchVolume
} }
if err != nil {
return nil, err return nil, err
} }
defer resp.Body.Close()
var volume Volume var volume Volume
if err := json.Unmarshal(body, &volume); err != nil { if err := json.NewDecoder(resp.Body).Decode(&volume); err != nil {
return nil, err return nil, err
} }
return &volume, nil return &volume, nil
@ -107,12 +110,18 @@ func (c *Client) InspectVolume(name string) (*Volume, error) {
// //
// See https://goo.gl/79GNQz for more details. // See https://goo.gl/79GNQz for more details.
func (c *Client) RemoveVolume(name string) error { func (c *Client) RemoveVolume(name string) error {
_, status, err := c.do("DELETE", "/volumes/"+name, doOptions{}) resp, err := c.do("DELETE", "/volumes/"+name, doOptions{})
if status == http.StatusNotFound { if err != nil {
if e, ok := err.(*Error); ok {
if e.Status == http.StatusNotFound {
return ErrNoSuchVolume return ErrNoSuchVolume
} }
if status == http.StatusConflict { if e.Status == http.StatusConflict {
return ErrVolumeInUse return ErrVolumeInUse
} }
return err }
return nil
}
defer resp.Body.Close()
return nil
} }