From 5bcf2324a96b52acc0ddb6549edeefeff0655473 Mon Sep 17 00:00:00 2001 From: Sami Wagiaalla Date: Mon, 5 Oct 2015 21:12:29 -0400 Subject: [PATCH] Bump fsouza/go-dockerclient --- Godeps/Godeps.json | 2 +- .../fsouza/go-dockerclient/.gitignore | 2 + .../fsouza/go-dockerclient/.travis.yml | 6 +- .../github.com/fsouza/go-dockerclient/AUTHORS | 7 + .../fsouza/go-dockerclient/README.markdown | 2 +- .../github.com/fsouza/go-dockerclient/auth.go | 6 +- .../fsouza/go-dockerclient/build_test.go | 10 + .../fsouza/go-dockerclient/client.go | 144 ++++++---- .../fsouza/go-dockerclient/client_test.go | 44 ++- .../fsouza/go-dockerclient/container.go | 260 +++++++++++------- .../fsouza/go-dockerclient/container_test.go | 54 +++- .../fsouza/go-dockerclient/event.go | 5 +- .../github.com/fsouza/go-dockerclient/exec.go | 41 +-- .../fsouza/go-dockerclient/image.go | 80 +++--- .../fsouza/go-dockerclient/image_test.go | 4 + .../github.com/fsouza/go-dockerclient/misc.go | 16 +- .../fsouza/go-dockerclient/network.go | 33 +-- .../go-dockerclient/testing/data/symlink | 1 - .../fsouza/go-dockerclient/testing/server.go | 12 +- .../github.com/fsouza/go-dockerclient/tls.go | 4 - .../fsouza/go-dockerclient/volume.go | 41 +-- 21 files changed, 501 insertions(+), 273 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/fsouza/go-dockerclient/.gitignore delete mode 120000 Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/data/symlink diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 71d907b549c..a539b6deb12 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -238,7 +238,7 @@ }, { "ImportPath": "github.com/fsouza/go-dockerclient", - "Rev": "76fd6c68cf24c48ee6a2b25def997182a29f940e" + "Rev": "1399676f53e6ccf46e0bf00751b21bed329bc60e" }, { "ImportPath": "github.com/garyburd/redigo/internal", diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/.gitignore b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/.gitignore new file mode 100644 index 00000000000..5f6b48eae0d --- /dev/null +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/.gitignore @@ -0,0 +1,2 @@ +# temporary symlink for testing +testing/data/symlink diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/.travis.yml b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/.travis.yml index e03407b3e79..927a999f084 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/.travis.yml +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/.travis.yml @@ -1,9 +1,9 @@ language: go sudo: false go: - - 1.3.1 - - 1.4 - - 1.5 + - 1.3.3 + - 1.4.2 + - 1.5.1 - tip env: - GOARCH=amd64 diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/AUTHORS b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/AUTHORS index a149a7723c8..7abd05b14c2 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/AUTHORS +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/AUTHORS @@ -1,6 +1,7 @@ # This is the official list of go-dockerclient authors for copyright purposes. Adam Bell-Hanssen +Adrien Kohlbecker Aldrin Leal Andreas Jaekle Andrews Medina @@ -11,6 +12,7 @@ Ben McCann Brendan Fosberry Brian Lalor Brian Palmer +Bryan Boreham Burke Libbey Carlos Diaz-Padron Cesar Wong @@ -28,6 +30,7 @@ David Huie Dawn Chen Dinesh Subhraveti Ed +Erez Horev Eric Anderson Ewout Prangsma Fabio Rehm @@ -45,6 +48,7 @@ Jawher Moussa Jean-Baptiste Dalido Jeff Mitchell Jeffrey Hulten +Jen Andre Johan Euphrosine Kamil Domanski Karan Misra @@ -52,6 +56,7 @@ Kim, Hirokuni Kyle Allan Liron Levin Liu Peng +Lorenz Leutgeb Lucas Clemente Lucas Weiblen Mantas Matelis @@ -75,7 +80,9 @@ Rob Miller Robert Williamson Salvador Gironès Sam Rijs +Sami Wagiaalla Samuel Karp +Silas Sewell Simon Eskildsen Simon Menke Skolos diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/README.markdown b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/README.markdown index a124d0b45e0..9f3c0ca1bac 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/README.markdown +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/README.markdown @@ -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) 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 passthrough to the libnetwork remote API. Note that docker's network API is diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/auth.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/auth.go index d2af8780c2a..30e3af3eb7a 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/auth.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/auth.go @@ -127,12 +127,10 @@ func (c *Client) AuthCheck(conf *AuthConfiguration) error { if conf == 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 { return err } - if statusCode > 400 { - return fmt.Errorf("auth error (%d): %s", statusCode, body) - } + resp.Body.Close() return nil } diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/build_test.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/build_test.go index a4864db8370..c9640f20571 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/build_test.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/build_test.go @@ -35,6 +35,16 @@ func TestBuildImageMultipleContextsError(t *testing.T) { func TestBuildImageContextDirDockerignoreParsing(t *testing.T) { fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} 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 opts := BuildImageOptions{ Name: "testImage", diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/client.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/client.go index c3d86f40b8a..0129eb334f4 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/client.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/client.go @@ -130,6 +130,7 @@ type Client struct { SkipServerVersionCheck bool HTTPClient *http.Client TLSConfig *tls.Config + Dialer *net.Dialer endpoint string endpointURL *url.URL @@ -137,6 +138,7 @@ type Client struct { requestedAPIVersion APIVersion serverAPIVersion APIVersion expectedAPIVersion APIVersion + unixHTTPClient *http.Client } // 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{ HTTPClient: http.DefaultClient, + Dialer: &net.Dialer{}, endpoint: endpoint, endpointURL: u, eventMonitor: new(eventMonitoringState), @@ -302,6 +305,7 @@ func NewVersionedTLSClientFromBytes(endpoint string, certPEMBlock, keyPEMBlock, return &Client{ HTTPClient: &http.Client{Transport: tr}, TLSConfig: tlsConfig, + Dialer: &net.Dialer{}, endpoint: endpoint, endpointURL: u, eventMonitor: new(eventMonitoringState), @@ -326,32 +330,40 @@ func (c *Client) checkAPIVersion() error { 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 // // See https://goo.gl/kQCfJj for more details. func (c *Client) Ping() error { path := "/_ping" - body, status, err := c.do("GET", path, doOptions{}) + resp, err := c.do("GET", path, doOptions{}) if err != nil { return err } - if status != http.StatusOK { - return newError(status, body) + if resp.StatusCode != http.StatusOK { + return newError(resp) } + resp.Body.Close() return nil } 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 { return "", err } - if status != http.StatusOK { - return "", fmt.Errorf("Received unexpected status %d while trying to retrieve the server version", status) + defer resp.Body.Close() + 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{} - err = json.Unmarshal(body, &versionResponse) - if err != nil { + if err := json.NewDecoder(resp.Body).Decode(&versionResponse); err != nil { return "", err } if version, ok := (versionResponse["ApiVersion"]).(string); ok { @@ -365,24 +377,35 @@ type doOptions struct { 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 if doOptions.data != nil || doOptions.forceJSON { buf, err := json.Marshal(doOptions.data) if err != nil { - return nil, -1, err + return nil, err } params = bytes.NewBuffer(buf) } if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil { err := c.checkAPIVersion() 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 { - return nil, -1, err + return nil, err } req.Header.Set("User-Agent", userAgent) if doOptions.data != nil { @@ -390,40 +413,19 @@ func (c *Client) do(method, path string, doOptions doOptions) ([]byte, int, erro } else if method == "POST" { req.Header.Set("Content-Type", "plain/text") } - var resp *http.Response - protocol := c.endpointURL.Scheme - 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) - } + + resp, err := httpClient.Do(req) if err != nil { if strings.Contains(err.Error(), "connection refused") { - return nil, -1, ErrConnectionRefused + return nil, ErrConnectionRefused } - return nil, -1, err - } - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, -1, err + return nil, err } + 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 { @@ -464,16 +466,12 @@ func (c *Client) stream(method, path string, streamOptions streamOptions) error address := c.endpointURL.Path if streamOptions.stdout == nil { streamOptions.stdout = ioutil.Discard - } else if t, ok := streamOptions.stdout.(io.Closer); ok { - defer t.Close() } if streamOptions.stderr == nil { streamOptions.stderr = ioutil.Discard - } else if t, ok := streamOptions.stderr.(io.Closer); ok { - defer t.Close() } if protocol == "unix" { - dial, err := net.Dial(protocol, address) + dial, err := c.Dialer.Dial(protocol, address) if err != nil { return err } @@ -509,11 +507,7 @@ func (c *Client) stream(method, path string, streamOptions streamOptions) error } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 400 { - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return err - } - return newError(resp.StatusCode, body) + return newError(resp) } if streamOptions.useJSONDecoder || resp.Header.Get("Content-Type") == "application/json" { // 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 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 { return err } } else { - dial, err = net.Dial(protocol, address) + dial, err = c.Dialer.Dial(protocol, address) if err != nil { return err } @@ -666,6 +660,41 @@ func (c *Client) getURL(path string) string { 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 { Status string `json:"status,omitempty"` Progress string `json:"progress,omitempty"` @@ -747,8 +776,13 @@ type Error struct { Message string } -func newError(status int, body []byte) *Error { - return &Error{Status: status, Message: string(body)} +func newError(resp *http.Response) *Error { + 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 { diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/client_test.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/client_test.go index c00c3d30c8e..67230a4e15c 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/client_test.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/client_test.go @@ -5,6 +5,7 @@ package docker import ( + "bytes" "fmt" "io/ioutil" "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) { var tests = []struct { 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) { - 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"} if !reflect.DeepEqual(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() 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() @@ -345,7 +382,7 @@ func TestPingErrorWithUnixSocket(t *testing.T) { buf := make([]byte, 512) nr, err := fd.Read(buf) - // Create invalid response message to occur error + // Create invalid response message to trigger error. data := buf[0:nr] for i := 0; i < 10; i++ { data[i] = 63 @@ -366,6 +403,7 @@ func TestPingErrorWithUnixSocket(t *testing.T) { u, _ := parseEndpoint(endpoint, false) client := Client{ HTTPClient: http.DefaultClient, + Dialer: &net.Dialer{}, endpoint: endpoint, endpointURL: u, SkipServerVersionCheck: true, diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/container.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/container.go index b74a992c0af..faf12632f0b 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/container.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/container.go @@ -5,7 +5,6 @@ package docker import ( - "bytes" "encoding/json" "errors" "fmt" @@ -61,13 +60,13 @@ type APIContainers struct { // See https://goo.gl/47a6tO for more details. func (c *Client) ListContainers(opts ListContainersOptions) ([]APIContainers, error) { path := "/containers/json?" + queryString(opts) - body, _, err := c.do("GET", path, doOptions{}) + resp, err := c.do("GET", path, doOptions{}) if err != nil { return nil, err } + defer resp.Body.Close() var containers []APIContainers - err = json.Unmarshal(body, &containers) - if err != nil { + if err := json.NewDecoder(resp.Body).Decode(&containers); err != nil { return nil, err } 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 Image string `json:"Image,omitempty" yaml:"Image,omitempty"` Volumes map[string]struct{} `json:"Volumes,omitempty" yaml:"Volumes,omitempty"` + VolumeDriver string `json:"VolumeDriver,omitempty" yaml:"VolumeDriver,omitempty"` VolumesFrom string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"` WorkingDir string `json:"WorkingDir,omitempty" yaml:"WorkingDir,omitempty"` MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty"` @@ -271,13 +271,14 @@ type Container struct { NetworkSettings *NetworkSettings `json:"NetworkSettings,omitempty" yaml:"NetworkSettings,omitempty"` - SysInitPath string `json:"SysInitPath,omitempty" yaml:"SysInitPath,omitempty"` - ResolvConfPath string `json:"ResolvConfPath,omitempty" yaml:"ResolvConfPath,omitempty"` - HostnamePath string `json:"HostnamePath,omitempty" yaml:"HostnamePath,omitempty"` - HostsPath string `json:"HostsPath,omitempty" yaml:"HostsPath,omitempty"` - LogPath string `json:"LogPath,omitempty" yaml:"LogPath,omitempty"` - Name string `json:"Name,omitempty" yaml:"Name,omitempty"` - Driver string `json:"Driver,omitempty" yaml:"Driver,omitempty"` + SysInitPath string `json:"SysInitPath,omitempty" yaml:"SysInitPath,omitempty"` + ResolvConfPath string `json:"ResolvConfPath,omitempty" yaml:"ResolvConfPath,omitempty"` + HostnamePath string `json:"HostnamePath,omitempty" yaml:"HostnamePath,omitempty"` + HostsPath string `json:"HostsPath,omitempty" yaml:"HostsPath,omitempty"` + LogPath string `json:"LogPath,omitempty" yaml:"LogPath,omitempty"` + Name string `json:"Name,omitempty" yaml:"Name,omitempty"` + Driver string `json:"Driver,omitempty" yaml:"Driver,omitempty"` + Mounts []Mount `json:"Mounts,omitempty" yaml:"Mounts,omitempty"` Volumes map[string]string `json:"Volumes,omitempty" yaml:"Volumes,omitempty"` VolumesRW map[string]bool `json:"VolumesRW,omitempty" yaml:"VolumesRW,omitempty"` @@ -304,8 +305,12 @@ type RenameContainerOptions struct { // // See https://goo.gl/laSOIy for more details. func (c *Client) RenameContainer(opts RenameContainerOptions) error { - _, _, err := c.do("POST", fmt.Sprintf("/containers/"+opts.ID+"/rename?%s", queryString(opts)), doOptions{}) - return err + resp, err := c.do("POST", fmt.Sprintf("/containers/"+opts.ID+"/rename?%s", queryString(opts)), doOptions{}) + if err != nil { + return err + } + resp.Body.Close() + return nil } // InspectContainer returns information about a container by its ID. @@ -313,16 +318,16 @@ func (c *Client) RenameContainer(opts RenameContainerOptions) error { // See https://goo.gl/RdIq0b for more details. func (c *Client) InspectContainer(id string) (*Container, error) { path := "/containers/" + id + "/json" - body, status, err := c.do("GET", path, doOptions{}) - if status == http.StatusNotFound { - return nil, &NoSuchContainer{ID: id} - } + resp, err := c.do("GET", path, doOptions{}) if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return nil, &NoSuchContainer{ID: id} + } return nil, err } + defer resp.Body.Close() var container Container - err = json.Unmarshal(body, &container) - if err != nil { + if err := json.NewDecoder(resp.Body).Decode(&container); err != nil { return nil, err } return &container, nil @@ -333,16 +338,16 @@ func (c *Client) InspectContainer(id string) (*Container, error) { // See https://goo.gl/9GsTIF for more details. func (c *Client) ContainerChanges(id string) ([]Change, error) { path := "/containers/" + id + "/changes" - body, status, err := c.do("GET", path, doOptions{}) - if status == http.StatusNotFound { - return nil, &NoSuchContainer{ID: id} - } + resp, err := c.do("GET", path, doOptions{}) if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return nil, &NoSuchContainer{ID: id} + } return nil, err } + defer resp.Body.Close() var changes []Change - err = json.Unmarshal(body, &changes) - if err != nil { + if err := json.NewDecoder(resp.Body).Decode(&changes); err != nil { return nil, err } return changes, nil @@ -363,7 +368,7 @@ type CreateContainerOptions struct { // See https://goo.gl/WxQzrr for more details. func (c *Client) CreateContainer(opts CreateContainerOptions) (*Container, error) { path := "/containers/create?" + queryString(opts) - body, status, err := c.do( + resp, err := c.do( "POST", path, doOptions{ @@ -377,18 +382,21 @@ func (c *Client) CreateContainer(opts CreateContainerOptions) (*Container, error }, ) - if status == http.StatusNotFound { - return nil, ErrNoSuchImage - } - if status == http.StatusConflict { - return nil, ErrContainerAlreadyExists + if e, ok := err.(*Error); ok { + if e.Status == http.StatusNotFound { + return nil, ErrNoSuchImage + } + if e.Status == http.StatusConflict { + return nil, ErrContainerAlreadyExists + } } + if err != nil { return nil, err } + defer resp.Body.Close() var container Container - err = json.Unmarshal(body, &container) - if err != nil { + if err := json.NewDecoder(resp.Body).Decode(&container); err != nil { return nil, err } @@ -449,6 +457,7 @@ type HostConfig struct { Binds []string `json:"Binds,omitempty" yaml:"Binds,omitempty"` CapAdd []string `json:"CapAdd,omitempty" yaml:"CapAdd,omitempty"` CapDrop []string `json:"CapDrop,omitempty" yaml:"CapDrop,omitempty"` + GroupAdd []string `json:"GroupAdd,omitempty" yaml:"GroupAdd,omitempty"` ContainerIDFile string `json:"ContainerIDFile,omitempty" yaml:"ContainerIDFile,omitempty"` LxcConf []KeyValuePair `json:"LxcConf,omitempty" yaml:"LxcConf,omitempty"` Privileged bool `json:"Privileged,omitempty" yaml:"Privileged,omitempty"` @@ -488,16 +497,17 @@ type HostConfig struct { // See https://goo.gl/MrBAJv for more details. func (c *Client) StartContainer(id string, hostConfig *HostConfig) error { path := "/containers/" + id + "/start" - _, status, err := c.do("POST", path, doOptions{data: hostConfig, forceJSON: true}) - if status == http.StatusNotFound { - return &NoSuchContainer{ID: id, Err: err} - } - if status == http.StatusNotModified { - return &ContainerAlreadyRunning{ID: id} - } + resp, err := c.do("POST", path, doOptions{data: hostConfig, forceJSON: true}) if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchContainer{ID: id, Err: err} + } return err } + if resp.StatusCode == http.StatusNotModified { + return &ContainerAlreadyRunning{ID: id} + } + resp.Body.Close() return nil } @@ -507,16 +517,17 @@ func (c *Client) StartContainer(id string, hostConfig *HostConfig) error { // See https://goo.gl/USqsFt for more details. func (c *Client) StopContainer(id string, timeout uint) error { path := fmt.Sprintf("/containers/%s/stop?t=%d", id, timeout) - _, status, err := c.do("POST", path, doOptions{}) - if status == http.StatusNotFound { - return &NoSuchContainer{ID: id} - } - if status == http.StatusNotModified { - return &ContainerNotRunning{ID: id} - } + resp, err := c.do("POST", path, doOptions{}) if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchContainer{ID: id} + } return err } + if resp.StatusCode == http.StatusNotModified { + return &ContainerNotRunning{ID: id} + } + resp.Body.Close() return nil } @@ -526,13 +537,14 @@ func (c *Client) StopContainer(id string, timeout uint) error { // See https://goo.gl/QzsDnz for more details. func (c *Client) RestartContainer(id string, timeout uint) error { path := fmt.Sprintf("/containers/%s/restart?t=%d", id, timeout) - _, status, err := c.do("POST", path, doOptions{}) - if status == http.StatusNotFound { - return &NoSuchContainer{ID: id} - } + resp, err := c.do("POST", path, doOptions{}) if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchContainer{ID: id} + } return err } + resp.Body.Close() return nil } @@ -541,13 +553,14 @@ func (c *Client) RestartContainer(id string, timeout uint) error { // See https://goo.gl/OF7W9X for more details. func (c *Client) PauseContainer(id string) error { path := fmt.Sprintf("/containers/%s/pause", id) - _, status, err := c.do("POST", path, doOptions{}) - if status == http.StatusNotFound { - return &NoSuchContainer{ID: id} - } + resp, err := c.do("POST", path, doOptions{}) if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchContainer{ID: id} + } return err } + resp.Body.Close() return nil } @@ -556,13 +569,14 @@ func (c *Client) PauseContainer(id string) error { // See https://goo.gl/7dwyPA for more details. func (c *Client) UnpauseContainer(id string) error { path := fmt.Sprintf("/containers/%s/unpause", id) - _, status, err := c.do("POST", path, doOptions{}) - if status == http.StatusNotFound { - return &NoSuchContainer{ID: id} - } + resp, err := c.do("POST", path, doOptions{}) if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchContainer{ID: id} + } return err } + resp.Body.Close() return nil } @@ -585,15 +599,15 @@ func (c *Client) TopContainer(id string, psArgs string) (TopResult, error) { args = fmt.Sprintf("?ps_args=%s", psArgs) } path := fmt.Sprintf("/containers/%s/top%s", id, args) - body, status, err := c.do("GET", path, doOptions{}) - if status == http.StatusNotFound { - return result, &NoSuchContainer{ID: id} - } + resp, err := c.do("GET", path, doOptions{}) if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return result, &NoSuchContainer{ID: id} + } return result, err } - err = json.Unmarshal(body, &result) - if err != nil { + defer resp.Body.Close() + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return result, err } return result, nil @@ -768,7 +782,7 @@ func (c *Client) Stats(opts StatsOptions) (retErr error) { decoder := json.NewDecoder(readCloser) 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 { return err } @@ -797,13 +811,14 @@ type KillContainerOptions struct { // See https://goo.gl/hkS9i8 for more details. func (c *Client) KillContainer(opts KillContainerOptions) error { path := "/containers/" + opts.ID + "/kill" + "?" + queryString(opts) - _, status, err := c.do("POST", path, doOptions{}) - if status == http.StatusNotFound { - return &NoSuchContainer{ID: opts.ID} - } + resp, err := c.do("POST", path, doOptions{}) if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchContainer{ID: opts.ID} + } return err } + resp.Body.Close() return nil } @@ -828,43 +843,86 @@ type RemoveContainerOptions struct { // See https://goo.gl/RQyX62 for more details. func (c *Client) RemoveContainer(opts RemoveContainerOptions) error { path := "/containers/" + opts.ID + "?" + queryString(opts) - _, status, err := c.do("DELETE", path, doOptions{}) - if status == http.StatusNotFound { - return &NoSuchContainer{ID: opts.ID} - } + resp, err := c.do("DELETE", path, doOptions{}) if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchContainer{ID: opts.ID} + } return err } + resp.Body.Close() return nil } -// CopyFromContainerOptions is the set of options that can be used when copying -// files or folders from a container. +// UploadToContainerOptions is the set of options that can be used when +// 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 { OutputStream io.Writer `json:"-"` Container string `json:"-"` Resource string } -// CopyFromContainer copy files or folders from a container, using a given -// resource. +// CopyFromContainer has been DEPRECATED, please use DownloadFromContainerOptions along with DownloadFromContainer. // -// See https://goo.gl/4L7b07 for more details. +// See https://goo.gl/R2jevW for more details. func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error { if opts.Container == "" { return &NoSuchContainer{ID: opts.Container} } url := fmt.Sprintf("/containers/%s/copy", opts.Container) - body, status, err := c.do("POST", url, doOptions{data: opts}) - if status == http.StatusNotFound { - return &NoSuchContainer{ID: opts.Container} - } + resp, err := c.do("POST", url, doOptions{data: opts}) if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchContainer{ID: opts.Container} + } return err } - _, err = io.Copy(opts.OutputStream, bytes.NewBuffer(body)) + defer resp.Body.Close() + _, err = io.Copy(opts.OutputStream, resp.Body) return err } @@ -873,16 +931,16 @@ func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error { // // See https://goo.gl/Gc1rge for more details. func (c *Client) WaitContainer(id string) (int, error) { - body, status, err := c.do("POST", "/containers/"+id+"/wait", doOptions{}) - if status == http.StatusNotFound { - return 0, &NoSuchContainer{ID: id} - } + resp, err := c.do("POST", "/containers/"+id+"/wait", doOptions{}) if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return 0, &NoSuchContainer{ID: id} + } return 0, err } + defer resp.Body.Close() var r struct{ StatusCode int } - err = json.Unmarshal(body, &r) - if err != nil { + if err := json.NewDecoder(resp.Body).Decode(&r); err != nil { return 0, err } return r.StatusCode, nil @@ -905,16 +963,16 @@ type CommitContainerOptions struct { // See https://goo.gl/mqfoCw for more details. func (c *Client) CommitContainer(opts CommitContainerOptions) (*Image, error) { path := "/commit?" + queryString(opts) - body, status, err := c.do("POST", path, doOptions{data: opts.Run}) - if status == http.StatusNotFound { - return nil, &NoSuchContainer{ID: opts.Container} - } + resp, err := c.do("POST", path, doOptions{data: opts.Run}) if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return nil, &NoSuchContainer{ID: opts.Container} + } return nil, err } + defer resp.Body.Close() var image Image - err = json.Unmarshal(body, &image) - if err != nil { + if err := json.NewDecoder(resp.Body).Decode(&image); err != nil { return nil, err } return &image, nil @@ -1017,8 +1075,12 @@ func (c *Client) ResizeContainerTTY(id string, height, width int) error { params := make(url.Values) params.Set("h", strconv.Itoa(height)) params.Set("w", strconv.Itoa(width)) - _, _, err := c.do("POST", "/containers/"+id+"/resize?"+params.Encode(), doOptions{}) - return err + resp, err := c.do("POST", "/containers/"+id+"/resize?"+params.Encode(), doOptions{}) + if err != nil { + return err + } + resp.Body.Close() + return nil } // ExportContainerOptions is the set of parameters to the ExportContainer diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/container_test.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/container_test.go index d9dbea86fdb..ea7fad2f6f7 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/container_test.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/container_test.go @@ -238,7 +238,8 @@ func TestInspectContainer(t *testing.T) { "PublishAllPorts": false, "CgroupParent": "/mesos", "Memory": 17179869184, - "MemorySwap": 34359738368 + "MemorySwap": 34359738368, + "GroupAdd": ["fake", "12345"] } }` var expected Container @@ -1371,6 +1372,7 @@ func TestExportContainerViaUnixSocket(t *testing.T) { u, _ := parseEndpoint(endpoint, false) client := Client{ HTTPClient: http.DefaultClient, + Dialer: &net.Dialer{}, endpoint: endpoint, endpointURL: u, 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) { content := "File content" out := stdoutMock{bytes.NewBufferString(content)} client := newTestClient(&FakeRoundTripper{status: http.StatusOK}) opts := CopyFromContainerOptions{ Container: "a123456", - OutputStream: out, + OutputStream: &out, } err := client.CopyFromContainer(opts) 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 { 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) { - l, err := net.Listen("unix", "/tmp/docker_test.sock") if err != nil { t.Fatal(err) @@ -1594,7 +1638,7 @@ func TestStatsTimeout(t *testing.T) { go func() { l.Accept() received = true - time.Sleep(time.Millisecond * 250) + time.Sleep(time.Second) }() client, _ := NewClient("unix:///tmp/docker_test.sock") client.SkipServerVersionCheck = true diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/event.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/event.go index 5a85983cc89..eaffddb825f 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/event.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/event.go @@ -5,7 +5,6 @@ package docker import ( - "crypto/tls" "encoding/json" "errors" "fmt" @@ -260,9 +259,9 @@ func (c *Client) eventHijack(startTime int64, eventChan chan *APIEvents, errChan var dial net.Conn var err error if c.TLSConfig == nil { - dial, err = net.Dial(protocol, address) + dial, err = c.Dialer.Dial(protocol, address) } else { - dial, err = tls.Dial(protocol, address, c.TLSConfig) + dial, err = tlsDialWithDialer(c.Dialer, protocol, address, c.TLSConfig) } if err != nil { return err diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/exec.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/exec.go index 84047f04efb..f3b705fa0a7 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/exec.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/exec.go @@ -38,16 +38,16 @@ type CreateExecOptions struct { // See https://goo.gl/1KSIb7 for more details func (c *Client) CreateExec(opts CreateExecOptions) (*Exec, error) { path := fmt.Sprintf("/containers/%s/exec", opts.Container) - body, status, err := c.do("POST", path, doOptions{data: opts}) - if status == http.StatusNotFound { - return nil, &NoSuchContainer{ID: opts.Container} - } + 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 - err = json.Unmarshal(body, &exec) - if err != nil { + if err := json.NewDecoder(resp.Body).Decode(&exec); err != nil { return nil, err } @@ -90,13 +90,14 @@ func (c *Client) StartExec(id string, opts StartExecOptions) error { path := fmt.Sprintf("/exec/%s/start", id) if opts.Detach { - _, status, err := c.do("POST", path, doOptions{data: opts}) - if status == http.StatusNotFound { - return &NoSuchExec{ID: id} - } + resp, err := c.do("POST", path, doOptions{data: opts}) if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchExec{ID: id} + } return err } + defer resp.Body.Close() return nil } @@ -121,8 +122,12 @@ func (c *Client) ResizeExecTTY(id string, height, width int) error { params.Set("w", strconv.Itoa(width)) path := fmt.Sprintf("/exec/%s/resize?%s", id, params.Encode()) - _, _, err := c.do("POST", path, doOptions{}) - return err + resp, err := c.do("POST", path, doOptions{}) + if err != nil { + return err + } + resp.Body.Close() + return nil } // ExecProcessConfig is a type describing the command associated to a Exec @@ -156,16 +161,16 @@ type ExecInspect struct { // See https://goo.gl/gPtX9R for more details func (c *Client) InspectExec(id string) (*ExecInspect, error) { path := fmt.Sprintf("/exec/%s/json", id) - body, status, err := c.do("GET", path, doOptions{}) - if status == http.StatusNotFound { - return nil, &NoSuchExec{ID: id} - } + resp, err := c.do("GET", path, doOptions{}) if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return nil, &NoSuchExec{ID: id} + } return nil, err } + defer resp.Body.Close() var exec ExecInspect - err = json.Unmarshal(body, &exec) - if err != nil { + if err := json.NewDecoder(resp.Body).Decode(&exec); err != nil { return nil, err } return &exec, nil diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/image.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/image.go index f8be846901f..87c1e19b1e1 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/image.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/image.go @@ -96,13 +96,13 @@ type ListImagesOptions struct { // See https://goo.gl/xBe1u3 for more details. func (c *Client) ListImages(opts ListImagesOptions) ([]APIImages, error) { path := "/images/json?" + queryString(opts) - body, _, err := c.do("GET", path, doOptions{}) + resp, err := c.do("GET", path, doOptions{}) if err != nil { return nil, err } + defer resp.Body.Close() var images []APIImages - err = json.Unmarshal(body, &images) - if err != nil { + if err := json.NewDecoder(resp.Body).Decode(&images); err != nil { return nil, err } return images, nil @@ -122,16 +122,16 @@ type ImageHistory struct { // // See https://goo.gl/8bnTId for more details. func (c *Client) ImageHistory(name string) ([]ImageHistory, error) { - body, status, err := c.do("GET", "/images/"+name+"/history", doOptions{}) - if status == http.StatusNotFound { - return nil, ErrNoSuchImage - } + resp, err := c.do("GET", "/images/"+name+"/history", doOptions{}) if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return nil, ErrNoSuchImage + } return nil, err } + defer resp.Body.Close() var history []ImageHistory - err = json.Unmarshal(body, &history) - if err != nil { + if err := json.NewDecoder(resp.Body).Decode(&history); err != nil { return nil, err } return history, nil @@ -141,11 +141,15 @@ func (c *Client) ImageHistory(name string) ([]ImageHistory, error) { // // See https://goo.gl/V3ZWnK for more details. func (c *Client) RemoveImage(name string) error { - _, status, err := c.do("DELETE", "/images/"+name, doOptions{}) - if status == http.StatusNotFound { - return ErrNoSuchImage + resp, err := c.do("DELETE", "/images/"+name, doOptions{}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return ErrNoSuchImage + } + return err } - return err + resp.Body.Close() + return nil } // RemoveImageOptions present the set of options available for removing an image @@ -163,37 +167,40 @@ type RemoveImageOptions struct { // See https://goo.gl/V3ZWnK for more details. func (c *Client) RemoveImageExtended(name string, opts RemoveImageOptions) error { uri := fmt.Sprintf("/images/%s?%s", name, queryString(&opts)) - _, status, err := c.do("DELETE", uri, doOptions{}) - if status == http.StatusNotFound { - return ErrNoSuchImage + resp, err := c.do("DELETE", uri, doOptions{}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return ErrNoSuchImage + } + return err } - return err + resp.Body.Close() + return nil } // InspectImage returns an image by its name or ID. // // See https://goo.gl/jHPcg6 for more details. func (c *Client) InspectImage(name string) (*Image, error) { - body, status, err := c.do("GET", "/images/"+name+"/json", doOptions{}) - if status == http.StatusNotFound { - return nil, ErrNoSuchImage - } + resp, err := c.do("GET", "/images/"+name+"/json", doOptions{}) if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return nil, ErrNoSuchImage + } return nil, err } + defer resp.Body.Close() var image Image // if the caller elected to skip checking the server's version, assume it's the latest if c.SkipServerVersionCheck || c.expectedAPIVersion.GreaterThanOrEqualTo(apiVersion112) { - err = json.Unmarshal(body, &image) - if err != nil { + if err := json.NewDecoder(resp.Body).Decode(&image); err != nil { return nil, err } } else { var imagePre012 ImagePre012 - err = json.Unmarshal(body, &imagePre012) - if err != nil { + if err := json.NewDecoder(resp.Body).Decode(&imagePre012); err != nil { return nil, err } @@ -409,6 +416,7 @@ type BuildImageOptions struct { Auth AuthConfiguration `qs:"-"` // for older docker X-Registry-Auth header AuthConfigs AuthConfigurations `qs:"-"` // for newer docker X-Registry-Config header ContextDir string `qs:"-"` + Ulimits []ULimit `qs:"-"` } // 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, rawJSONStream: opts.RawJSONStream, headers: headers, @@ -477,10 +494,11 @@ func (c *Client) TagImage(name string, opts TagImageOptions) error { if name == "" { 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{}) + defer resp.Body.Close() - if status == http.StatusNotFound { + if resp.StatusCode == http.StatusNotFound { return ErrNoSuchImage } @@ -533,13 +551,13 @@ type APIImageSearch struct { // // See https://goo.gl/AYjyrF for more details. 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 { return nil, err } var searchResult []APIImageSearch - err = json.Unmarshal(body, &searchResult) - if err != nil { + if err := json.NewDecoder(resp.Body).Decode(&searchResult); err != nil { return nil, err } return searchResult, nil diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/image_test.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/image_test.go index bf010a2f994..8d9ee7ae2ff 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/image_test.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/image_test.go @@ -9,6 +9,7 @@ import ( "encoding/base64" "encoding/json" "io/ioutil" + "net" "net/http" "net/url" "os" @@ -24,6 +25,7 @@ func newTestClient(rt *FakeRoundTripper) Client { testAPIVersion, _ := NewAPIVersion("1.17") client := Client{ HTTPClient: &http.Client{Transport: rt}, + Dialer: &net.Dialer{}, endpoint: endpoint, endpointURL: u, SkipServerVersionCheck: true, @@ -680,6 +682,7 @@ func TestBuildImageParameters(t *testing.T) { Memswap: 2048, CPUShares: 10, CPUSetCPUs: "0-3", + Ulimits: []ULimit{ULimit{Name: "nofile", Soft: 100, Hard: 200}}, InputStream: &buf, OutputStream: &buf, } @@ -699,6 +702,7 @@ func TestBuildImageParameters(t *testing.T) { "memswap": {"2048"}, "cpushares": {"10"}, "cpusetcpus": {"0-3"}, + "ulimits": {"[{\"Name\":\"nofile\",\"Soft\":100,\"Hard\":200}]"}, } got := map[string][]string(req.URL.Query()) if !reflect.DeepEqual(got, expected) { diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/misc.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/misc.go index df22cf4945b..34c96531ad9 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/misc.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/misc.go @@ -4,21 +4,19 @@ package docker -import ( - "bytes" - "strings" -) +import "strings" // Version returns version information about the docker server. // // See https://goo.gl/ND9R8L for more details. func (c *Client) Version() (*Env, error) { - body, _, err := c.do("GET", "/version", doOptions{}) + resp, err := c.do("GET", "/version", doOptions{}) if err != nil { return nil, err } + defer resp.Body.Close() var env Env - if err := env.Decode(bytes.NewReader(body)); err != nil { + if err := env.Decode(resp.Body); err != nil { return nil, err } return &env, nil @@ -28,13 +26,13 @@ func (c *Client) Version() (*Env, error) { // // See https://goo.gl/ElTHi2 for more details. func (c *Client) Info() (*Env, error) { - body, _, err := c.do("GET", "/info", doOptions{}) + resp, err := c.do("GET", "/info", doOptions{}) if err != nil { return nil, err } + defer resp.Body.Close() var info Env - err = info.Decode(bytes.NewReader(body)) - if err != nil { + if err := info.Decode(resp.Body); err != nil { return nil, err } return &info, nil diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/network.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/network.go index 0d3e2d43f24..e21e7dfeac9 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/network.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/network.go @@ -38,12 +38,13 @@ type Endpoint struct { // // See https://goo.gl/4hCNtZ for more details. func (c *Client) ListNetworks() ([]Network, error) { - body, _, err := c.do("GET", "/networks", doOptions{}) + resp, err := c.do("GET", "/networks", doOptions{}) if err != nil { return nil, err } + defer resp.Body.Close() 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 networks, nil @@ -54,15 +55,16 @@ func (c *Client) ListNetworks() ([]Network, error) { // See https://goo.gl/4hCNtZ for more details. func (c *Client) NetworkInfo(id string) (*Network, error) { path := "/networks/" + id - body, status, err := c.do("GET", path, doOptions{}) - if status == http.StatusNotFound { - return nil, &NoSuchNetwork{ID: id} - } + resp, err := c.do("GET", path, doOptions{}) if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return nil, &NoSuchNetwork{ID: id} + } return nil, err } + defer resp.Body.Close() 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 &network, nil @@ -83,35 +85,34 @@ type CreateNetworkOptions struct { // // See http://goo.gl/mErxNp for more details. func (c *Client) CreateNetwork(opts CreateNetworkOptions) (*Network, error) { - body, status, err := c.do( + resp, err := c.do( "POST", "/networks", doOptions{ data: opts, }, ) - - if status == http.StatusConflict { - return nil, ErrNetworkAlreadyExists - } if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusConflict { + return nil, ErrNetworkAlreadyExists + } return nil, err } + defer resp.Body.Close() type createNetworkResponse struct { ID string } var ( network Network - resp createNetworkResponse + cnr createNetworkResponse ) - err = json.Unmarshal(body, &resp) - if err != nil { + if err := json.NewDecoder(resp.Body).Decode(&cnr); err != nil { return nil, err } network.Name = opts.Name - network.ID = resp.ID + network.ID = cnr.ID network.Type = opts.NetworkType return &network, nil diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/data/symlink b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/data/symlink deleted file mode 120000 index 3ddf86a3594..00000000000 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/data/symlink +++ /dev/null @@ -1 +0,0 @@ -doesnotexist \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/server.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/server.go index 05cd2e25bf0..46f6b672165 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/server.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/server.go @@ -957,7 +957,7 @@ func (s *DockerServer) createExecContainer(w http.ResponseWriter, r *http.Reques func (s *DockerServer) startExecContainer(w http.ResponseWriter, r *http.Request) { 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() exec.Running = true 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) { id := mux.Vars(r)["id"] - if _, err := s.getExec(id); err == nil { + if _, err := s.getExec(id, false); err == nil { w.WriteHeader(http.StatusOK) 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) { 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.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(exec) @@ -997,11 +997,15 @@ func (s *DockerServer) inspectExecContainer(w http.ResponseWriter, r *http.Reque 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() defer s.execMut.RUnlock() for _, exec := range s.execs { if exec.ID == id { + if copy { + cp := *exec + exec = &cp + } return exec, nil } } diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/tls.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/tls.go index 11d571761a8..55f43174bf0 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/tls.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/tls.go @@ -94,7 +94,3 @@ func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Con // wrapper which holds both the TLS and raw connections. return &tlsClientCon{conn, rawConn}, nil } - -func tlsDial(network, addr string, config *tls.Config) (net.Conn, error) { - return tlsDialWithDialer(new(net.Dialer), network, addr, config) -} diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/volume.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/volume.go index 4e63272542b..a989a6eee3d 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/volume.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/volume.go @@ -38,12 +38,13 @@ type ListVolumesOptions struct { // // See https://goo.gl/FZA4BK for more details. func (c *Client) ListVolumes(opts ListVolumesOptions) ([]Volume, error) { - body, _, err := c.do("GET", "/volumes?"+queryString(opts), doOptions{}) + 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.Unmarshal(body, &m); err != nil { + if err := json.NewDecoder(resp.Body).Decode(&m); err != nil { return nil, err } var volumes []Volume @@ -74,12 +75,13 @@ type CreateVolumeOptions struct { // // See https://goo.gl/pBUbZ9 for more details. func (c *Client) CreateVolume(opts CreateVolumeOptions) (*Volume, error) { - body, _, err := c.do("POST", "/volumes", doOptions{data: opts}) + 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.Unmarshal(body, &volume); err != nil { + if err := json.NewDecoder(resp.Body).Decode(&volume); err != nil { return nil, err } return &volume, nil @@ -89,15 +91,16 @@ func (c *Client) CreateVolume(opts CreateVolumeOptions) (*Volume, error) { // // See https://goo.gl/0g9A6i for more details. func (c *Client) InspectVolume(name string) (*Volume, error) { - body, status, err := c.do("GET", "/volumes/"+name, doOptions{}) - if status == http.StatusNotFound { - return nil, ErrNoSuchVolume - } + 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.Unmarshal(body, &volume); err != nil { + if err := json.NewDecoder(resp.Body).Decode(&volume); err != nil { return nil, err } return &volume, nil @@ -107,12 +110,18 @@ func (c *Client) InspectVolume(name string) (*Volume, error) { // // See https://goo.gl/79GNQz for more details. func (c *Client) RemoveVolume(name string) error { - _, status, err := c.do("DELETE", "/volumes/"+name, doOptions{}) - if status == http.StatusNotFound { - return ErrNoSuchVolume + 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 } - if status == http.StatusConflict { - return ErrVolumeInUse - } - return err + defer resp.Body.Close() + return nil }