diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 68849c7e657..c2b206a2a80 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -164,7 +164,7 @@ }, { "ImportPath": "github.com/fsouza/go-dockerclient", - "Rev": "17d39bcb22e8103ba6d1c0cb2530c6434cb870a3" + "Rev": "09334c56c63bab2cd6c4ccab924d89e2419a361f" }, { "ImportPath": "github.com/garyburd/redigo/internal", 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 672f6ed38fb..36c16d56c31 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/.travis.yml +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/.travis.yml @@ -7,7 +7,6 @@ env: - GOARCH=amd64 - GOARCH=386 install: - - go get -d ./... + - make testdeps script: - - go test ./... - - ./testing/bin/fmtpolice + - make test diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/AUTHORS b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/AUTHORS index 0bd1e5c9169..01115a3eabf 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/AUTHORS +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/AUTHORS @@ -26,6 +26,7 @@ Fatih Arslan Flavia Missi Francisco Souza Guillermo Álvarez Fernández +James Bardin Jari Kolehmainen Jason Wilder Jawher Moussa @@ -49,6 +50,7 @@ Nick Ethier Omeid Matten Paul Morie Paul Weil +Peter Edge Peter Jihoon Kim Philippe Lafoucrière Rafe Colton @@ -64,6 +66,7 @@ Sridhar Ratnakumar Summer Mousa Tarsis Azevedo Tim Schindler +Tobi Knaup Vincenzo Prignano Wiliam Souza Ye Yin diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/Makefile b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/Makefile new file mode 100644 index 00000000000..bade5f178ef --- /dev/null +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/Makefile @@ -0,0 +1,35 @@ +.PHONY: \ + all \ + deps \ + updatedeps \ + testdeps \ + updatetestdeps \ + cov \ + test \ + clean + +all: test + +deps: + go get -d -v ./... + +updatedeps: + go get -d -v -u -f ./... + +testdeps: + go get -d -v -t ./... + +updatetestdeps: + go get -d -v -t -u -f ./... + +cov: testdeps + go get -v github.com/axw/gocov/gocov + go get golang.org/x/tools/cmd/cover + gocov test | gocov report + +test: testdeps + go test ./... + ./testing/bin/fmtpolice + +clean: + go clean ./... 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 4019bca6e10..f475f2ab3a0 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/README.markdown +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/README.markdown @@ -66,5 +66,4 @@ func main() { You can run the tests with: - go get -d ./... - go test ./... + make test 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 d64f8c15bc6..6cd53b5df62 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/client.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/client.go @@ -252,7 +252,7 @@ func (c *Client) checkAPIVersion() error { // See http://goo.gl/stJENm for more details. func (c *Client) Ping() error { path := "/_ping" - body, status, err := c.do("GET", path, nil, false) + body, status, err := c.do("GET", path, doOptions{}) if err != nil { return err } @@ -263,7 +263,7 @@ func (c *Client) Ping() error { } func (c *Client) getServerAPIVersionString() (version string, err error) { - body, status, err := c.do("GET", "/version", nil, false) + body, status, err := c.do("GET", "/version", doOptions{}) if err != nil { return "", err } @@ -279,10 +279,15 @@ func (c *Client) getServerAPIVersionString() (version string, err error) { return version, nil } -func (c *Client) do(method, path string, data interface{}, forceJSON bool) ([]byte, int, error) { +type doOptions struct { + data interface{} + forceJSON bool +} + +func (c *Client) do(method, path string, doOptions doOptions) ([]byte, int, error) { var params io.Reader - if data != nil || forceJSON { - buf, err := json.Marshal(data) + if doOptions.data != nil || doOptions.forceJSON { + buf, err := json.Marshal(doOptions.data) if err != nil { return nil, -1, err } @@ -299,7 +304,7 @@ func (c *Client) do(method, path string, data interface{}, forceJSON bool) ([]by return nil, -1, err } req.Header.Set("User-Agent", userAgent) - if data != nil { + if doOptions.data != nil { req.Header.Set("Content-Type", "application/json") } else if method == "POST" { req.Header.Set("Content-Type", "plain/text") @@ -339,9 +344,18 @@ func (c *Client) do(method, path string, data interface{}, forceJSON bool) ([]by return body, resp.StatusCode, nil } -func (c *Client) stream(method, path string, setRawTerminal, rawJSONStream bool, headers map[string]string, in io.Reader, stdout, stderr io.Writer) error { - if (method == "POST" || method == "PUT") && in == nil { - in = bytes.NewReader(nil) +type streamOptions struct { + setRawTerminal bool + rawJSONStream bool + headers map[string]string + in io.Reader + stdout io.Writer + stderr io.Writer +} + +func (c *Client) stream(method, path string, streamOptions streamOptions) error { + if (method == "POST" || method == "PUT") && streamOptions.in == nil { + streamOptions.in = bytes.NewReader(nil) } if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil { err := c.checkAPIVersion() @@ -349,7 +363,7 @@ func (c *Client) stream(method, path string, setRawTerminal, rawJSONStream bool, return err } } - req, err := http.NewRequest(method, c.getURL(path), in) + req, err := http.NewRequest(method, c.getURL(path), streamOptions.in) if err != nil { return err } @@ -357,17 +371,17 @@ func (c *Client) stream(method, path string, setRawTerminal, rawJSONStream bool, if method == "POST" { req.Header.Set("Content-Type", "plain/text") } - for key, val := range headers { + for key, val := range streamOptions.headers { req.Header.Set(key, val) } var resp *http.Response protocol := c.endpointURL.Scheme address := c.endpointURL.Path - if stdout == nil { - stdout = ioutil.Discard + if streamOptions.stdout == nil { + streamOptions.stdout = ioutil.Discard } - if stderr == nil { - stderr = ioutil.Discard + if streamOptions.stderr == nil { + streamOptions.stderr = ioutil.Discard } if protocol == "unix" { dial, err := net.Dial(protocol, address) @@ -397,8 +411,8 @@ func (c *Client) stream(method, path string, setRawTerminal, rawJSONStream bool, if resp.Header.Get("Content-Type") == "application/json" { // if we want to get raw json stream, just copy it back to output // without decoding it - if rawJSONStream { - _, err = io.Copy(stdout, resp.Body) + if streamOptions.rawJSONStream { + _, err = io.Copy(streamOptions.stdout, resp.Body) return err } dec := json.NewDecoder(resp.Body) @@ -410,28 +424,37 @@ func (c *Client) stream(method, path string, setRawTerminal, rawJSONStream bool, return err } if m.Stream != "" { - fmt.Fprint(stdout, m.Stream) + fmt.Fprint(streamOptions.stdout, m.Stream) } else if m.Progress != "" { - fmt.Fprintf(stdout, "%s %s\r", m.Status, m.Progress) + fmt.Fprintf(streamOptions.stdout, "%s %s\r", m.Status, m.Progress) } else if m.Error != "" { return errors.New(m.Error) } if m.Status != "" { - fmt.Fprintln(stdout, m.Status) + fmt.Fprintln(streamOptions.stdout, m.Status) } } } else { - if setRawTerminal { - _, err = io.Copy(stdout, resp.Body) + if streamOptions.setRawTerminal { + _, err = io.Copy(streamOptions.stdout, resp.Body) } else { - _, err = stdCopy(stdout, stderr, resp.Body) + _, err = stdCopy(streamOptions.stdout, streamOptions.stderr, resp.Body) } return err } return nil } -func (c *Client) hijack(method, path string, success chan struct{}, setRawTerminal bool, in io.Reader, stderr, stdout io.Writer, data interface{}) error { +type hijackOptions struct { + success chan struct{} + setRawTerminal bool + in io.Reader + stdout io.Writer + stderr io.Writer + data interface{} +} + +func (c *Client) hijack(method, path string, hijackOptions hijackOptions) error { if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil { err := c.checkAPIVersion() if err != nil { @@ -440,19 +463,19 @@ func (c *Client) hijack(method, path string, success chan struct{}, setRawTermin } var params io.Reader - if data != nil { - buf, err := json.Marshal(data) + if hijackOptions.data != nil { + buf, err := json.Marshal(hijackOptions.data) if err != nil { return err } params = bytes.NewBuffer(buf) } - if stdout == nil { - stdout = ioutil.Discard + if hijackOptions.stdout == nil { + hijackOptions.stdout = ioutil.Discard } - if stderr == nil { - stderr = ioutil.Discard + if hijackOptions.stderr == nil { + hijackOptions.stderr = ioutil.Discard } req, err := http.NewRequest(method, c.getURL(path), params) if err != nil { @@ -480,9 +503,9 @@ func (c *Client) hijack(method, path string, success chan struct{}, setRawTermin clientconn := httputil.NewClientConn(dial, nil) defer clientconn.Close() clientconn.Do(req) - if success != nil { - success <- struct{}{} - <-success + if hijackOptions.success != nil { + hijackOptions.success <- struct{}{} + <-hijackOptions.success } rwc, br := clientconn.Hijack() defer rwc.Close() @@ -491,18 +514,18 @@ func (c *Client) hijack(method, path string, success chan struct{}, setRawTermin go func() { defer close(exit) var err error - if setRawTerminal { + if hijackOptions.setRawTerminal { // When TTY is ON, use regular copy - _, err = io.Copy(stdout, br) + _, err = io.Copy(hijackOptions.stdout, br) } else { - _, err = stdCopy(stdout, stderr, br) + _, err = stdCopy(hijackOptions.stdout, hijackOptions.stderr, br) } errs <- err }() go func() { var err error - if in != nil { - _, err = io.Copy(rwc, in) + if hijackOptions.in != nil { + _, err = io.Copy(rwc, hijackOptions.in) } rwc.(interface { CloseWrite() error @@ -555,39 +578,49 @@ func queryString(opts interface{}) string { } else if key == "-" { continue } - v := value.Field(i) - switch v.Kind() { - case reflect.Bool: - if v.Bool() { - items.Add(key, "1") + addQueryStringValue(items, key, value.Field(i)) + } + return items.Encode() +} + +func addQueryStringValue(items url.Values, key string, v reflect.Value) { + switch v.Kind() { + case reflect.Bool: + if v.Bool() { + items.Add(key, "1") + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if v.Int() > 0 { + items.Add(key, strconv.FormatInt(v.Int(), 10)) + } + case reflect.Float32, reflect.Float64: + if v.Float() > 0 { + items.Add(key, strconv.FormatFloat(v.Float(), 'f', -1, 64)) + } + case reflect.String: + if v.String() != "" { + items.Add(key, v.String()) + } + case reflect.Ptr: + if !v.IsNil() { + if b, err := json.Marshal(v.Interface()); err == nil { + items.Add(key, string(b)) } - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - if v.Int() > 0 { - items.Add(key, strconv.FormatInt(v.Int(), 10)) + } + case reflect.Map: + if len(v.MapKeys()) > 0 { + if b, err := json.Marshal(v.Interface()); err == nil { + items.Add(key, string(b)) } - case reflect.Float32, reflect.Float64: - if v.Float() > 0 { - items.Add(key, strconv.FormatFloat(v.Float(), 'f', -1, 64)) - } - case reflect.String: - if v.String() != "" { - items.Add(key, v.String()) - } - case reflect.Ptr: - if !v.IsNil() { - if b, err := json.Marshal(v.Interface()); err == nil { - items.Add(key, string(b)) - } - } - case reflect.Map: - if len(v.MapKeys()) > 0 { - if b, err := json.Marshal(v.Interface()); err == nil { - items.Add(key, string(b)) - } + } + case reflect.Array, reflect.Slice: + vLen := v.Len() + if vLen > 0 { + for i := 0; i < vLen; i++ { + addQueryStringValue(items, key, v.Index(i)) } } } - return items.Encode() } // Error represents failures in the API. It represents a failure from the API. 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 ab34a991972..e88eb52d399 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/container.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/container.go @@ -56,7 +56,7 @@ type APIContainers struct { // See http://goo.gl/6Y4Gz7 for more details. func (c *Client) ListContainers(opts ListContainersOptions) ([]APIContainers, error) { path := "/containers/json?" + queryString(opts) - body, _, err := c.do("GET", path, nil, false) + body, _, err := c.do("GET", path, doOptions{}) if err != nil { return nil, err } @@ -90,6 +90,7 @@ func (p Port) Proto() string { type State struct { Running bool `json:"Running,omitempty" yaml:"Running,omitempty"` Paused bool `json:"Paused,omitempty" yaml:"Paused,omitempty"` + Restarting bool `json:"Restarting,omitempty" yaml:"Restarting,omitempty"` OOMKilled bool `json:"OOMKilled,omitempty" yaml:"OOMKilled,omitempty"` Pid int `json:"Pid,omitempty" yaml:"Pid,omitempty"` ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty"` @@ -263,7 +264,7 @@ type RenameContainerOptions struct { // // See http://goo.gl/L00hoj for more details. func (c *Client) RenameContainer(opts RenameContainerOptions) error { - _, _, err := c.do("POST", fmt.Sprintf("/containers/"+opts.ID+"/rename?%s", queryString(opts)), nil, false) + _, _, err := c.do("POST", fmt.Sprintf("/containers/"+opts.ID+"/rename?%s", queryString(opts)), doOptions{}) return err } @@ -272,7 +273,7 @@ func (c *Client) RenameContainer(opts RenameContainerOptions) error { // See http://goo.gl/CxVuJ5 for more details. func (c *Client) InspectContainer(id string) (*Container, error) { path := "/containers/" + id + "/json" - body, status, err := c.do("GET", path, nil, false) + body, status, err := c.do("GET", path, doOptions{}) if status == http.StatusNotFound { return nil, &NoSuchContainer{ID: id} } @@ -292,7 +293,7 @@ func (c *Client) InspectContainer(id string) (*Container, error) { // See http://goo.gl/QkW9sH for more details. func (c *Client) ContainerChanges(id string) ([]Change, error) { path := "/containers/" + id + "/changes" - body, status, err := c.do("GET", path, nil, false) + body, status, err := c.do("GET", path, doOptions{}) if status == http.StatusNotFound { return nil, &NoSuchContainer{ID: id} } @@ -322,13 +323,19 @@ type CreateContainerOptions struct { // See http://goo.gl/mErxNp for more details. func (c *Client) CreateContainer(opts CreateContainerOptions) (*Container, error) { path := "/containers/create?" + queryString(opts) - body, status, err := c.do("POST", path, struct { - *Config - HostConfig *HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty"` - }{ - opts.Config, - opts.HostConfig, - }, false) + body, status, err := c.do( + "POST", + path, + doOptions{ + data: struct { + *Config + HostConfig *HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty"` + }{ + opts.Config, + opts.HostConfig, + }, + }, + ) if status == http.StatusNotFound { return nil, ErrNoSuchImage @@ -417,6 +424,7 @@ type HostConfig struct { LogConfig LogConfig `json:"LogConfig,omitempty" yaml:"LogConfig,omitempty"` ReadonlyRootfs bool `json:"ReadonlyRootfs,omitempty" yaml:"ReadonlyRootfs,omitempty"` SecurityOpt []string `json:"SecurityOpt,omitempty" yaml:"SecurityOpt,omitempty"` + CgroupParent string `json:"CgroupParent,omitempty" yaml:"CgroupParent,omitempty"` } // StartContainer starts a container, returning an error in case of failure. @@ -424,7 +432,7 @@ type HostConfig struct { // See http://goo.gl/iM5GYs for more details. func (c *Client) StartContainer(id string, hostConfig *HostConfig) error { path := "/containers/" + id + "/start" - _, status, err := c.do("POST", path, hostConfig, true) + _, status, err := c.do("POST", path, doOptions{data: hostConfig, forceJSON: true}) if status == http.StatusNotFound { return &NoSuchContainer{ID: id, Err: err} } @@ -443,7 +451,7 @@ func (c *Client) StartContainer(id string, hostConfig *HostConfig) error { // See http://goo.gl/EbcpXt 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, nil, false) + _, status, err := c.do("POST", path, doOptions{}) if status == http.StatusNotFound { return &NoSuchContainer{ID: id} } @@ -462,7 +470,7 @@ func (c *Client) StopContainer(id string, timeout uint) error { // See http://goo.gl/VOzR2n 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, nil, false) + _, status, err := c.do("POST", path, doOptions{}) if status == http.StatusNotFound { return &NoSuchContainer{ID: id} } @@ -477,7 +485,7 @@ func (c *Client) RestartContainer(id string, timeout uint) error { // See http://goo.gl/AM5t42 for more details. func (c *Client) PauseContainer(id string) error { path := fmt.Sprintf("/containers/%s/pause", id) - _, status, err := c.do("POST", path, nil, false) + _, status, err := c.do("POST", path, doOptions{}) if status == http.StatusNotFound { return &NoSuchContainer{ID: id} } @@ -492,7 +500,7 @@ func (c *Client) PauseContainer(id string) error { // See http://goo.gl/eBrNSL for more details. func (c *Client) UnpauseContainer(id string) error { path := fmt.Sprintf("/containers/%s/unpause", id) - _, status, err := c.do("POST", path, nil, false) + _, status, err := c.do("POST", path, doOptions{}) if status == http.StatusNotFound { return &NoSuchContainer{ID: id} } @@ -521,7 +529,7 @@ 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, nil, false) + body, status, err := c.do("GET", path, doOptions{}) if status == http.StatusNotFound { return result, &NoSuchContainer{ID: id} } @@ -553,7 +561,7 @@ type KillContainerOptions struct { // See http://goo.gl/TFkECx for more details. func (c *Client) KillContainer(opts KillContainerOptions) error { path := "/containers/" + opts.ID + "/kill" + "?" + queryString(opts) - _, status, err := c.do("POST", path, nil, false) + _, status, err := c.do("POST", path, doOptions{}) if status == http.StatusNotFound { return &NoSuchContainer{ID: opts.ID} } @@ -584,7 +592,7 @@ type RemoveContainerOptions struct { // See http://goo.gl/ZB83ji for more details. func (c *Client) RemoveContainer(opts RemoveContainerOptions) error { path := "/containers/" + opts.ID + "?" + queryString(opts) - _, status, err := c.do("DELETE", path, nil, false) + _, status, err := c.do("DELETE", path, doOptions{}) if status == http.StatusNotFound { return &NoSuchContainer{ID: opts.ID} } @@ -613,7 +621,7 @@ func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error { return &NoSuchContainer{ID: opts.Container} } url := fmt.Sprintf("/containers/%s/copy", opts.Container) - body, status, err := c.do("POST", url, opts, false) + body, status, err := c.do("POST", url, doOptions{data: opts}) if status == http.StatusNotFound { return &NoSuchContainer{ID: opts.Container} } @@ -629,7 +637,7 @@ func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error { // // See http://goo.gl/J88DHU for more details. func (c *Client) WaitContainer(id string) (int, error) { - body, status, err := c.do("POST", "/containers/"+id+"/wait", nil, false) + body, status, err := c.do("POST", "/containers/"+id+"/wait", doOptions{}) if status == http.StatusNotFound { return 0, &NoSuchContainer{ID: id} } @@ -661,7 +669,7 @@ type CommitContainerOptions struct { // See http://goo.gl/Jn8pe8 for more details. func (c *Client) CommitContainer(opts CommitContainerOptions) (*Image, error) { path := "/commit?" + queryString(opts) - body, status, err := c.do("POST", path, opts.Run, false) + body, status, err := c.do("POST", path, doOptions{data: opts.Run}) if status == http.StatusNotFound { return nil, &NoSuchContainer{ID: opts.Container} } @@ -720,7 +728,13 @@ func (c *Client) AttachToContainer(opts AttachToContainerOptions) error { return &NoSuchContainer{ID: opts.Container} } path := "/containers/" + opts.Container + "/attach?" + queryString(opts) - return c.hijack("POST", path, opts.Success, opts.RawTerminal, opts.InputStream, opts.ErrorStream, opts.OutputStream, nil) + return c.hijack("POST", path, hijackOptions{ + success: opts.Success, + setRawTerminal: opts.RawTerminal, + in: opts.InputStream, + stdout: opts.OutputStream, + stderr: opts.ErrorStream, + }) } // LogsOptions represents the set of options used when getting logs from a @@ -752,7 +766,11 @@ func (c *Client) Logs(opts LogsOptions) error { opts.Tail = "all" } path := "/containers/" + opts.Container + "/logs?" + queryString(opts) - return c.stream("GET", path, opts.RawTerminal, false, nil, nil, opts.OutputStream, opts.ErrorStream) + return c.stream("GET", path, streamOptions{ + setRawTerminal: opts.RawTerminal, + stdout: opts.OutputStream, + stderr: opts.ErrorStream, + }) } // ResizeContainerTTY resizes the terminal to the given height and width. @@ -760,7 +778,7 @@ 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(), nil, false) + _, _, err := c.do("POST", "/containers/"+id+"/resize?"+params.Encode(), doOptions{}) return err } @@ -782,7 +800,10 @@ func (c *Client) ExportContainer(opts ExportContainerOptions) error { return &NoSuchContainer{ID: opts.ID} } url := fmt.Sprintf("/containers/%s/export", opts.ID) - return c.stream("GET", url, true, false, nil, nil, opts.OutputStream, nil) + return c.stream("GET", url, streamOptions{ + setRawTerminal: true, + stdout: opts.OutputStream, + }) } // NoSuchContainer is the error returned when a given container does not exist. 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 b43447363fa..7e85cd4e7ba 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 @@ -229,7 +229,8 @@ func TestInspectContainer(t *testing.T) { ] }, "Links": null, - "PublishAllPorts": false + "PublishAllPorts": false, + "CgroupParent": "/mesos" } }` var expected Container 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 a2fec1e69b2..c3f6409fbf5 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/exec.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/exec.go @@ -90,7 +90,7 @@ type ExecInspect struct { // See http://goo.gl/8izrzI for more details func (c *Client) CreateExec(opts CreateExecOptions) (*Exec, error) { path := fmt.Sprintf("/containers/%s/exec", opts.Container) - body, status, err := c.do("POST", path, opts, false) + body, status, err := c.do("POST", path, doOptions{data: opts}) if status == http.StatusNotFound { return nil, &NoSuchContainer{ID: opts.Container} } @@ -119,7 +119,7 @@ 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, opts, false) + _, status, err := c.do("POST", path, doOptions{data: opts}) if status == http.StatusNotFound { return &NoSuchExec{ID: id} } @@ -129,7 +129,14 @@ func (c *Client) StartExec(id string, opts StartExecOptions) error { return nil } - return c.hijack("POST", path, opts.Success, opts.RawTerminal, opts.InputStream, opts.ErrorStream, opts.OutputStream, opts) + return c.hijack("POST", path, hijackOptions{ + success: opts.Success, + setRawTerminal: opts.RawTerminal, + in: opts.InputStream, + stdout: opts.OutputStream, + stderr: opts.ErrorStream, + data: opts, + }) } // ResizeExecTTY resizes the tty session used by the exec command id. This API @@ -143,7 +150,7 @@ 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, nil, false) + _, _, err := c.do("POST", path, doOptions{}) return err } @@ -152,7 +159,7 @@ func (c *Client) ResizeExecTTY(id string, height, width int) error { // See http://goo.gl/ypQULN 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, nil, false) + body, status, err := c.do("GET", path, doOptions{}) if status == http.StatusNotFound { return nil, &NoSuchExec{ID: id} } 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 ceac53ee3e6..4b762d30ebd 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/image.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/image.go @@ -26,6 +26,7 @@ type APIImages struct { Size int64 `json:"Size,omitempty" yaml:"Size,omitempty"` VirtualSize int64 `json:"VirtualSize,omitempty" yaml:"VirtualSize,omitempty"` ParentID string `json:"ParentId,omitempty" yaml:"ParentId,omitempty"` + RepoDigests []string `json:"RepoDigests,omitempty" yaml:"RepoDigests,omitempty"` } // Image is the type representing a docker image and its various properties @@ -71,10 +72,11 @@ type ImagePre012 struct { // ListImagesOptions specify parameters to the ListImages function. // -// See http://goo.gl/2rOLFF for more details. +// See http://goo.gl/HRVN1Z for more details. type ListImagesOptions struct { All bool Filters map[string][]string + Digests bool } var ( @@ -92,14 +94,19 @@ var ( // ErrMultipleContexts is the error returned when both a ContextDir and // InputStream are provided in BuildImageOptions ErrMultipleContexts = errors.New("image build may not be provided BOTH context dir and input stream") + + // ErrMustSpecifyNames is the error rreturned when the Names field on + // ExportImagesOptions is nil or empty + ErrMustSpecifyNames = errors.New("must specify at least one name to export") ) // ListImages returns the list of available images in the server. // -// See http://goo.gl/2rOLFF for more details. +// See http://goo.gl/HRVN1Z for more details. func (c *Client) ListImages(opts ListImagesOptions) ([]APIImages, error) { + // TODO(pedge): what happens if we specify the digest parameter when using API Version <1.18? path := "/images/json?" + queryString(opts) - body, _, err := c.do("GET", path, nil, false) + body, _, err := c.do("GET", path, doOptions{}) if err != nil { return nil, err } @@ -115,7 +122,7 @@ func (c *Client) ListImages(opts ListImagesOptions) ([]APIImages, error) { // // See http://goo.gl/2oJmNs for more details. func (c *Client) ImageHistory(name string) ([]ImageHistory, error) { - body, status, err := c.do("GET", "/images/"+name+"/history", nil, false) + body, status, err := c.do("GET", "/images/"+name+"/history", doOptions{}) if status == http.StatusNotFound { return nil, ErrNoSuchImage } @@ -134,7 +141,7 @@ func (c *Client) ImageHistory(name string) ([]ImageHistory, error) { // // See http://goo.gl/znj0wM for more details. func (c *Client) RemoveImage(name string) error { - _, status, err := c.do("DELETE", "/images/"+name, nil, false) + _, status, err := c.do("DELETE", "/images/"+name, doOptions{}) if status == http.StatusNotFound { return ErrNoSuchImage } @@ -156,7 +163,7 @@ type RemoveImageOptions struct { // See http://goo.gl/znj0wM 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, nil, false) + _, status, err := c.do("DELETE", uri, doOptions{}) if status == http.StatusNotFound { return ErrNoSuchImage } @@ -167,7 +174,7 @@ func (c *Client) RemoveImageExtended(name string, opts RemoveImageOptions) error // // See http://goo.gl/Q112NY for more details. func (c *Client) InspectImage(name string) (*Image, error) { - body, status, err := c.do("GET", "/images/"+name+"/json", nil, false) + body, status, err := c.do("GET", "/images/"+name+"/json", doOptions{}) if status == http.StatusNotFound { return nil, ErrNoSuchImage } @@ -236,8 +243,12 @@ func (c *Client) PushImage(opts PushImageOptions, auth AuthConfiguration) error name := opts.Name opts.Name = "" path := "/images/" + name + "/push?" + queryString(&opts) - headers := headersWithAuth(auth) - return c.stream("POST", path, true, opts.RawJSONStream, headers, nil, opts.OutputStream, nil) + return c.stream("POST", path, streamOptions{ + setRawTerminal: true, + rawJSONStream: opts.RawJSONStream, + headers: headersWithAuth(auth), + stdout: opts.OutputStream, + }) } // PullImageOptions present the set of options available for pulling an image @@ -266,7 +277,13 @@ func (c *Client) PullImage(opts PullImageOptions, auth AuthConfiguration) error func (c *Client) createImage(qs string, headers map[string]string, in io.Reader, w io.Writer, rawJSONStream bool) error { path := "/images/create?" + qs - return c.stream("POST", path, true, rawJSONStream, headers, in, w, nil) + return c.stream("POST", path, streamOptions{ + setRawTerminal: true, + rawJSONStream: rawJSONStream, + headers: headers, + in: in, + stdout: w, + }) } // LoadImageOptions represents the options for LoadImage Docker API Call @@ -280,7 +297,10 @@ type LoadImageOptions struct { // // See http://goo.gl/Y8NNCq for more details. func (c *Client) LoadImage(opts LoadImageOptions) error { - return c.stream("POST", "/images/load", true, false, nil, opts.InputStream, nil, nil) + return c.stream("POST", "/images/load", streamOptions{ + setRawTerminal: true, + in: opts.InputStream, + }) } // ExportImageOptions represent the options for ExportImage Docker API call @@ -295,7 +315,31 @@ type ExportImageOptions struct { // // See http://goo.gl/mi6kvk for more details. func (c *Client) ExportImage(opts ExportImageOptions) error { - return c.stream("GET", fmt.Sprintf("/images/%s/get", opts.Name), true, false, nil, nil, opts.OutputStream, nil) + return c.stream("GET", fmt.Sprintf("/images/%s/get", opts.Name), streamOptions{ + setRawTerminal: true, + stdout: opts.OutputStream, + }) +} + +// ExportImagesOptions represent the options for ExportImages Docker API call +// +// See http://goo.gl/YeZzQK for more details. +type ExportImagesOptions struct { + Names []string + OutputStream io.Writer `qs:"-"` +} + +// ExportImages exports one or more images (as a tar file) into the stream +// +// See http://goo.gl/YeZzQK for more details. +func (c *Client) ExportImages(opts ExportImagesOptions) error { + if opts.Names == nil || len(opts.Names) == 0 { + return ErrMustSpecifyNames + } + return c.stream("GET", "/images/get?"+queryString(&opts), streamOptions{ + setRawTerminal: true, + stdout: opts.OutputStream, + }) } // ImportImageOptions present the set of informations available for importing @@ -382,8 +426,13 @@ func (c *Client) BuildImage(opts BuildImageOptions) error { } } - return c.stream("POST", fmt.Sprintf("/build?%s", - queryString(&opts)), true, opts.RawJSONStream, headers, opts.InputStream, opts.OutputStream, nil) + return c.stream("POST", fmt.Sprintf("/build?%s", queryString(&opts)), streamOptions{ + setRawTerminal: true, + rawJSONStream: opts.RawJSONStream, + headers: headers, + in: opts.InputStream, + stdout: opts.OutputStream, + }) } // TagImageOptions present the set of options to tag an image. @@ -403,7 +452,7 @@ func (c *Client) TagImage(name string, opts TagImageOptions) error { return ErrNoSuchImage } _, status, err := c.do("POST", fmt.Sprintf("/images/"+name+"/tag?%s", - queryString(&opts)), nil, false) + queryString(&opts)), doOptions{}) if status == http.StatusNotFound { return ErrNoSuchImage @@ -454,7 +503,7 @@ type APIImageSearch struct { // // See http://goo.gl/xI5lLZ for more details. func (c *Client) SearchImages(term string) ([]APIImageSearch, error) { - body, _, err := c.do("GET", "/images/search?term="+term, nil, false) + body, _, err := c.do("GET", "/images/search?term="+term, doOptions{}) if err != nil { return nil, err } 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 539ea978c31..27d60937489 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 @@ -860,6 +860,40 @@ func TestExportImage(t *testing.T) { } } +func TestExportImages(t *testing.T) { + var buf bytes.Buffer + fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} + client := newTestClient(fakeRT) + opts := ExportImagesOptions{Names: []string{"testimage1", "testimage2:latest"}, OutputStream: &buf} + err := client.ExportImages(opts) + if nil != err { + t.Error(err) + } + req := fakeRT.requests[0] + if req.Method != "GET" { + t.Errorf("ExportImage: wrong method. Expected %q. Got %q.", "GET", req.Method) + } + expected := "http://localhost:4243/images/get?names=testimage1&names=testimage2%3Alatest" + got := req.URL.String() + if !reflect.DeepEqual(got, expected) { + t.Errorf("ExportIMage: wrong path. Expected %q. Got %q.", expected, got) + } +} + +func TestExportImagesNoNames(t *testing.T) { + var buf bytes.Buffer + fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} + client := newTestClient(fakeRT) + opts := ExportImagesOptions{Names: []string{}, OutputStream: &buf} + err := client.ExportImages(opts) + if err == nil { + t.Error("Expected an error") + } + if err != ErrMustSpecifyNames { + t.Error(err) + } +} + func TestSearchImages(t *testing.T) { body := `[ { 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 689036cc5ed..42d1c7e48e3 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/misc.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/misc.go @@ -13,7 +13,7 @@ import ( // // See http://goo.gl/BOZrF5 for more details. func (c *Client) Version() (*Env, error) { - body, _, err := c.do("GET", "/version", nil, false) + body, _, err := c.do("GET", "/version", doOptions{}) if err != nil { return nil, err } @@ -28,7 +28,7 @@ func (c *Client) Version() (*Env, error) { // // See http://goo.gl/wmqZsW for more details. func (c *Client) Info() (*Env, error) { - body, _, err := c.do("GET", "/info", nil, false) + body, _, err := c.do("GET", "/info", doOptions{}) if err != nil { return nil, err } 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 2114261750f..a556d67e5b3 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 @@ -25,6 +25,8 @@ import ( "github.com/gorilla/mux" ) +var nameRegexp = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_.-]+$`) + // DockerServer represents a programmable, concurrent (not much), HTTP server // implementing a fake version of the Docker remote API. // @@ -339,6 +341,11 @@ func (s *DockerServer) createContainer(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusBadRequest) return } + name := r.URL.Query().Get("name") + if name != "" && !nameRegexp.MatchString(name) { + http.Error(w, "Invalid container name", http.StatusInternalServerError) + return + } if _, err := s.findImage(config.Image); err != nil { http.Error(w, err.Error(), http.StatusNotFound) return @@ -363,7 +370,7 @@ func (s *DockerServer) createContainer(w http.ResponseWriter, r *http.Request) { } container := docker.Container{ - Name: r.URL.Query().Get("name"), + Name: name, ID: s.generateID(), Created: time.Now(), Path: path, diff --git a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/server_test.go b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/server_test.go index 097158fbd18..127d490bfa0 100644 --- a/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/server_test.go +++ b/Godeps/_workspace/src/github.com/fsouza/go-dockerclient/testing/server_test.go @@ -241,6 +241,24 @@ func TestCreateContainerInvalidBody(t *testing.T) { } } +func TestCreateContainerInvalidName(t *testing.T) { + server := DockerServer{} + server.buildMuxer() + recorder := httptest.NewRecorder() + body := `{"Hostname":"", "User":"", "Memory":0, "MemorySwap":0, "AttachStdin":false, "AttachStdout":true, "AttachStderr":true, +"PortSpecs":null, "Tty":false, "OpenStdin":false, "StdinOnce":false, "Env":null, "Cmd":["date"], +"Image":"base", "Volumes":{}, "VolumesFrom":""}` + request, _ := http.NewRequest("POST", "/containers/create?name=myapp/container1", strings.NewReader(body)) + server.ServeHTTP(recorder, request) + if recorder.Code != http.StatusInternalServerError { + t.Errorf("CreateContainer: wrong status. Want %d. Got %d.", http.StatusInternalServerError, recorder.Code) + } + expectedBody := "Invalid container name\n" + if got := recorder.Body.String(); got != expectedBody { + t.Errorf("CreateContainer: wrong body. Want %q. Got %q.", expectedBody, got) + } +} + func TestCreateContainerImageNotFound(t *testing.T) { server := DockerServer{} server.buildMuxer()