diff --git a/Gopkg.lock b/Gopkg.lock index 067bd220c..151a0091d 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -92,19 +92,15 @@ "api/types/swarm/runtime", "api/types/versions", "opts", - "pkg/archive", "pkg/fileutils", "pkg/homedir", "pkg/idtools", "pkg/ioutils", - "pkg/jsonmessage", "pkg/longpath", "pkg/mount", "pkg/pools", "pkg/stdcopy", - "pkg/system", - "pkg/term", - "pkg/term/windows" + "pkg/system" ] revision = "29fc64b590badcb1c3f5beff7563ffd31eb58974" @@ -131,8 +127,14 @@ [[projects]] name = "github.com/fsouza/go-dockerclient" - packages = ["."] - revision = "98edf3edfae6a6500fecc69d2bcccf1302544004" + packages = [ + ".", + "internal/archive", + "internal/jsonmessage", + "internal/term" + ] + revision = "8842d40dbf5ee062d80f9dc429db31a0fe0cdc73" + version = "v1.2.2" [[projects]] name = "github.com/garyburd/redigo" @@ -275,10 +277,7 @@ [[projects]] name = "github.com/opencontainers/runc" - packages = [ - "libcontainer/system", - "libcontainer/user" - ] + packages = ["libcontainer/user"] revision = "baf6536d6259209c3edfa2b22237af82942d3dfa" version = "v0.1.1" @@ -393,7 +392,6 @@ name = "golang.org/x/net" packages = [ "context", - "context/ctxhttp", "http/httpguts", "http2", "http2/hpack", @@ -504,6 +502,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "6c6cfaf48ee2f7f926ed2d063af6a65a505f927e7354fe4613ead8c8a6efb203" + inputs-digest = "6a60a2130326efcc95c191b4d0a160b2fc8770c86c6c6f5646ecd93b42e7fbb5" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 295e0d4f6..cec58a4aa 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -56,7 +56,7 @@ ignored = ["github.com/fnproject/fn/cli", [[constraint]] name = "github.com/fsouza/go-dockerclient" - revision = "98edf3edfae6a6500fecc69d2bcccf1302544004" + version = "~1.2.0" [[constraint]] name = "github.com/coreos/go-semver" diff --git a/api/agent/drivers/docker/docker_client.go b/api/agent/drivers/docker/docker_client.go index cb949b35b..26391b888 100644 --- a/api/agent/drivers/docker/docker_client.go +++ b/api/agent/drivers/docker/docker_client.go @@ -4,19 +4,15 @@ package docker import ( "context" - "crypto/tls" - "net" - "net/http" "os" "strings" "time" - "go.opencensus.io/stats/view" - "github.com/fnproject/fn/api/common" "github.com/fsouza/go-dockerclient" "github.com/sirupsen/logrus" "go.opencensus.io/stats" + "go.opencensus.io/stats/view" "go.opencensus.io/trace" ) @@ -58,49 +54,15 @@ func newClient() dockerClient { logrus.WithError(err).Fatal("couldn't create docker client") } - t := &http.Transport{ - Dial: (&net.Dialer{ - Timeout: 10 * time.Second, - KeepAlive: 1 * time.Minute, - }).Dial, - TLSClientConfig: &tls.Config{ - ClientSessionCache: tls.NewLRUClientSessionCache(8192), - }, - TLSHandshakeTimeout: 10 * time.Second, - MaxIdleConnsPerHost: 512, - Proxy: http.ProxyFromEnvironment, - MaxIdleConns: 512, - IdleConnTimeout: 90 * time.Second, - ExpectContinueTimeout: 1 * time.Second, - } - - client.HTTPClient = &http.Client{Transport: t} - if err := client.Ping(); err != nil { logrus.WithError(err).Fatal("couldn't connect to docker daemon") } - client.SetTimeout(120 * time.Second) - - // get 2 clients, one with a small timeout, one with no timeout to use contexts - - clientNoTimeout, err := docker.NewClientFromEnv() - if err != nil { - logrus.WithError(err).Fatal("couldn't create other docker client") - } - - clientNoTimeout.HTTPClient = &http.Client{Transport: t} - - if err := clientNoTimeout.Ping(); err != nil { - logrus.WithError(err).Fatal("couldn't connect to other docker daemon") - } - - return &dockerWrap{client, clientNoTimeout} + return &dockerWrap{client} } type dockerWrap struct { - docker *docker.Client - dockerNoTimeout *docker.Client + docker *docker.Client } var ( @@ -237,7 +199,7 @@ func (d *dockerWrap) WaitContainerWithContext(id string, ctx context.Context) (c logger := common.Logger(ctx).WithField("docker_cmd", "WaitContainer") err = d.retry(ctx, logger, func() error { - code, err = d.dockerNoTimeout.WaitContainerWithContext(id, ctx) + code, err = d.docker.WaitContainerWithContext(id, ctx) return err }) return code, filterNoSuchContainer(ctx, err) @@ -249,7 +211,7 @@ func (d *dockerWrap) StartContainerWithContext(id string, hostConfig *docker.Hos logger := common.Logger(ctx).WithField("docker_cmd", "StartContainer") err = d.retry(ctx, logger, func() error { - err = d.dockerNoTimeout.StartContainerWithContext(id, hostConfig, ctx) + err = d.docker.StartContainerWithContext(id, hostConfig, ctx) if _, ok := err.(*docker.NoSuchContainer); ok { // for some reason create will sometimes return successfully then say no such container here. wtf. so just retry like normal return temp(err) @@ -265,7 +227,7 @@ func (d *dockerWrap) CreateContainer(opts docker.CreateContainerOptions) (c *doc logger := common.Logger(ctx).WithField("docker_cmd", "CreateContainer") err = d.retry(ctx, logger, func() error { - c, err = d.dockerNoTimeout.CreateContainer(opts) + c, err = d.docker.CreateContainer(opts) return err }) return c, err @@ -277,7 +239,7 @@ func (d *dockerWrap) KillContainer(opts docker.KillContainerOptions) (err error) logger := common.Logger(ctx).WithField("docker_cmd", "KillContainer") err = d.retry(ctx, logger, func() error { - err = d.dockerNoTimeout.KillContainer(opts) + err = d.docker.KillContainer(opts) return err }) return err @@ -289,7 +251,7 @@ func (d *dockerWrap) PullImage(opts docker.PullImageOptions, auth docker.AuthCon logger := common.Logger(ctx).WithField("docker_cmd", "PullImage") err = d.retry(ctx, logger, func() error { - err = d.dockerNoTimeout.PullImage(opts, auth) + err = d.docker.PullImage(opts, auth) return err }) return err diff --git a/vendor/github.com/fsouza/go-dockerclient/.gitignore b/vendor/github.com/fsouza/go-dockerclient/.gitignore index 5f6b48eae..ef22245ea 100644 --- a/vendor/github.com/fsouza/go-dockerclient/.gitignore +++ b/vendor/github.com/fsouza/go-dockerclient/.gitignore @@ -1,2 +1,4 @@ # temporary symlink for testing testing/data/symlink +Gopkg.lock +vendor/ diff --git a/vendor/github.com/fsouza/go-dockerclient/.travis.yml b/vendor/github.com/fsouza/go-dockerclient/.travis.yml index e768e88ba..7da0371a0 100644 --- a/vendor/github.com/fsouza/go-dockerclient/.travis.yml +++ b/vendor/github.com/fsouza/go-dockerclient/.travis.yml @@ -1,20 +1,18 @@ language: go sudo: required go: - - 1.8.x - - 1.9 - - tip + - 1.9.x + - 1.10.x os: - linux - osx env: matrix: - - GOARCH=amd64 DOCKER_PKG_VERSION=17.06.0~ce-0~ubuntu - - GOARCH=386 DOCKER_PKG_VERSION=17.06.0~ce-0~ubuntu - - GOARCH=amd64 DOCKER_PKG_VERSION=17.05.0~ce-0~ubuntu-trusty - - GOARCH=386 DOCKER_PKG_VERSION=17.05.0~ce-0~ubuntu-trusty + - GOARCH=amd64 DOCKER_PKG_VERSION=18.03.0~ce-0~ubuntu + - GOARCH=386 DOCKER_PKG_VERSION=18.03.0~ce-0~ubuntu + - GOARCH=amd64 DOCKER_PKG_VERSION=18.02.0~ce-0~ubuntu + - GOARCH=386 DOCKER_PKG_VERSION=18.02.0~ce-0~ubuntu global: - - GO_TEST_FLAGS=-race - DOCKER_HOST=tcp://127.0.0.1:2375 install: - make testdeps @@ -27,6 +25,6 @@ matrix: fast_finish: true exclude: - os: osx - env: GOARCH=amd64 DOCKER_PKG_VERSION=17.05.0~ce-0~ubuntu-trusty + env: GOARCH=amd64 DOCKER_PKG_VERSION=18.02.0~ce-0~ubuntu - os: osx - env: GOARCH=386 DOCKER_PKG_VERSION=17.05.0~ce-0~ubuntu-trusty + env: GOARCH=386 DOCKER_PKG_VERSION=18.02.0~ce-0~ubuntu diff --git a/vendor/github.com/fsouza/go-dockerclient/AUTHORS b/vendor/github.com/fsouza/go-dockerclient/AUTHORS index 55dd8e3b3..f944f3950 100644 --- a/vendor/github.com/fsouza/go-dockerclient/AUTHORS +++ b/vendor/github.com/fsouza/go-dockerclient/AUTHORS @@ -2,7 +2,9 @@ Abhishek Chanda Adam Bell-Hanssen +Adnan Khan Adrien Kohlbecker +Aithal Aldrin Leal Alex Dadgar Alfonso Acosta @@ -39,20 +41,24 @@ Chris Bednarski Chris Stavropoulos Christian Stewart Christophe Mourette +Clayton Coleman Clint Armstrong CMGS Colin Hebert Craig Jellick +Damien Lespiau Damon Wang Dan Williams Daniel, Dao Quang Minh Daniel Garcia Daniel Hiltgen +Daniel Nephin Daniel Tsui Darren Shepherd Dave Choi David Huie Dawn Chen +Denis Makogon Derek Petersen Dinesh Subhraveti Drew Wells @@ -67,6 +73,7 @@ Ethan Mosbaugh Ewout Prangsma Fabio Rehm Fatih Arslan +Faye Salwin Felipe Oliveira Flavia Missi Florent Aide @@ -78,6 +85,7 @@ Guilherme Rezende Guillermo Álvarez Fernández Harry Zhang He Simei +Isaac Schnitzer Ivan Mikushin James Bardin James Nugent @@ -105,6 +113,7 @@ Kevin Xu Kim, Hirokuni Kostas Lekkas Kyle Allan +Yunhee Lee Liron Levin Lior Yankovich Liu Peng @@ -113,6 +122,7 @@ Lucas Clemente Lucas Weiblen Lyon Hill Mantas Matelis +Manuel Vogel Marguerite des Trois Maisons Mariusz Borsa Martin Sweeney @@ -133,6 +143,7 @@ Paul Morie Paul Weil Peter Edge Peter Jihoon Kim +Peter Teich Phil Lu Philippe Lafoucrière Radek Simko @@ -150,6 +161,7 @@ Sam Rijs Sami Wagiaalla Samuel Archambault Samuel Karp +Sebastian Borza Seth Jennings Shane Xie Silas Sewell @@ -158,8 +170,10 @@ Simon Menke Skolos Soulou Sridhar Ratnakumar +Steven Jack Summer Mousa Sunjin Lee +Sunny Swaroop Ramachandra Tarsis Azevedo Tim Schindler @@ -175,5 +189,6 @@ Vlad Alexandru Ionescu Weitao Zhou Wiliam Souza Ye Yin +Yosuke Otosu Yu, Zou Yuriy Bogdanov diff --git a/vendor/github.com/fsouza/go-dockerclient/Gopkg.toml b/vendor/github.com/fsouza/go-dockerclient/Gopkg.toml new file mode 100644 index 000000000..47971edae --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/Gopkg.toml @@ -0,0 +1,28 @@ +[[constraint]] + name = "github.com/Microsoft/go-winio" + version = "v0.4.5" + +[[constraint]] + name = "github.com/docker/docker" + revision = "3dfb26ab3cbf961298f8ce3f94659b5fe4146ceb" + +[[constraint]] + name = "github.com/docker/go-units" + version = "v0.3.2" + +[[constraint]] + name = "github.com/google/go-cmp" + version = "v0.2.0" + +[[constraint]] + name = "github.com/gorilla/mux" + version = "v1.5.0" + +[[override]] + name = "github.com/Nvveen/Gotty" + source = "https://github.com/ijc25/Gotty.git" + revision = "a8b993ba6abdb0e0c12b0125c603323a71c7790c" + +[[override]] + name = "github.com/docker/libnetwork" + revision = "19279f0492417475b6bfbd0aa529f73e8f178fb5" diff --git a/vendor/github.com/fsouza/go-dockerclient/LICENSE b/vendor/github.com/fsouza/go-dockerclient/LICENSE index 545174c18..fc7e73f8f 100644 --- a/vendor/github.com/fsouza/go-dockerclient/LICENSE +++ b/vendor/github.com/fsouza/go-dockerclient/LICENSE @@ -1,18 +1,19 @@ -Copyright (c) 2013-2017, go-dockerclient authors +Copyright (c) 2013-2018, go-dockerclient authors All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation -and/or other materials provided with the distribution. +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR diff --git a/vendor/github.com/fsouza/go-dockerclient/Makefile b/vendor/github.com/fsouza/go-dockerclient/Makefile index 483aa1bb4..479b07b0c 100644 --- a/vendor/github.com/fsouza/go-dockerclient/Makefile +++ b/vendor/github.com/fsouza/go-dockerclient/Makefile @@ -2,40 +2,33 @@ all \ lint \ vet \ - fmt \ fmtcheck \ pretest \ test \ - integration \ - clean + integration all: test lint: - @ go get -v github.com/golang/lint/golint + @ go get -v golang.org/x/lint/golint [ -z "$$(golint . | grep -v 'type name will be used as docker.DockerInfo' | grep -v 'context.Context should be the first' | tee /dev/stderr)" ] vet: go vet ./... -fmt: - gofmt -s -w . - fmtcheck: - [ -z "$$(gofmt -s -d . | tee /dev/stderr)" ] + [ -z "$$(gofmt -s -d *.go ./testing | tee /dev/stderr)" ] testdeps: - go get -d -t ./... + go get -u github.com/golang/dep/cmd/dep + dep ensure -v pretest: testdeps lint vet fmtcheck gotest: - go test $(GO_TEST_FLAGS) ./... + go test -race ./... test: pretest gotest integration: go test -tags docker_integration -run TestIntegration -v - -clean: - go clean ./... diff --git a/vendor/github.com/fsouza/go-dockerclient/README.markdown b/vendor/github.com/fsouza/go-dockerclient/README.markdown index a9ffc17a0..86824d6c5 100644 --- a/vendor/github.com/fsouza/go-dockerclient/README.markdown +++ b/vendor/github.com/fsouza/go-dockerclient/README.markdown @@ -6,7 +6,6 @@ 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/swarm-api/). -It currently supports the Docker API up to version 1.23. 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 @@ -111,6 +110,15 @@ Running `make test` will check all of these. If your editor does not automatically call ``gofmt -s``, `make fmt` will format all go files in this repository. +## Vendoring + +go-dockerclient uses [dep](https://github.com/golang/dep/) for vendoring. If +you're using dep, you should be able to pick go-dockerclient releases and get +the proper dependencies. + +With other vendoring tools, users might need to specify go-dockerclient's +dependencies manually. + ## Using with Docker 1.9 and Go 1.4 There's a tag for using go-dockerclient with Docker 1.9 (which requires diff --git a/vendor/github.com/fsouza/go-dockerclient/appveyor.yml b/vendor/github.com/fsouza/go-dockerclient/appveyor.yml index 3d8e319cd..ee1297ad0 100644 --- a/vendor/github.com/fsouza/go-dockerclient/appveyor.yml +++ b/vendor/github.com/fsouza/go-dockerclient/appveyor.yml @@ -5,16 +5,17 @@ clone_folder: c:\gopath\src\github.com\fsouza\go-dockerclient environment: GOPATH: c:\gopath matrix: - - GOVERSION: 1.8.3 - - GOVERSION: 1.9 + - GOVERSION: 1.9.7 + - GOVERSION: 1.10.3 install: - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% - rmdir c:\go /s /q - appveyor DownloadFile https://storage.googleapis.com/golang/go%GOVERSION%.windows-amd64.zip - 7z x go%GOVERSION%.windows-amd64.zip -y -oC:\ > NUL build_script: - - go get -race -d -t ./... + - go get -u github.com/golang/dep/cmd/dep + - dep ensure -v test_script: - - go test -race ./... + - for /f "" %%G in ('go list ./... ^| find /i /v "/vendor/"') do ( go test %%G & IF ERRORLEVEL == 1 EXIT 1) matrix: fast_finish: true diff --git a/vendor/github.com/fsouza/go-dockerclient/auth.go b/vendor/github.com/fsouza/go-dockerclient/auth.go index 03d192b79..c58de8671 100644 --- a/vendor/github.com/fsouza/go-dockerclient/auth.go +++ b/vendor/github.com/fsouza/go-dockerclient/auth.go @@ -129,6 +129,9 @@ func authConfigs(confs map[string]dockerConfig) (*AuthConfigurations, error) { Configs: make(map[string]AuthConfiguration), } for reg, conf := range confs { + if conf.Auth == "" { + continue + } data, err := base64.StdEncoding.DecodeString(conf.Auth) if err != nil { return nil, err diff --git a/vendor/github.com/fsouza/go-dockerclient/auth_test.go b/vendor/github.com/fsouza/go-dockerclient/auth_test.go index ba846f655..2f57aa1a5 100644 --- a/vendor/github.com/fsouza/go-dockerclient/auth_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/auth_test.go @@ -43,7 +43,7 @@ func TestAuthConfigurationsFromFile(t *testing.T) { } defer os.RemoveAll(tmpDir) authString := base64.StdEncoding.EncodeToString([]byte("user:pass")) - content := fmt.Sprintf("{\"auths\":{\"foo\": {\"auth\": \"%s\"}}}", authString) + content := fmt.Sprintf(`{"auths":{"foo": {"auth": "%s"}}}`, authString) configFile := path.Join(tmpDir, "docker_config") if err = ioutil.WriteFile(configFile, []byte(content), 0600); err != nil { t.Errorf("Error writing auth config for TestAuthConfigurationsFromFile: %s", err) @@ -96,6 +96,29 @@ func TestAuthBadConfig(t *testing.T) { } } +func TestAuthMixedWithKeyChain(t *testing.T) { + t.Parallel() + auth := base64.StdEncoding.EncodeToString([]byte("user:pass")) + read := strings.NewReader(fmt.Sprintf(`{"auths":{"docker.io":{},"localhost:5000":{"auth":"%s"}},"credsStore":"osxkeychain"}`, auth)) + ac, err := NewAuthConfigurations(read) + if err != nil { + t.Fatal(err) + } + c, ok := ac.Configs["localhost:5000"] + if !ok { + t.Error("NewAuthConfigurations: Expected Configs to contain localhost:5000") + } + if got, want := c.Username, "user"; got != want { + t.Errorf(`AuthConfigurations.Configs["docker.io"].Username: wrong result. Want %q. Got %q`, want, got) + } + if got, want := c.Password, "pass"; got != want { + t.Errorf(`AuthConfigurations.Configs["docker.io"].Password: wrong result. Want %q. Got %q`, want, got) + } + if got, want := c.ServerAddress, "localhost:5000"; got != want { + t.Errorf(`AuthConfigurations.Configs["localhost:5000"].ServerAddress: wrong result. Want %q. Got %q`, want, got) + } +} + func TestAuthAndOtherFields(t *testing.T) { t.Parallel() auth := base64.StdEncoding.EncodeToString([]byte("user:pass")) diff --git a/vendor/github.com/fsouza/go-dockerclient/build_test.go b/vendor/github.com/fsouza/go-dockerclient/build_test.go index 174cca4a6..f3cf0295f 100644 --- a/vendor/github.com/fsouza/go-dockerclient/build_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/build_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build go1.10 + package docker import ( diff --git a/vendor/github.com/fsouza/go-dockerclient/client.go b/vendor/github.com/fsouza/go-dockerclient/client.go index 625d4cd5a..581e31417 100644 --- a/vendor/github.com/fsouza/go-dockerclient/client.go +++ b/vendor/github.com/fsouza/go-dockerclient/client.go @@ -10,6 +10,7 @@ package docker import ( "bufio" "bytes" + "context" "crypto/tls" "crypto/x509" "encoding/json" @@ -32,10 +33,8 @@ import ( "github.com/docker/docker/opts" "github.com/docker/docker/pkg/homedir" - "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/stdcopy" - "golang.org/x/net/context" - "golang.org/x/net/context/ctxhttp" + "github.com/fsouza/go-dockerclient/internal/jsonmessage" ) const ( @@ -59,6 +58,7 @@ var ( apiVersion119, _ = NewAPIVersion("1.19") apiVersion124, _ = NewAPIVersion("1.24") apiVersion125, _ = NewAPIVersion("1.25") + apiVersion135, _ = NewAPIVersion("1.35") ) // APIVersion is an internal representation of a version of the Remote API. @@ -151,7 +151,6 @@ type Client struct { requestedAPIVersion APIVersion serverAPIVersion APIVersion expectedAPIVersion APIVersion - nativeHTTPClient *http.Client } // Dialer is an interface that allows network connections to be dialed @@ -219,11 +218,19 @@ func NewVersionedClient(endpoint string, apiVersionString string) (*Client, erro eventMonitor: new(eventMonitoringState), requestedAPIVersion: requestedAPIVersion, } - c.initializeNativeClient() + c.initializeNativeClient(defaultTransport) return c, nil } -// NewVersionnedTLSClient has been DEPRECATED, please use NewVersionedTLSClient. +// WithTransport replaces underlying HTTP client of Docker Client by accepting +// a function that returns pointer to a transport object. +func (c *Client) WithTransport(trFunc func() *http.Transport) { + c.initializeNativeClient(trFunc) +} + +// NewVersionnedTLSClient is like NewVersionedClient, but with ann extra n. +// +// Deprecated: Use NewVersionedTLSClient instead. func NewVersionnedTLSClient(endpoint string, cert, key, ca, apiVersionString string) (*Client, error) { return NewVersionedTLSClient(endpoint, cert, key, ca, apiVersionString) } @@ -340,20 +347,16 @@ func NewVersionedTLSClientFromBytes(endpoint string, certPEMBlock, keyPEMBlock, eventMonitor: new(eventMonitoringState), requestedAPIVersion: requestedAPIVersion, } - c.initializeNativeClient() + c.initializeNativeClient(defaultTransport) return c, nil } -// SetTimeout takes a timeout and applies it to both the HTTPClient and -// nativeHTTPClient. It should not be called concurrently with any other Client -// methods. +// SetTimeout takes a timeout and applies it to the HTTPClient. It should not +// be called concurrently with any other Client methods. func (c *Client) SetTimeout(t time.Duration) { if c.HTTPClient != nil { c.HTTPClient.Timeout = t } - if c.nativeHTTPClient != nil { - c.nativeHTTPClient.Timeout = t - } } func (c *Client) checkAPIVersion() error { @@ -445,12 +448,10 @@ func (c *Client) do(method, path string, doOptions doOptions) (*http.Response, e return nil, err } } - httpClient := c.HTTPClient protocol := c.endpointURL.Scheme var u string switch protocol { case unixProtocol, namedPipeProtocol: - httpClient = c.nativeHTTPClient u = c.getFakeNativeURL(path) default: u = c.getURL(path) @@ -476,7 +477,7 @@ func (c *Client) do(method, path string, doOptions doOptions) (*http.Response, e ctx = context.Background() } - resp, err := ctxhttp.Do(ctx, httpClient, req) + resp, err := c.HTTPClient.Do(req.WithContext(ctx)) if err != nil { if strings.Contains(err.Error(), "connection refused") { return nil, ErrConnectionRefused @@ -592,7 +593,7 @@ func (c *Client) stream(method, path string, streamOptions streamOptions) error return chooseError(subCtx, err) } } else { - if resp, err = ctxhttp.Do(subCtx, c.HTTPClient, req); err != nil { + if resp, err = c.HTTPClient.Do(req.WithContext(subCtx)); err != nil { if strings.Contains(err.Error(), "connection refused") { return ErrConnectionRefused } @@ -916,6 +917,10 @@ func addQueryStringValue(items url.Values, key string, v reflect.Value) { if v.Int() > 0 { items.Add(key, strconv.FormatInt(v.Int(), 10)) } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if v.Uint() > 0 { + items.Add(key, strconv.FormatUint(v.Uint(), 10)) + } case reflect.Float32, reflect.Float64: if v.Float() > 0 { items.Add(key, strconv.FormatFloat(v.Float(), 'f', -1, 64)) @@ -953,12 +958,20 @@ type Error struct { } func newError(resp *http.Response) *Error { + type ErrMsg struct { + Message string `json:"message"` + } 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)} + var emsg ErrMsg + err = json.Unmarshal(data, &emsg) + if err != nil { + return &Error{Status: resp.StatusCode, Message: string(data)} + } + return &Error{Status: resp.StatusCode, Message: emsg.Message} } func (e *Error) Error() string { diff --git a/vendor/github.com/fsouza/go-dockerclient/client_test.go b/vendor/github.com/fsouza/go-dockerclient/client_test.go index 5989c49ff..a12ab82b0 100644 --- a/vendor/github.com/fsouza/go-dockerclient/client_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/client_test.go @@ -6,6 +6,7 @@ package docker import ( "bytes" + "context" "fmt" "io/ioutil" "net/http" @@ -18,8 +19,6 @@ import ( "strings" "testing" "time" - - "golang.org/x/net/context" ) func TestNewAPIClient(t *testing.T) { @@ -548,7 +547,7 @@ func TestClientStreamContextDeadline(t *testing.T) { if f, ok := w.(http.Flusher); ok { f.Flush() } - time.Sleep(500 * time.Millisecond) + time.Sleep(time.Second) fmt.Fprint(w, "def\n") if f, ok := w.(http.Flusher); ok { f.Flush() @@ -559,7 +558,7 @@ func TestClientStreamContextDeadline(t *testing.T) { t.Fatal(err) } var w bytes.Buffer - ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) + ctx, cancel := context.WithTimeout(context.Background(), 400*time.Millisecond) defer cancel() err = client.stream("POST", "/image/create", streamOptions{ setRawTerminal: true, diff --git a/vendor/github.com/fsouza/go-dockerclient/client_unix.go b/vendor/github.com/fsouza/go-dockerclient/client_unix.go index b578e0303..57d7904ea 100644 --- a/vendor/github.com/fsouza/go-dockerclient/client_unix.go +++ b/vendor/github.com/fsouza/go-dockerclient/client_unix.go @@ -14,17 +14,19 @@ import ( // initializeNativeClient initializes the native Unix domain socket client on // Unix-style operating systems -func (c *Client) initializeNativeClient() { +func (c *Client) initializeNativeClient(trFunc func() *http.Transport) { if c.endpointURL.Scheme != unixProtocol { return } - socketPath := c.endpointURL.Path - tr := defaultTransport() + sockPath := c.endpointURL.Path + + tr := trFunc() + tr.Dial = func(network, addr string) (net.Conn, error) { - return c.Dialer.Dial(unixProtocol, socketPath) + return c.Dialer.Dial(unixProtocol, sockPath) } tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { - return c.Dialer.Dial(unixProtocol, socketPath) + return c.Dialer.Dial(unixProtocol, sockPath) } - c.nativeHTTPClient = &http.Client{Transport: tr} + c.HTTPClient.Transport = tr } diff --git a/vendor/github.com/fsouza/go-dockerclient/client_windows.go b/vendor/github.com/fsouza/go-dockerclient/client_windows.go index c863fb05f..8e7b457d7 100644 --- a/vendor/github.com/fsouza/go-dockerclient/client_windows.go +++ b/vendor/github.com/fsouza/go-dockerclient/client_windows.go @@ -1,9 +1,9 @@ -// +build windows - // Copyright 2016 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build windows + package docker import ( @@ -26,7 +26,7 @@ func (p pipeDialer) Dial(network, address string) (net.Conn, error) { } // initializeNativeClient initializes the native Named Pipe client for Windows -func (c *Client) initializeNativeClient() { +func (c *Client) initializeNativeClient(trFunc func() *http.Transport) { if c.endpointURL.Scheme != namedPipeProtocol { return } @@ -35,11 +35,11 @@ func (c *Client) initializeNativeClient() { timeout := namedPipeConnectTimeout return winio.DialPipe(namedPipePath, &timeout) } - tr := defaultTransport() + tr := trFunc() tr.Dial = dialFunc tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { return dialFunc(network, addr) } c.Dialer = &pipeDialer{dialFunc} - c.nativeHTTPClient = &http.Client{Transport: tr} + c.HTTPClient.Transport = tr } diff --git a/vendor/github.com/fsouza/go-dockerclient/container.go b/vendor/github.com/fsouza/go-dockerclient/container.go index 652deee8f..e24c9fb2e 100644 --- a/vendor/github.com/fsouza/go-dockerclient/container.go +++ b/vendor/github.com/fsouza/go-dockerclient/container.go @@ -5,6 +5,7 @@ package docker import ( + "context" "encoding/json" "errors" "fmt" @@ -16,7 +17,6 @@ import ( "time" "github.com/docker/go-units" - "golang.org/x/net/context" ) // ErrContainerAlreadyExists is the error returned by CreateContainer when the @@ -300,8 +300,10 @@ type Config struct { ExposedPorts map[Port]struct{} `json:"ExposedPorts,omitempty" yaml:"ExposedPorts,omitempty" toml:"ExposedPorts,omitempty"` PublishService string `json:"PublishService,omitempty" yaml:"PublishService,omitempty" toml:"PublishService,omitempty"` StopSignal string `json:"StopSignal,omitempty" yaml:"StopSignal,omitempty" toml:"StopSignal,omitempty"` + StopTimeout int `json:"StopTimeout,omitempty" yaml:"StopTimeout,omitempty" toml:"StopTimeout,omitempty"` Env []string `json:"Env,omitempty" yaml:"Env,omitempty" toml:"Env,omitempty"` Cmd []string `json:"Cmd" yaml:"Cmd" toml:"Cmd"` + Shell []string `json:"Shell,omitempty" yaml:"Shell,omitempty" toml:"Shell,omitempty"` Healthcheck *HealthConfig `json:"Healthcheck,omitempty" yaml:"Healthcheck,omitempty" toml:"Healthcheck,omitempty"` DNS []string `json:"Dns,omitempty" yaml:"Dns,omitempty" toml:"Dns,omitempty"` // For Docker API v1.9 and below only Image string `json:"Image,omitempty" yaml:"Image,omitempty" toml:"Image,omitempty"` @@ -426,8 +428,9 @@ type HealthConfig struct { Test []string `json:"Test,omitempty" yaml:"Test,omitempty" toml:"Test,omitempty"` // Zero means to inherit. Durations are expressed as integer nanoseconds. - Interval time.Duration `json:"Interval,omitempty" yaml:"Interval,omitempty" toml:"Interval,omitempty"` // Interval is the time to wait between checks. - Timeout time.Duration `json:"Timeout,omitempty" yaml:"Timeout,omitempty" toml:"Timeout,omitempty"` // Timeout is the time to wait before considering the check to have hung. + Interval time.Duration `json:"Interval,omitempty" yaml:"Interval,omitempty" toml:"Interval,omitempty"` // Interval is the time to wait between checks. + Timeout time.Duration `json:"Timeout,omitempty" yaml:"Timeout,omitempty" toml:"Timeout,omitempty"` // Timeout is the time to wait before considering the check to have hung. + StartPeriod time.Duration `json:"StartPeriod,omitempty" yaml:"StartPeriod,omitempty" toml:"StartPeriod,omitempty"` // The start period for the container to initialize before the retries starts to count down. // Retries is the number of consecutive failures needed to consider a container as unhealthy. // Zero means inherit. @@ -630,6 +633,11 @@ func (c *Client) CreateContainer(opts CreateContainerOptions) (*Container, error if e.Status == http.StatusConflict { return nil, ErrContainerAlreadyExists } + // Workaround for 17.09 bug returning 400 instead of 409. + // See https://github.com/moby/moby/issues/35021 + if e.Status == http.StatusBadRequest && strings.Contains(e.Message, "Conflict.") { + return nil, ErrContainerAlreadyExists + } } if err != nil { @@ -737,6 +745,7 @@ type HostConfig struct { UTSMode string `json:"UTSMode,omitempty" yaml:"UTSMode,omitempty" toml:"UTSMode,omitempty"` RestartPolicy RestartPolicy `json:"RestartPolicy,omitempty" yaml:"RestartPolicy,omitempty" toml:"RestartPolicy,omitempty"` Devices []Device `json:"Devices,omitempty" yaml:"Devices,omitempty" toml:"Devices,omitempty"` + DeviceCgroupRules []string `json:"DeviceCgroupRules,omitempty" yaml:"DeviceCgroupRules,omitempty" toml:"DeviceCgroupRules,omitempty"` LogConfig LogConfig `json:"LogConfig,omitempty" yaml:"LogConfig,omitempty" toml:"LogConfig,omitempty"` SecurityOpt []string `json:"SecurityOpt,omitempty" yaml:"SecurityOpt,omitempty" toml:"SecurityOpt,omitempty"` Cgroup string `json:"Cgroup,omitempty" yaml:"Cgroup,omitempty" toml:"Cgroup,omitempty"` @@ -745,7 +754,7 @@ type HostConfig struct { MemoryReservation int64 `json:"MemoryReservation,omitempty" yaml:"MemoryReservation,omitempty" toml:"MemoryReservation,omitempty"` KernelMemory int64 `json:"KernelMemory,omitempty" yaml:"KernelMemory,omitempty" toml:"KernelMemory,omitempty"` MemorySwap int64 `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty" toml:"MemorySwap,omitempty"` - MemorySwappiness int64 `json:"MemorySwappiness" yaml:"MemorySwappiness" toml:"MemorySwappiness"` + MemorySwappiness int64 `json:"MemorySwappiness,omitempty" yaml:"MemorySwappiness,omitempty" toml:"MemorySwappiness,omitempty"` CPUShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty" toml:"CpuShares,omitempty"` CPUSet string `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty" toml:"Cpuset,omitempty"` CPUSetCPUs string `json:"CpusetCpus,omitempty" yaml:"CpusetCpus,omitempty" toml:"CpusetCpus,omitempty"` @@ -1044,6 +1053,7 @@ type CPUStats struct { UsageInKernelmode uint64 `json:"usage_in_kernelmode,omitempty" yaml:"usage_in_kernelmode,omitempty" toml:"usage_in_kernelmode,omitempty"` } `json:"cpu_usage,omitempty" yaml:"cpu_usage,omitempty" toml:"cpu_usage,omitempty"` SystemCPUUsage uint64 `json:"system_cpu_usage,omitempty" yaml:"system_cpu_usage,omitempty" toml:"system_cpu_usage,omitempty"` + OnlineCPUs uint64 `json:"online_cpus,omitempty" yaml:"online_cpus,omitempty" toml:"online_cpus,omitempty"` ThrottlingData struct { Periods uint64 `json:"periods,omitempty"` ThrottledPeriods uint64 `json:"throttled_periods,omitempty"` @@ -1179,10 +1189,18 @@ func (c *Client) KillContainer(opts KillContainerOptions) error { path := "/containers/" + opts.ID + "/kill" + "?" + queryString(opts) resp, err := c.do("POST", path, doOptions{context: opts.Context}) if err != nil { - if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { - return &NoSuchContainer{ID: opts.ID} + e, ok := err.(*Error) + if !ok { + return err + } + switch e.Status { + case http.StatusNotFound: + return &NoSuchContainer{ID: opts.ID} + case http.StatusConflict: + return &ContainerNotRunning{ID: opts.ID} + default: + return err } - return err } resp.Body.Close() return nil @@ -1270,9 +1288,10 @@ func (c *Client) DownloadFromContainer(id string, opts DownloadFromContainerOpti }) } -// CopyFromContainerOptions has been DEPRECATED, please use DownloadFromContainerOptions along with DownloadFromContainer. +// CopyFromContainerOptions contains the set of options used for copying +// files from a container. // -// See https://goo.gl/nWk2YQ for more details. +// Deprecated: Use DownloadFromContainerOptions and DownloadFromContainer instead. type CopyFromContainerOptions struct { OutputStream io.Writer `json:"-"` Container string `json:"-"` @@ -1280,9 +1299,9 @@ type CopyFromContainerOptions struct { Context context.Context `json:"-"` } -// CopyFromContainer has been DEPRECATED, please use DownloadFromContainerOptions along with DownloadFromContainer. +// CopyFromContainer copies files from a container. // -// See https://goo.gl/nWk2YQ for more details. +// Deprecated: Use DownloadFromContainer and DownloadFromContainer instead. func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error { if opts.Container == "" { return &NoSuchContainer{ID: opts.Container} @@ -1473,7 +1492,7 @@ type LogsOptions struct { // stderr to LogsOptions.ErrorStream. // // When LogsOptions.RawTerminal is true, callers will get the raw stream on -// LogOptions.OutputStream. The caller can use libraries such as dlog +// LogsOptions.OutputStream. The caller can use libraries such as dlog // (github.com/ahmetalpbalkan/dlog). // // See https://goo.gl/krK0ZH for more details. diff --git a/vendor/github.com/fsouza/go-dockerclient/container_test.go b/vendor/github.com/fsouza/go-dockerclient/container_test.go index 1395e621f..2dfcada65 100644 --- a/vendor/github.com/fsouza/go-dockerclient/container_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/container_test.go @@ -7,6 +7,7 @@ package docker import ( "bufio" "bytes" + "context" "encoding/json" "errors" "fmt" @@ -21,8 +22,6 @@ import ( "strings" "testing" "time" - - "golang.org/x/net/context" ) func TestStateString(t *testing.T) { @@ -217,7 +216,10 @@ func TestInspectContainer(t *testing.T) { ], "Ulimits": [ { "Name": "nofile", "Soft": 1024, "Hard": 2048 } - ] + ], + "Shell": [ + "/bin/sh", "-c" + ] }, "State": { "Running": false, @@ -921,6 +923,21 @@ func TestCreateContainerDuplicateName(t *testing.T) { } } +// Workaround for 17.09 bug returning 400 instead of 409. +// See https://github.com/moby/moby/issues/35021 +func TestCreateContainerDuplicateNameWorkaroundDocker17_09(t *testing.T) { + t.Parallel() + client := newTestClient(&FakeRoundTripper{message: `{"message":"Conflict. The container name \"/c1\" is already in use by container \"2ce137e165dfca5e087f247b5d05a2311f91ef3da4bb7772168446a1a47e2f68\". You have to remove (or rename) that container to be able to reuse that name."}`, status: http.StatusBadRequest}) + config := Config{AttachStdout: true, AttachStdin: true} + container, err := client.CreateContainer(CreateContainerOptions{Config: &config}) + if container != nil { + t.Errorf("CreateContainer: expected container, got %#v.", container) + } + if err != ErrContainerAlreadyExists { + t.Errorf("CreateContainer: Wrong error type. Want %#v. Got %#v.", ErrContainerAlreadyExists, err) + } +} + func TestCreateContainerWithHostConfig(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "{}", status: http.StatusOK} @@ -1314,6 +1331,18 @@ func TestKillContainerNotFound(t *testing.T) { } } +func TestKillContainerNotRunning(t *testing.T) { + t.Parallel() + id := "abcd1234567890" + msg := fmt.Sprintf("Cannot kill container: %[1]s: Container %[1]s is not running", id) + client := newTestClient(&FakeRoundTripper{message: msg, status: http.StatusConflict}) + err := client.KillContainer(KillContainerOptions{ID: id}) + expected := &ContainerNotRunning{ID: id} + if !reflect.DeepEqual(err, expected) { + t.Errorf("KillContainer: Wrong error returned. Want %#v. Got %#v.", expected, err) + } +} + func TestRemoveContainer(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} @@ -2446,7 +2475,8 @@ func TestStats(t *testing.T) { "total_usage" : 36488948, "usage_in_kernelmode" : 20000000 }, - "system_cpu_usage" : 20091722000000000 + "system_cpu_usage" : 20091722000000000, + "online_cpus": 4 }, "precpu_stats" : { "cpu_usage" : { @@ -2460,7 +2490,8 @@ func TestStats(t *testing.T) { "total_usage" : 36488948, "usage_in_kernelmode" : 20000000 }, - "system_cpu_usage" : 20091722000000000 + "system_cpu_usage" : 20091722000000000, + "online_cpus": 4 } }` // 1 second later, cache is 100 @@ -2564,7 +2595,8 @@ func TestStats(t *testing.T) { "total_usage" : 36488948, "usage_in_kernelmode" : 20000000 }, - "system_cpu_usage" : 20091722000000000 + "system_cpu_usage" : 20091722000000000, + "online_cpus": 4 }, "precpu_stats" : { "cpu_usage" : { @@ -2578,7 +2610,8 @@ func TestStats(t *testing.T) { "total_usage" : 36488948, "usage_in_kernelmode" : 20000000 }, - "system_cpu_usage" : 20091722000000000 + "system_cpu_usage" : 20091722000000000, + "online_cpus": 4 } }` var expected1 Stats @@ -2721,11 +2754,11 @@ func TestStartContainerWhenContextTimesOut(t *testing.T) { func TestStopContainerWhenContextTimesOut(t *testing.T) { t.Parallel() - rt := sleepyRoudTripper{sleepDuration: 200 * time.Millisecond} + rt := sleepyRoudTripper{sleepDuration: 300 * time.Millisecond} client := newTestClient(&rt) - ctx, cancel := context.WithTimeout(context.TODO(), 100*time.Millisecond) + ctx, cancel := context.WithTimeout(context.TODO(), 50*time.Millisecond) defer cancel() err := client.StopContainerWithContext("id", 10, ctx) diff --git a/vendor/github.com/fsouza/go-dockerclient/distribution.go b/vendor/github.com/fsouza/go-dockerclient/distribution.go new file mode 100644 index 000000000..d0f8ce74c --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/distribution.go @@ -0,0 +1,26 @@ +// Copyright 2017 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "encoding/json" + + "github.com/docker/docker/api/types/registry" +) + +// InspectDistribution returns image digest and platform information by contacting the registry +func (c *Client) InspectDistribution(name string) (*registry.DistributionInspect, error) { + path := "/distribution/" + name + "/json" + resp, err := c.do("GET", path, doOptions{}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var distributionInspect registry.DistributionInspect + if err := json.NewDecoder(resp.Body).Decode(&distributionInspect); err != nil { + return nil, err + } + return &distributionInspect, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/distribution_test.go b/vendor/github.com/fsouza/go-dockerclient/distribution_test.go new file mode 100644 index 000000000..bb9a9ca0b --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/distribution_test.go @@ -0,0 +1,54 @@ +package docker + +import ( + "encoding/json" + "net/http" + "reflect" + "testing" + + "github.com/docker/docker/api/types/registry" +) + +func TestInspectDistribution(t *testing.T) { + t.Parallel() + jsonDistribution := `{ + "Descriptor": { + "MediaType": "application/vnd.docker.distribution.manifest.v2+json", + "Digest": "sha256:c0537ff6a5218ef531ece93d4984efc99bbf3f7497c0a7726c88e2bb7584dc96", + "Size": 3987495, + "URLs": [ + "" + ] + }, + "Platforms": [ + { + "Architecture": "amd64", + "OS": "linux", + "OSVersion": "", + "OSFeatures": [ + "" + ], + "Variant": "", + "Features": [ + "" + ] + } + ] +}` + + var expected registry.DistributionInspect + err := json.Unmarshal([]byte(jsonDistribution), &expected) + if err != nil { + t.Fatal(err) + } + fakeRT := &FakeRoundTripper{message: jsonDistribution, status: http.StatusOK} + client := newTestClient(fakeRT) + // image name/tag is not present in the reply, so it can be omitted for testing purposes + distributionInspect, err := client.InspectDistribution("") + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(*distributionInspect, expected) { + t.Errorf("InspectDistribution(%q): Expected %#v. Got %#v.", "", expected, distributionInspect) + } +} diff --git a/vendor/github.com/fsouza/go-dockerclient/env.go b/vendor/github.com/fsouza/go-dockerclient/env.go index c54b0b0e8..13fedfb17 100644 --- a/vendor/github.com/fsouza/go-dockerclient/env.go +++ b/vendor/github.com/fsouza/go-dockerclient/env.go @@ -162,7 +162,11 @@ func (env *Env) Map() map[string]string { m := make(map[string]string) for _, kv := range *env { parts := strings.SplitN(kv, "=", 2) - m[parts[0]] = parts[1] + if len(parts) == 1 { + m[parts[0]] = "" + } else { + m[parts[0]] = parts[1] + } } return m } diff --git a/vendor/github.com/fsouza/go-dockerclient/env_test.go b/vendor/github.com/fsouza/go-dockerclient/env_test.go index 71160285d..5f844d0fb 100644 --- a/vendor/github.com/fsouza/go-dockerclient/env_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/env_test.go @@ -351,6 +351,7 @@ func TestMap(t *testing.T) { expected map[string]string }{ {[]string{"PATH=/usr/bin:/bin", "PYTHONPATH=/usr/local"}, map[string]string{"PATH": "/usr/bin:/bin", "PYTHONPATH": "/usr/local"}}, + {[]string{"ENABLE_LOGGING", "PYTHONPATH=/usr/local"}, map[string]string{"ENABLE_LOGGING": "", "PYTHONPATH": "/usr/local"}}, {nil, nil}, } for _, tt := range tests { diff --git a/vendor/github.com/fsouza/go-dockerclient/event.go b/vendor/github.com/fsouza/go-dockerclient/event.go index 21c0584f0..18ae5d5a6 100644 --- a/vendor/github.com/fsouza/go-dockerclient/event.go +++ b/vendor/github.com/fsouza/go-dockerclient/event.go @@ -195,10 +195,25 @@ func (eventState *eventMonitoringState) disableEventMonitoring() error { } func (eventState *eventMonitoringState) monitorEvents(c *Client) { + const ( + noListenersTimeout = 5 * time.Second + noListenersInterval = 10 * time.Millisecond + noListenersMaxTries = noListenersTimeout / noListenersInterval + ) + var err error - for eventState.noListeners() { + for i := time.Duration(0); i < noListenersMaxTries && eventState.noListeners(); i++ { time.Sleep(10 * time.Millisecond) } + + if eventState.noListeners() { + // terminate if no listener is available after 5 seconds. + // Prevents goroutine leak when RemoveEventListener is called + // right after AddEventListener. + eventState.disableEventMonitoring() + return + } + if err = eventState.connectWithRetry(c); err != nil { // terminate if connect failed eventState.disableEventMonitoring() diff --git a/vendor/github.com/fsouza/go-dockerclient/exec.go b/vendor/github.com/fsouza/go-dockerclient/exec.go index 004815309..5e7ea87f6 100644 --- a/vendor/github.com/fsouza/go-dockerclient/exec.go +++ b/vendor/github.com/fsouza/go-dockerclient/exec.go @@ -5,6 +5,7 @@ package docker import ( + "context" "encoding/json" "errors" "fmt" @@ -12,8 +13,6 @@ import ( "net/http" "net/url" "strconv" - - "golang.org/x/net/context" ) // Exec is the type representing a `docker exec` instance and containing the @@ -34,6 +33,7 @@ type CreateExecOptions struct { Cmd []string `json:"Cmd,omitempty" yaml:"Cmd,omitempty" toml:"Cmd,omitempty"` Container string `json:"Container,omitempty" yaml:"Container,omitempty" toml:"Container,omitempty"` User string `json:"User,omitempty" yaml:"User,omitempty" toml:"User,omitempty"` + WorkingDir string `json:"WorkingDir,omitempty" yaml:"WorkingDir,omitempty" toml:"WorkingDir,omitempty"` Context context.Context `json:"-"` Privileged bool `json:"Privileged,omitempty" yaml:"Privileged,omitempty" toml:"Privileged,omitempty"` } @@ -46,6 +46,9 @@ func (c *Client) CreateExec(opts CreateExecOptions) (*Exec, error) { if len(opts.Env) > 0 && c.serverAPIVersion.LessThan(apiVersion125) { return nil, errors.New("exec configuration Env is only supported in API#1.25 and above") } + if len(opts.WorkingDir) > 0 && c.serverAPIVersion.LessThan(apiVersion135) { + return nil, errors.New("exec configuration WorkingDir is only supported in API#1.35 and above") + } path := fmt.Sprintf("/containers/%s/exec", opts.Container) resp, err := c.do("POST", path, doOptions{data: opts, context: opts.Context}) if err != nil { diff --git a/vendor/github.com/fsouza/go-dockerclient/exec_test.go b/vendor/github.com/fsouza/go-dockerclient/exec_test.go index 8480536e0..ba24ddd7a 100644 --- a/vendor/github.com/fsouza/go-dockerclient/exec_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/exec_test.go @@ -120,6 +120,68 @@ func TestExecCreateWithEnv(t *testing.T) { } } +func TestExecCreateWithWorkingDirErr(t *testing.T) { + t.Parallel() + jsonContainer := `{"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"}` + var expected struct{ ID string } + err := json.Unmarshal([]byte(jsonContainer), &expected) + if err != nil { + t.Fatal(err) + } + fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK} + client := newTestClient(fakeRT) + config := CreateExecOptions{ + Container: "test", + AttachStdin: true, + AttachStdout: true, + AttachStderr: false, + Tty: false, + WorkingDir: "/tmp", + Cmd: []string{"touch", "file"}, + User: "a-user", + } + _, err = client.CreateExec(config) + if err == nil || err.Error() != "exec configuration WorkingDir is only supported in API#1.35 and above" { + t.Error("CreateExec: options contain WorkingDir for unsupported api version") + } +} + +func TestExecCreateWithWorkingDir(t *testing.T) { + t.Parallel() + jsonContainer := `{"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"}` + var expected struct{ ID string } + err := json.Unmarshal([]byte(jsonContainer), &expected) + if err != nil { + t.Fatal(err) + } + fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK} + endpoint := "http://localhost:4243" + u, _ := parseEndpoint("http://localhost:4243", false) + testAPIVersion, _ := NewAPIVersion("1.35") + client := Client{ + HTTPClient: &http.Client{Transport: fakeRT}, + Dialer: &net.Dialer{}, + endpoint: endpoint, + endpointURL: u, + SkipServerVersionCheck: true, + serverAPIVersion: testAPIVersion, + } + config := CreateExecOptions{ + Container: "test", + AttachStdin: true, + AttachStdout: true, + AttachStderr: false, + Tty: false, + WorkingDir: "/tmp", + Cmd: []string{"touch", "file"}, + User: "a-user", + } + _, err = client.CreateExec(config) + if err != nil { + t.Error(err) + } +} + func TestExecStartDetached(t *testing.T) { t.Parallel() execID := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" diff --git a/vendor/github.com/fsouza/go-dockerclient/image.go b/vendor/github.com/fsouza/go-dockerclient/image.go index c386ad5da..124e78da3 100644 --- a/vendor/github.com/fsouza/go-dockerclient/image.go +++ b/vendor/github.com/fsouza/go-dockerclient/image.go @@ -6,6 +6,7 @@ package docker import ( "bytes" + "context" "encoding/base64" "encoding/json" "errors" @@ -16,8 +17,6 @@ import ( "os" "strings" "time" - - "golang.org/x/net/context" ) // APIImages represent an image returned in the ListImages call. @@ -473,6 +472,8 @@ type BuildImageOptions struct { NetworkMode string `qs:"networkmode"` InactivityTimeout time.Duration `qs:"-"` CgroupParent string `qs:"cgroupparent"` + SecurityOpt []string `qs:"securityopt"` + Target string `gs:"target"` Context context.Context } diff --git a/vendor/github.com/fsouza/go-dockerclient/image_test.go b/vendor/github.com/fsouza/go-dockerclient/image_test.go index 27220b6e2..1a05a2e11 100644 --- a/vendor/github.com/fsouza/go-dockerclient/image_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/image_test.go @@ -801,6 +801,7 @@ func TestBuildImageParameters(t *testing.T) { Labels: map[string]string{"k": "v"}, NetworkMode: "host", CgroupParent: "cgparent", + SecurityOpt: []string{"securityoptions"}, } err := client.BuildImage(opts) if err != nil && !strings.Contains(err.Error(), "build image fail") { @@ -825,10 +826,11 @@ func TestBuildImageParameters(t *testing.T) { "buildargs": {`{"SOME_VAR":"some_value"}`}, "networkmode": {"host"}, "cgroupparent": {"cgparent"}, + "securityopt": {"securityoptions"}, } got := map[string][]string(req.URL.Query()) if !reflect.DeepEqual(got, expected) { - t.Errorf("BuildImage: wrong query string. Want %#v. Got %#v.", expected, got) + t.Errorf("BuildImage: wrong query string. Want %#v.\n Got %#v.", expected, got) } } diff --git a/vendor/github.com/fsouza/go-dockerclient/internal/archive/archive.go b/vendor/github.com/fsouza/go-dockerclient/internal/archive/archive.go new file mode 100644 index 000000000..a13ee7cca --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/internal/archive/archive.go @@ -0,0 +1,505 @@ +// Copyright 2014 Docker authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the DOCKER-LICENSE file. + +package archive + +import ( + "archive/tar" + "bufio" + "compress/gzip" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/docker/docker/pkg/fileutils" + "github.com/docker/docker/pkg/idtools" + "github.com/docker/docker/pkg/pools" + "github.com/docker/docker/pkg/system" + "github.com/sirupsen/logrus" +) + +const ( + // Uncompressed represents the uncompressed. + Uncompressed Compression = iota + // Bzip2 is bzip2 compression algorithm. + Bzip2 + // Gzip is gzip compression algorithm. + Gzip + // Xz is xz compression algorithm. + Xz +) + +const ( + modeISDIR = 040000 // Directory + modeISFIFO = 010000 // FIFO + modeISREG = 0100000 // Regular file + modeISLNK = 0120000 // Symbolic link + modeISBLK = 060000 // Block special file + modeISCHR = 020000 // Character special file + modeISSOCK = 0140000 // Socket +) + +// Compression is the state represents if compressed or not. +type Compression int + +// Extension returns the extension of a file that uses the specified compression algorithm. +func (compression *Compression) Extension() string { + switch *compression { + case Uncompressed: + return "tar" + case Bzip2: + return "tar.bz2" + case Gzip: + return "tar.gz" + case Xz: + return "tar.xz" + } + return "" +} + +// WhiteoutFormat is the format of whiteouts unpacked +type WhiteoutFormat int + +// TarOptions wraps the tar options. +type TarOptions struct { + IncludeFiles []string + ExcludePatterns []string + Compression Compression + NoLchown bool + UIDMaps []idtools.IDMap + GIDMaps []idtools.IDMap + ChownOpts *idtools.IDPair + IncludeSourceDir bool + // WhiteoutFormat is the expected on disk format for whiteout files. + // This format will be converted to the standard format on pack + // and from the standard format on unpack. + WhiteoutFormat WhiteoutFormat + // When unpacking, specifies whether overwriting a directory with a + // non-directory is allowed and vice versa. + NoOverwriteDirNonDir bool + // For each include when creating an archive, the included name will be + // replaced with the matching name from this map. + RebaseNames map[string]string + InUserNS bool +} + +// TarWithOptions creates an archive from the directory at `path`, only including files whose relative +// paths are included in `options.IncludeFiles` (if non-nil) or not in `options.ExcludePatterns`. +func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) { + + // Fix the source path to work with long path names. This is a no-op + // on platforms other than Windows. + srcPath = fixVolumePathPrefix(srcPath) + + pm, err := fileutils.NewPatternMatcher(options.ExcludePatterns) + if err != nil { + return nil, err + } + + pipeReader, pipeWriter := io.Pipe() + + compressWriter, err := CompressStream(pipeWriter, options.Compression) + if err != nil { + return nil, err + } + + go func() { + ta := newTarAppender( + idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps), + compressWriter, + options.ChownOpts, + ) + ta.WhiteoutConverter = getWhiteoutConverter(options.WhiteoutFormat) + + defer func() { + // Make sure to check the error on Close. + if err := ta.TarWriter.Close(); err != nil { + logrus.Errorf("Can't close tar writer: %s", err) + } + if err := compressWriter.Close(); err != nil { + logrus.Errorf("Can't close compress writer: %s", err) + } + if err := pipeWriter.Close(); err != nil { + logrus.Errorf("Can't close pipe writer: %s", err) + } + }() + + // this buffer is needed for the duration of this piped stream + defer pools.BufioWriter32KPool.Put(ta.Buffer) + + // In general we log errors here but ignore them because + // during e.g. a diff operation the container can continue + // mutating the filesystem and we can see transient errors + // from this + + stat, err := os.Lstat(srcPath) + if err != nil { + return + } + + if !stat.IsDir() { + // We can't later join a non-dir with any includes because the + // 'walk' will error if "file/." is stat-ed and "file" is not a + // directory. So, we must split the source path and use the + // basename as the include. + if len(options.IncludeFiles) > 0 { + logrus.Warn("Tar: Can't archive a file with includes") + } + + dir, base := SplitPathDirEntry(srcPath) + srcPath = dir + options.IncludeFiles = []string{base} + } + + if len(options.IncludeFiles) == 0 { + options.IncludeFiles = []string{"."} + } + + seen := make(map[string]bool) + + for _, include := range options.IncludeFiles { + rebaseName := options.RebaseNames[include] + + walkRoot := getWalkRoot(srcPath, include) + filepath.Walk(walkRoot, func(filePath string, f os.FileInfo, err error) error { + if err != nil { + logrus.Errorf("Tar: Can't stat file %s to tar: %s", srcPath, err) + return nil + } + + relFilePath, err := filepath.Rel(srcPath, filePath) + if err != nil || (!options.IncludeSourceDir && relFilePath == "." && f.IsDir()) { + // Error getting relative path OR we are looking + // at the source directory path. Skip in both situations. + return nil + } + + if options.IncludeSourceDir && include == "." && relFilePath != "." { + relFilePath = strings.Join([]string{".", relFilePath}, string(filepath.Separator)) + } + + skip := false + + // If "include" is an exact match for the current file + // then even if there's an "excludePatterns" pattern that + // matches it, don't skip it. IOW, assume an explicit 'include' + // is asking for that file no matter what - which is true + // for some files, like .dockerignore and Dockerfile (sometimes) + if include != relFilePath { + skip, err = pm.Matches(relFilePath) + if err != nil { + logrus.Errorf("Error matching %s: %v", relFilePath, err) + return err + } + } + + if skip { + // If we want to skip this file and its a directory + // then we should first check to see if there's an + // excludes pattern (e.g. !dir/file) that starts with this + // dir. If so then we can't skip this dir. + + // Its not a dir then so we can just return/skip. + if !f.IsDir() { + return nil + } + + // No exceptions (!...) in patterns so just skip dir + if !pm.Exclusions() { + return filepath.SkipDir + } + + dirSlash := relFilePath + string(filepath.Separator) + + for _, pat := range pm.Patterns() { + if !pat.Exclusion() { + continue + } + if strings.HasPrefix(pat.String()+string(filepath.Separator), dirSlash) { + // found a match - so can't skip this dir + return nil + } + } + + // No matching exclusion dir so just skip dir + return filepath.SkipDir + } + + if seen[relFilePath] { + return nil + } + seen[relFilePath] = true + + // Rename the base resource. + if rebaseName != "" { + var replacement string + if rebaseName != string(filepath.Separator) { + // Special case the root directory to replace with an + // empty string instead so that we don't end up with + // double slashes in the paths. + replacement = rebaseName + } + + relFilePath = strings.Replace(relFilePath, include, replacement, 1) + } + + if err := ta.addTarFile(filePath, relFilePath); err != nil { + logrus.Errorf("Can't add file %s to tar: %s", filePath, err) + // if pipe is broken, stop writing tar stream to it + if err == io.ErrClosedPipe { + return err + } + } + return nil + }) + } + }() + + return pipeReader, nil +} + +// CompressStream compresses the dest with specified compression algorithm. +func CompressStream(dest io.Writer, compression Compression) (io.WriteCloser, error) { + p := pools.BufioWriter32KPool + buf := p.Get(dest) + switch compression { + case Uncompressed: + writeBufWrapper := p.NewWriteCloserWrapper(buf, buf) + return writeBufWrapper, nil + case Gzip: + gzWriter := gzip.NewWriter(dest) + writeBufWrapper := p.NewWriteCloserWrapper(buf, gzWriter) + return writeBufWrapper, nil + case Bzip2, Xz: + // archive/bzip2 does not support writing, and there is no xz support at all + // However, this is not a problem as docker only currently generates gzipped tars + return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension()) + default: + return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension()) + } +} + +type tarWhiteoutConverter interface { + ConvertWrite(*tar.Header, string, os.FileInfo) (*tar.Header, error) + ConvertRead(*tar.Header, string) (bool, error) +} + +type tarAppender struct { + TarWriter *tar.Writer + Buffer *bufio.Writer + + // for hardlink mapping + SeenFiles map[uint64]string + IDMappings *idtools.IDMappings + ChownOpts *idtools.IDPair + + // For packing and unpacking whiteout files in the + // non standard format. The whiteout files defined + // by the AUFS standard are used as the tar whiteout + // standard. + WhiteoutConverter tarWhiteoutConverter +} + +func newTarAppender(idMapping *idtools.IDMappings, writer io.Writer, chownOpts *idtools.IDPair) *tarAppender { + return &tarAppender{ + SeenFiles: make(map[uint64]string), + TarWriter: tar.NewWriter(writer), + Buffer: pools.BufioWriter32KPool.Get(nil), + IDMappings: idMapping, + ChownOpts: chownOpts, + } +} + +// addTarFile adds to the tar archive a file from `path` as `name` +func (ta *tarAppender) addTarFile(path, name string) error { + fi, err := os.Lstat(path) + if err != nil { + return err + } + + var link string + if fi.Mode()&os.ModeSymlink != 0 { + var err error + link, err = os.Readlink(path) + if err != nil { + return err + } + } + + hdr, err := FileInfoHeader(name, fi, link) + if err != nil { + return err + } + if err := ReadSecurityXattrToTarHeader(path, hdr); err != nil { + return err + } + + // if it's not a directory and has more than 1 link, + // it's hard linked, so set the type flag accordingly + if !fi.IsDir() && hasHardlinks(fi) { + inode, err := getInodeFromStat(fi.Sys()) + if err != nil { + return err + } + // a link should have a name that it links too + // and that linked name should be first in the tar archive + if oldpath, ok := ta.SeenFiles[inode]; ok { + hdr.Typeflag = tar.TypeLink + hdr.Linkname = oldpath + hdr.Size = 0 // This Must be here for the writer math to add up! + } else { + ta.SeenFiles[inode] = name + } + } + + //check whether the file is overlayfs whiteout + //if yes, skip re-mapping container ID mappings. + isOverlayWhiteout := fi.Mode()&os.ModeCharDevice != 0 && hdr.Devmajor == 0 && hdr.Devminor == 0 + + //handle re-mapping container ID mappings back to host ID mappings before + //writing tar headers/files. We skip whiteout files because they were written + //by the kernel and already have proper ownership relative to the host + if !isOverlayWhiteout && + !strings.HasPrefix(filepath.Base(hdr.Name), WhiteoutPrefix) && + !ta.IDMappings.Empty() { + fileIDPair, err := getFileUIDGID(fi.Sys()) + if err != nil { + return err + } + hdr.Uid, hdr.Gid, err = ta.IDMappings.ToContainer(fileIDPair) + if err != nil { + return err + } + } + + // explicitly override with ChownOpts + if ta.ChownOpts != nil { + hdr.Uid = ta.ChownOpts.UID + hdr.Gid = ta.ChownOpts.GID + } + + if ta.WhiteoutConverter != nil { + wo, err := ta.WhiteoutConverter.ConvertWrite(hdr, path, fi) + if err != nil { + return err + } + + // If a new whiteout file exists, write original hdr, then + // replace hdr with wo to be written after. Whiteouts should + // always be written after the original. Note the original + // hdr may have been updated to be a whiteout with returning + // a whiteout header + if wo != nil { + if err := ta.TarWriter.WriteHeader(hdr); err != nil { + return err + } + if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 { + return fmt.Errorf("tar: cannot use whiteout for non-empty file") + } + hdr = wo + } + } + + if err := ta.TarWriter.WriteHeader(hdr); err != nil { + return err + } + + if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 { + // We use system.OpenSequential to ensure we use sequential file + // access on Windows to avoid depleting the standby list. + // On Linux, this equates to a regular os.Open. + file, err := system.OpenSequential(path) + if err != nil { + return err + } + + ta.Buffer.Reset(ta.TarWriter) + defer ta.Buffer.Reset(nil) + _, err = io.Copy(ta.Buffer, file) + file.Close() + if err != nil { + return err + } + err = ta.Buffer.Flush() + if err != nil { + return err + } + } + + return nil +} + +// ReadSecurityXattrToTarHeader reads security.capability xattr from filesystem +// to a tar header +func ReadSecurityXattrToTarHeader(path string, hdr *tar.Header) error { + capability, _ := system.Lgetxattr(path, "security.capability") + if capability != nil { + hdr.Xattrs = make(map[string]string) + hdr.Xattrs["security.capability"] = string(capability) + } + return nil +} + +// FileInfoHeader creates a populated Header from fi. +// Compared to archive pkg this function fills in more information. +// Also, regardless of Go version, this function fills file type bits (e.g. hdr.Mode |= modeISDIR), +// which have been deleted since Go 1.9 archive/tar. +func FileInfoHeader(name string, fi os.FileInfo, link string) (*tar.Header, error) { + hdr, err := tar.FileInfoHeader(fi, link) + if err != nil { + return nil, err + } + hdr.Mode = fillGo18FileTypeBits(int64(chmodTarEntry(os.FileMode(hdr.Mode))), fi) + name, err = canonicalTarName(name, fi.IsDir()) + if err != nil { + return nil, fmt.Errorf("tar: cannot canonicalize path: %v", err) + } + hdr.Name = name + if err := setHeaderForSpecialDevice(hdr, name, fi.Sys()); err != nil { + return nil, err + } + return hdr, nil +} + +// fillGo18FileTypeBits fills type bits which have been removed on Go 1.9 archive/tar +// https://github.com/golang/go/commit/66b5a2f +func fillGo18FileTypeBits(mode int64, fi os.FileInfo) int64 { + fm := fi.Mode() + switch { + case fm.IsRegular(): + mode |= modeISREG + case fi.IsDir(): + mode |= modeISDIR + case fm&os.ModeSymlink != 0: + mode |= modeISLNK + case fm&os.ModeDevice != 0: + if fm&os.ModeCharDevice != 0 { + mode |= modeISCHR + } else { + mode |= modeISBLK + } + case fm&os.ModeNamedPipe != 0: + mode |= modeISFIFO + case fm&os.ModeSocket != 0: + mode |= modeISSOCK + } + return mode +} + +// canonicalTarName provides a platform-independent and consistent posix-style +//path for files and directories to be archived regardless of the platform. +func canonicalTarName(name string, isDir bool) (string, error) { + name, err := CanonicalTarNameForPath(name) + if err != nil { + return "", err + } + + // suffix with '/' for directories + if isDir && !strings.HasSuffix(name, "/") { + name += "/" + } + return name, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/internal/archive/archive_linux.go b/vendor/github.com/fsouza/go-dockerclient/internal/archive/archive_linux.go new file mode 100644 index 000000000..9e1f3f2f1 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/internal/archive/archive_linux.go @@ -0,0 +1,104 @@ +// Copyright 2014 Docker authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the DOCKER-LICENSE file. + +package archive + +import ( + "archive/tar" + "os" + "path/filepath" + "strings" + + "github.com/docker/docker/pkg/system" + "golang.org/x/sys/unix" +) + +const ( + // AUFSWhiteoutFormat is the default format for whiteouts + AUFSWhiteoutFormat WhiteoutFormat = iota + // OverlayWhiteoutFormat formats whiteout according to the overlay + // standard. + OverlayWhiteoutFormat +) + +func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter { + if format == OverlayWhiteoutFormat { + return overlayWhiteoutConverter{} + } + return nil +} + +type overlayWhiteoutConverter struct{} + +func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os.FileInfo) (wo *tar.Header, err error) { + // convert whiteouts to AUFS format + if fi.Mode()&os.ModeCharDevice != 0 && hdr.Devmajor == 0 && hdr.Devminor == 0 { + // we just rename the file and make it normal + dir, filename := filepath.Split(hdr.Name) + hdr.Name = filepath.Join(dir, WhiteoutPrefix+filename) + hdr.Mode = 0600 + hdr.Typeflag = tar.TypeReg + hdr.Size = 0 + } + + if fi.Mode()&os.ModeDir != 0 { + // convert opaque dirs to AUFS format by writing an empty file with the prefix + opaque, err := system.Lgetxattr(path, "trusted.overlay.opaque") + if err != nil { + return nil, err + } + if len(opaque) == 1 && opaque[0] == 'y' { + if hdr.Xattrs != nil { + delete(hdr.Xattrs, "trusted.overlay.opaque") + } + + // create a header for the whiteout file + // it should inherit some properties from the parent, but be a regular file + wo = &tar.Header{ + Typeflag: tar.TypeReg, + Mode: hdr.Mode & int64(os.ModePerm), + Name: filepath.Join(hdr.Name, WhiteoutOpaqueDir), + Size: 0, + Uid: hdr.Uid, + Uname: hdr.Uname, + Gid: hdr.Gid, + Gname: hdr.Gname, + AccessTime: hdr.AccessTime, + ChangeTime: hdr.ChangeTime, + } + } + } + + return +} + +func (overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, path string) (bool, error) { + base := filepath.Base(path) + dir := filepath.Dir(path) + + // if a directory is marked as opaque by the AUFS special file, we need to translate that to overlay + if base == WhiteoutOpaqueDir { + err := unix.Setxattr(dir, "trusted.overlay.opaque", []byte{'y'}, 0) + // don't write the file itself + return false, err + } + + // if a file was deleted and we are using overlay, we need to create a character device + if strings.HasPrefix(base, WhiteoutPrefix) { + originalBase := base[len(WhiteoutPrefix):] + originalPath := filepath.Join(dir, originalBase) + + if err := unix.Mknod(originalPath, unix.S_IFCHR, 0); err != nil { + return false, err + } + if err := os.Chown(originalPath, hdr.Uid, hdr.Gid); err != nil { + return false, err + } + + // don't write the file itself + return false, nil + } + + return true, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/internal/archive/archive_other.go b/vendor/github.com/fsouza/go-dockerclient/internal/archive/archive_other.go new file mode 100644 index 000000000..72822c857 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/internal/archive/archive_other.go @@ -0,0 +1,11 @@ +// Copyright 2014 Docker authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the DOCKER-LICENSE file. + +// +build !linux + +package archive + +func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter { + return nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/internal/archive/archive_unix.go b/vendor/github.com/fsouza/go-dockerclient/internal/archive/archive_unix.go new file mode 100644 index 000000000..2633f5020 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/internal/archive/archive_unix.go @@ -0,0 +1,77 @@ +// Copyright 2014 Docker authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the DOCKER-LICENSE file. + +// +build !windows + +package archive + +import ( + "archive/tar" + "errors" + "os" + "path/filepath" + "syscall" + + "github.com/docker/docker/pkg/idtools" + "golang.org/x/sys/unix" +) + +// CanonicalTarNameForPath returns platform-specific filepath +// to canonical posix-style path for tar archival. p is relative +// path. +func CanonicalTarNameForPath(p string) (string, error) { + return p, nil // already unix-style +} + +// fixVolumePathPrefix does platform specific processing to ensure that if +// the path being passed in is not in a volume path format, convert it to one. +func fixVolumePathPrefix(srcPath string) string { + return srcPath +} + +// getWalkRoot calculates the root path when performing a TarWithOptions. +// We use a separate function as this is platform specific. On Linux, we +// can't use filepath.Join(srcPath,include) because this will clean away +// a trailing "." or "/" which may be important. +func getWalkRoot(srcPath string, include string) string { + return srcPath + string(filepath.Separator) + include +} + +func getInodeFromStat(stat interface{}) (inode uint64, err error) { + s, ok := stat.(*syscall.Stat_t) + + if ok { + inode = uint64(s.Ino) + } + + return +} + +func getFileUIDGID(stat interface{}) (idtools.IDPair, error) { + s, ok := stat.(*syscall.Stat_t) + + if !ok { + return idtools.IDPair{}, errors.New("cannot convert stat value to syscall.Stat_t") + } + return idtools.IDPair{UID: int(s.Uid), GID: int(s.Gid)}, nil +} + +func chmodTarEntry(perm os.FileMode) os.FileMode { + return perm // noop for unix as golang APIs provide perm bits correctly +} + +func setHeaderForSpecialDevice(hdr *tar.Header, name string, stat interface{}) (err error) { + s, ok := stat.(*syscall.Stat_t) + + if ok { + // Currently go does not fill in the major/minors + if s.Mode&unix.S_IFBLK != 0 || + s.Mode&unix.S_IFCHR != 0 { + hdr.Devmajor = int64(unix.Major(uint64(s.Rdev))) // nolint: unconvert + hdr.Devminor = int64(unix.Minor(uint64(s.Rdev))) // nolint: unconvert + } + } + + return +} diff --git a/vendor/github.com/fsouza/go-dockerclient/internal/archive/archive_windows.go b/vendor/github.com/fsouza/go-dockerclient/internal/archive/archive_windows.go new file mode 100644 index 000000000..c14875cd7 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/internal/archive/archive_windows.go @@ -0,0 +1,71 @@ +// Copyright 2014 Docker authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the DOCKER-LICENSE file. + +package archive + +import ( + "archive/tar" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/docker/docker/pkg/idtools" + "github.com/docker/docker/pkg/longpath" +) + +// CanonicalTarNameForPath returns platform-specific filepath +// to canonical posix-style path for tar archival. p is relative +// path. +func CanonicalTarNameForPath(p string) (string, error) { + // windows: convert windows style relative path with backslashes + // into forward slashes. Since windows does not allow '/' or '\' + // in file names, it is mostly safe to replace however we must + // check just in case + if strings.Contains(p, "/") { + return "", fmt.Errorf("Windows path contains forward slash: %s", p) + } + return strings.Replace(p, string(os.PathSeparator), "/", -1), nil + +} + +// fixVolumePathPrefix does platform specific processing to ensure that if +// the path being passed in is not in a volume path format, convert it to one. +func fixVolumePathPrefix(srcPath string) string { + return longpath.AddPrefix(srcPath) +} + +// getWalkRoot calculates the root path when performing a TarWithOptions. +// We use a separate function as this is platform specific. +func getWalkRoot(srcPath string, include string) string { + return filepath.Join(srcPath, include) +} + +func getInodeFromStat(stat interface{}) (inode uint64, err error) { + // do nothing. no notion of Inode in stat on Windows + return +} + +func getFileUIDGID(stat interface{}) (idtools.IDPair, error) { + // no notion of file ownership mapping yet on Windows + return idtools.IDPair{0, 0}, nil +} + +// chmodTarEntry is used to adjust the file permissions used in tar header based +// on the platform the archival is done. +func chmodTarEntry(perm os.FileMode) os.FileMode { + //perm &= 0755 // this 0-ed out tar flags (like link, regular file, directory marker etc.) + permPart := perm & os.ModePerm + noPermPart := perm &^ os.ModePerm + // Add the x bit: make everything +x from windows + permPart |= 0111 + permPart &= 0755 + + return noPermPart | permPart +} + +func setHeaderForSpecialDevice(hdr *tar.Header, name string, stat interface{}) (err error) { + // do nothing. no notion of Rdev, Nlink in stat on Windows + return +} diff --git a/vendor/github.com/fsouza/go-dockerclient/internal/archive/changes_unix.go b/vendor/github.com/fsouza/go-dockerclient/internal/archive/changes_unix.go new file mode 100644 index 000000000..39ea287bf --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/internal/archive/changes_unix.go @@ -0,0 +1,16 @@ +// Copyright 2014 Docker authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the DOCKER-LICENSE file. + +// +build !windows + +package archive + +import ( + "os" + "syscall" +) + +func hasHardlinks(fi os.FileInfo) bool { + return fi.Sys().(*syscall.Stat_t).Nlink > 1 +} diff --git a/vendor/github.com/fsouza/go-dockerclient/internal/archive/changes_windows.go b/vendor/github.com/fsouza/go-dockerclient/internal/archive/changes_windows.go new file mode 100644 index 000000000..a93130474 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/internal/archive/changes_windows.go @@ -0,0 +1,11 @@ +// Copyright 2014 Docker authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the DOCKER-LICENSE file. + +package archive + +import "os" + +func hasHardlinks(fi os.FileInfo) bool { + return false +} diff --git a/vendor/github.com/fsouza/go-dockerclient/internal/archive/copy.go b/vendor/github.com/fsouza/go-dockerclient/internal/archive/copy.go new file mode 100644 index 000000000..45d45f20e --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/internal/archive/copy.go @@ -0,0 +1,29 @@ +// Copyright 2014 Docker authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the DOCKER-LICENSE file. + +package archive + +import ( + "os" + "path/filepath" +) + +// SplitPathDirEntry splits the given path between its directory name and its +// basename by first cleaning the path but preserves a trailing "." if the +// original path specified the current directory. +func SplitPathDirEntry(path string) (dir, base string) { + cleanedPath := filepath.Clean(filepath.FromSlash(path)) + + if specifiesCurrentDir(path) { + cleanedPath += string(os.PathSeparator) + "." + } + + return filepath.Dir(cleanedPath), filepath.Base(cleanedPath) +} + +// specifiesCurrentDir returns whether the given path specifies +// a "current directory", i.e., the last path segment is `.`. +func specifiesCurrentDir(path string) bool { + return filepath.Base(path) == "." +} diff --git a/vendor/github.com/fsouza/go-dockerclient/internal/archive/whiteouts.go b/vendor/github.com/fsouza/go-dockerclient/internal/archive/whiteouts.go new file mode 100644 index 000000000..a61c22a08 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/internal/archive/whiteouts.go @@ -0,0 +1,27 @@ +// Copyright 2014 Docker authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the DOCKER-LICENSE file. + +package archive + +// Whiteouts are files with a special meaning for the layered filesystem. +// Docker uses AUFS whiteout files inside exported archives. In other +// filesystems these files are generated/handled on tar creation/extraction. + +// WhiteoutPrefix prefix means file is a whiteout. If this is followed by a +// filename this means that file has been removed from the base layer. +const WhiteoutPrefix = ".wh." + +// WhiteoutMetaPrefix prefix means whiteout has a special meaning and is not +// for removing an actual file. Normally these files are excluded from exported +// archives. +const WhiteoutMetaPrefix = WhiteoutPrefix + WhiteoutPrefix + +// WhiteoutLinkDir is a directory AUFS uses for storing hardlink links to other +// layers. Normally these should not go into exported archives and all changed +// hardlinks should be copied to the top layer. +const WhiteoutLinkDir = WhiteoutMetaPrefix + "plnk" + +// WhiteoutOpaqueDir file means directory has been made opaque - meaning +// readdir calls to this directory do not follow to lower layers. +const WhiteoutOpaqueDir = WhiteoutMetaPrefix + ".opq" diff --git a/vendor/github.com/fsouza/go-dockerclient/internal/jsonmessage/jsonmessage.go b/vendor/github.com/fsouza/go-dockerclient/internal/jsonmessage/jsonmessage.go new file mode 100644 index 000000000..71b3395ce --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/internal/jsonmessage/jsonmessage.go @@ -0,0 +1,339 @@ +// Copyright 2014 Docker authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the DOCKER-LICENSE file. + +package jsonmessage + +import ( + "encoding/json" + "fmt" + "io" + "os" + "strings" + "time" + + "github.com/Nvveen/Gotty" + "github.com/docker/go-units" + "github.com/fsouza/go-dockerclient/internal/term" +) + +// RFC3339NanoFixed is time.RFC3339Nano with nanoseconds padded using zeros to +// ensure the formatted time isalways the same number of characters. +const RFC3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00" + +// JSONError wraps a concrete Code and Message, `Code` is +// is an integer error code, `Message` is the error message. +type JSONError struct { + Code int `json:"code,omitempty"` + Message string `json:"message,omitempty"` +} + +func (e *JSONError) Error() string { + return e.Message +} + +// JSONProgress describes a Progress. terminalFd is the fd of the current terminal, +// Start is the initial value for the operation. Current is the current status and +// value of the progress made towards Total. Total is the end value describing when +// we made 100% progress for an operation. +type JSONProgress struct { + terminalFd uintptr + Current int64 `json:"current,omitempty"` + Total int64 `json:"total,omitempty"` + Start int64 `json:"start,omitempty"` + // If true, don't show xB/yB + HideCounts bool `json:"hidecounts,omitempty"` + Units string `json:"units,omitempty"` + nowFunc func() time.Time + winSize int +} + +func (p *JSONProgress) String() string { + var ( + width = p.width() + pbBox string + numbersBox string + timeLeftBox string + ) + if p.Current <= 0 && p.Total <= 0 { + return "" + } + if p.Total <= 0 { + switch p.Units { + case "": + current := units.HumanSize(float64(p.Current)) + return fmt.Sprintf("%8v", current) + default: + return fmt.Sprintf("%d %s", p.Current, p.Units) + } + } + + percentage := int(float64(p.Current)/float64(p.Total)*100) / 2 + if percentage > 50 { + percentage = 50 + } + if width > 110 { + // this number can't be negative gh#7136 + numSpaces := 0 + if 50-percentage > 0 { + numSpaces = 50 - percentage + } + pbBox = fmt.Sprintf("[%s>%s] ", strings.Repeat("=", percentage), strings.Repeat(" ", numSpaces)) + } + + switch { + case p.HideCounts: + case p.Units == "": // no units, use bytes + current := units.HumanSize(float64(p.Current)) + total := units.HumanSize(float64(p.Total)) + + numbersBox = fmt.Sprintf("%8v/%v", current, total) + + if p.Current > p.Total { + // remove total display if the reported current is wonky. + numbersBox = fmt.Sprintf("%8v", current) + } + default: + numbersBox = fmt.Sprintf("%d/%d %s", p.Current, p.Total, p.Units) + + if p.Current > p.Total { + // remove total display if the reported current is wonky. + numbersBox = fmt.Sprintf("%d %s", p.Current, p.Units) + } + } + + if p.Current > 0 && p.Start > 0 && percentage < 50 { + fromStart := p.now().Sub(time.Unix(p.Start, 0)) + perEntry := fromStart / time.Duration(p.Current) + left := time.Duration(p.Total-p.Current) * perEntry + left = (left / time.Second) * time.Second + + if width > 50 { + timeLeftBox = " " + left.String() + } + } + return pbBox + numbersBox + timeLeftBox +} + +// shim for testing +func (p *JSONProgress) now() time.Time { + if p.nowFunc == nil { + p.nowFunc = func() time.Time { + return time.Now().UTC() + } + } + return p.nowFunc() +} + +// shim for testing +func (p *JSONProgress) width() int { + if p.winSize != 0 { + return p.winSize + } + ws, err := term.GetWinsize(p.terminalFd) + if err == nil { + return int(ws.Width) + } + return 200 +} + +// JSONMessage defines a message struct. It describes +// the created time, where it from, status, ID of the +// message. It's used for docker events. +type JSONMessage struct { + Stream string `json:"stream,omitempty"` + Status string `json:"status,omitempty"` + Progress *JSONProgress `json:"progressDetail,omitempty"` + ProgressMessage string `json:"progress,omitempty"` //deprecated + ID string `json:"id,omitempty"` + From string `json:"from,omitempty"` + Time int64 `json:"time,omitempty"` + TimeNano int64 `json:"timeNano,omitempty"` + Error *JSONError `json:"errorDetail,omitempty"` + ErrorMessage string `json:"error,omitempty"` //deprecated + // Aux contains out-of-band data, such as digests for push signing and image id after building. + Aux *json.RawMessage `json:"aux,omitempty"` +} + +/* Satisfied by gotty.TermInfo as well as noTermInfo from below */ +type termInfo interface { + Parse(attr string, params ...interface{}) (string, error) +} + +type noTermInfo struct{} // canary used when no terminfo. + +func (ti *noTermInfo) Parse(attr string, params ...interface{}) (string, error) { + return "", fmt.Errorf("noTermInfo") +} + +func clearLine(out io.Writer, ti termInfo) { + // el2 (clear whole line) is not exposed by terminfo. + + // First clear line from beginning to cursor + if attr, err := ti.Parse("el1"); err == nil { + fmt.Fprintf(out, "%s", attr) + } else { + fmt.Fprintf(out, "\x1b[1K") + } + // Then clear line from cursor to end + if attr, err := ti.Parse("el"); err == nil { + fmt.Fprintf(out, "%s", attr) + } else { + fmt.Fprintf(out, "\x1b[K") + } +} + +func cursorUp(out io.Writer, ti termInfo, l int) { + if l == 0 { // Should never be the case, but be tolerant + return + } + if attr, err := ti.Parse("cuu", l); err == nil { + fmt.Fprintf(out, "%s", attr) + } else { + fmt.Fprintf(out, "\x1b[%dA", l) + } +} + +func cursorDown(out io.Writer, ti termInfo, l int) { + if l == 0 { // Should never be the case, but be tolerant + return + } + if attr, err := ti.Parse("cud", l); err == nil { + fmt.Fprintf(out, "%s", attr) + } else { + fmt.Fprintf(out, "\x1b[%dB", l) + } +} + +// Display displays the JSONMessage to `out`. `termInfo` is non-nil if `out` +// is a terminal. If this is the case, it will erase the entire current line +// when displaying the progressbar. +func (jm *JSONMessage) Display(out io.Writer, termInfo termInfo) error { + if jm.Error != nil { + if jm.Error.Code == 401 { + return fmt.Errorf("authentication is required") + } + return jm.Error + } + var endl string + if termInfo != nil && jm.Stream == "" && jm.Progress != nil { + clearLine(out, termInfo) + endl = "\r" + fmt.Fprintf(out, endl) + } else if jm.Progress != nil && jm.Progress.String() != "" { //disable progressbar in non-terminal + return nil + } + if jm.TimeNano != 0 { + fmt.Fprintf(out, "%s ", time.Unix(0, jm.TimeNano).Format(RFC3339NanoFixed)) + } else if jm.Time != 0 { + fmt.Fprintf(out, "%s ", time.Unix(jm.Time, 0).Format(RFC3339NanoFixed)) + } + if jm.ID != "" { + fmt.Fprintf(out, "%s: ", jm.ID) + } + if jm.From != "" { + fmt.Fprintf(out, "(from %s) ", jm.From) + } + if jm.Progress != nil && termInfo != nil { + fmt.Fprintf(out, "%s %s%s", jm.Status, jm.Progress.String(), endl) + } else if jm.ProgressMessage != "" { //deprecated + fmt.Fprintf(out, "%s %s%s", jm.Status, jm.ProgressMessage, endl) + } else if jm.Stream != "" { + fmt.Fprintf(out, "%s%s", jm.Stream, endl) + } else { + fmt.Fprintf(out, "%s%s\n", jm.Status, endl) + } + return nil +} + +// DisplayJSONMessagesStream displays a json message stream from `in` to `out`, `isTerminal` +// describes if `out` is a terminal. If this is the case, it will print `\n` at the end of +// each line and move the cursor while displaying. +func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr, isTerminal bool, auxCallback func(JSONMessage)) error { + var ( + dec = json.NewDecoder(in) + ids = make(map[string]int) + ) + + var termInfo termInfo + + if isTerminal { + term := os.Getenv("TERM") + if term == "" { + term = "vt102" + } + + var err error + if termInfo, err = gotty.OpenTermInfo(term); err != nil { + termInfo = &noTermInfo{} + } + } + + for { + diff := 0 + var jm JSONMessage + if err := dec.Decode(&jm); err != nil { + if err == io.EOF { + break + } + return err + } + + if jm.Aux != nil { + if auxCallback != nil { + auxCallback(jm) + } + continue + } + + if jm.Progress != nil { + jm.Progress.terminalFd = terminalFd + } + if jm.ID != "" && (jm.Progress != nil || jm.ProgressMessage != "") { + line, ok := ids[jm.ID] + if !ok { + // NOTE: This approach of using len(id) to + // figure out the number of lines of history + // only works as long as we clear the history + // when we output something that's not + // accounted for in the map, such as a line + // with no ID. + line = len(ids) + ids[jm.ID] = line + if termInfo != nil { + fmt.Fprintf(out, "\n") + } + } + diff = len(ids) - line + if termInfo != nil { + cursorUp(out, termInfo, diff) + } + } else { + // When outputting something that isn't progress + // output, clear the history of previous lines. We + // don't want progress entries from some previous + // operation to be updated (for example, pull -a + // with multiple tags). + ids = make(map[string]int) + } + err := jm.Display(out, termInfo) + if jm.ID != "" && termInfo != nil { + cursorDown(out, termInfo, diff) + } + if err != nil { + return err + } + } + return nil +} + +type stream interface { + io.Writer + FD() uintptr + IsTerminal() bool +} + +// DisplayJSONMessagesToStream prints json messages to the output stream +func DisplayJSONMessagesToStream(in io.Reader, stream stream, auxCallback func(JSONMessage)) error { + return DisplayJSONMessagesStream(in, stream, stream.FD(), stream.IsTerminal(), auxCallback) +} diff --git a/vendor/github.com/fsouza/go-dockerclient/internal/term/term.go b/vendor/github.com/fsouza/go-dockerclient/internal/term/term.go new file mode 100644 index 000000000..af06911d8 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/internal/term/term.go @@ -0,0 +1,13 @@ +// Copyright 2014 Docker authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the DOCKER-LICENSE file. + +package term + +// Winsize represents the size of the terminal window. +type Winsize struct { + Height uint16 + Width uint16 + x uint16 + y uint16 +} diff --git a/vendor/github.com/fsouza/go-dockerclient/internal/term/winsize.go b/vendor/github.com/fsouza/go-dockerclient/internal/term/winsize.go new file mode 100644 index 000000000..2a9964a0d --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/internal/term/winsize.go @@ -0,0 +1,16 @@ +// Copyright 2014 Docker authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the DOCKER-LICENSE file. + +// +build !windows + +package term + +import "golang.org/x/sys/unix" + +// GetWinsize returns the window size based on the specified file descriptor. +func GetWinsize(fd uintptr) (*Winsize, error) { + uws, err := unix.IoctlGetWinsize(int(fd), unix.TIOCGWINSZ) + ws := &Winsize{Height: uws.Row, Width: uws.Col, x: uws.Xpixel, y: uws.Ypixel} + return ws, err +} diff --git a/vendor/github.com/fsouza/go-dockerclient/internal/term/winsize_windows.go b/vendor/github.com/fsouza/go-dockerclient/internal/term/winsize_windows.go new file mode 100644 index 000000000..4a07a5d19 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/internal/term/winsize_windows.go @@ -0,0 +1,22 @@ +// Copyright 2014 Docker authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the DOCKER-LICENSE file. + +package term + +import "github.com/Azure/go-ansiterm/winterm" + +// GetWinsize returns the window size based on the specified file descriptor. +func GetWinsize(fd uintptr) (*Winsize, error) { + info, err := winterm.GetConsoleScreenBufferInfo(fd) + if err != nil { + return nil, err + } + + winsize := &Winsize{ + Width: uint16(info.Window.Right - info.Window.Left + 1), + Height: uint16(info.Window.Bottom - info.Window.Top + 1), + } + + return winsize, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/misc.go b/vendor/github.com/fsouza/go-dockerclient/misc.go index 0482838ab..1fc37b14e 100644 --- a/vendor/github.com/fsouza/go-dockerclient/misc.go +++ b/vendor/github.com/fsouza/go-dockerclient/misc.go @@ -5,6 +5,7 @@ package docker import ( + "context" "encoding/json" "net" "strings" @@ -16,7 +17,12 @@ import ( // // See https://goo.gl/mU7yje for more details. func (c *Client) Version() (*Env, error) { - resp, err := c.do("GET", "/version", doOptions{}) + return c.VersionWithContext(nil) +} + +// VersionWithContext returns version information about the docker server. +func (c *Client) VersionWithContext(ctx context.Context) (*Env, error) { + resp, err := c.do("GET", "/version", doOptions{context: ctx}) if err != nil { return nil, err } @@ -68,6 +74,7 @@ type DockerInfo struct { Architecture string IndexServerAddress string RegistryConfig *ServiceConfig + SecurityOptions []string NCPU int MemTotal int64 DockerRootDir string diff --git a/vendor/github.com/fsouza/go-dockerclient/misc_test.go b/vendor/github.com/fsouza/go-dockerclient/misc_test.go index d30391df5..0e27d99a0 100644 --- a/vendor/github.com/fsouza/go-dockerclient/misc_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/misc_test.go @@ -90,7 +90,12 @@ func TestInfo(t *testing.T) { } }, "Mirrors":null - } + }, + "SecurityOptions": [ + "name=apparmor", + "name=seccomp", + "profile=default" + ] }` fakeRT := FakeRoundTripper{message: body, status: http.StatusOK} client := newTestClient(&fakeRT) @@ -117,6 +122,11 @@ func TestInfo(t *testing.T) { }, }, }, + SecurityOptions: []string{ + "name=apparmor", + "name=seccomp", + "profile=default", + }, } info, err := client.Info() if err != nil { diff --git a/vendor/github.com/fsouza/go-dockerclient/network.go b/vendor/github.com/fsouza/go-dockerclient/network.go index b79b67cd5..c6ddb22c6 100644 --- a/vendor/github.com/fsouza/go-dockerclient/network.go +++ b/vendor/github.com/fsouza/go-dockerclient/network.go @@ -5,12 +5,11 @@ package docker import ( + "context" "encoding/json" "errors" "fmt" "net/http" - - "golang.org/x/net/context" ) // ErrNetworkAlreadyExists is the error returned by CreateNetwork when the @@ -112,7 +111,7 @@ func (c *Client) NetworkInfo(id string) (*Network, error) { type CreateNetworkOptions struct { Name string `json:"Name" yaml:"Name" toml:"Name"` Driver string `json:"Driver" yaml:"Driver" toml:"Driver"` - IPAM IPAMOptions `json:"IPAM" yaml:"IPAM" toml:"IPAM"` + IPAM *IPAMOptions `json:"IPAM,omitempty" yaml:"IPAM" toml:"IPAM"` Options map[string]interface{} `json:"Options" yaml:"Options" toml:"Options"` Labels map[string]string `json:"Labels" yaml:"Labels" toml:"Labels"` CheckDuplicate bool `json:"CheckDuplicate" yaml:"CheckDuplicate" toml:"CheckDuplicate"` diff --git a/vendor/github.com/fsouza/go-dockerclient/plugin.go b/vendor/github.com/fsouza/go-dockerclient/plugin.go new file mode 100644 index 000000000..a28ff3d1e --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/plugin.go @@ -0,0 +1,418 @@ +// Copyright 2018 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "context" + "encoding/json" + "io/ioutil" + "net/http" +) + +// PluginPrivilege represents a privilege for a plugin. +type PluginPrivilege struct { + Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` + Description string `json:"Description,omitempty" yaml:"Description,omitempty" toml:"Description,omitempty"` + Value []string `json:"Value,omitempty" yaml:"Value,omitempty" toml:"Value,omitempty"` +} + +// InstallPluginOptions specify parameters to the InstallPlugins function. +// +// See https://goo.gl/C4t7Tz for more details. +type InstallPluginOptions struct { + Remote string + Name string + Plugins []PluginPrivilege `qs:"-"` + + Auth AuthConfiguration + + Context context.Context +} + +// InstallPlugins installs a plugin or returns an error in case of failure. +// +// See https://goo.gl/C4t7Tz for more details. +func (c *Client) InstallPlugins(opts InstallPluginOptions) error { + path := "/plugins/pull?" + queryString(opts) + resp, err := c.do("POST", path, doOptions{ + data: opts.Plugins, + context: opts.Context, + }) + defer resp.Body.Close() + if err != nil { + return err + } + return nil +} + +// PluginSettings stores plugin settings. +// +// See https://goo.gl/C4t7Tz for more details. +type PluginSettings struct { + Env []string `json:"Env,omitempty" yaml:"Env,omitempty" toml:"Env,omitempty"` + Args []string `json:"Args,omitempty" yaml:"Args,omitempty" toml:"Args,omitempty"` + Devices []string `json:"Devices,omitempty" yaml:"Devices,omitempty" toml:"Devices,omitempty"` +} + +// PluginInterface stores plugin interface. +// +// See https://goo.gl/C4t7Tz for more details. +type PluginInterface struct { + Types []string `json:"Types,omitempty" yaml:"Types,omitempty" toml:"Types,omitempty"` + Socket string `json:"Socket,omitempty" yaml:"Socket,omitempty" toml:"Socket,omitempty"` +} + +// PluginNetwork stores plugin network type. +// +// See https://goo.gl/C4t7Tz for more details. +type PluginNetwork struct { + Type string `json:"Type,omitempty" yaml:"Type,omitempty" toml:"Type,omitempty"` +} + +// PluginLinux stores plugin linux setting. +// +// See https://goo.gl/C4t7Tz for more details. +type PluginLinux struct { + Capabilities []string `json:"Capabilities,omitempty" yaml:"Capabilities,omitempty" toml:"Capabilities,omitempty"` + AllowAllDevices bool `json:"AllowAllDevices,omitempty" yaml:"AllowAllDevices,omitempty" toml:"AllowAllDevices,omitempty"` + Devices []PluginLinuxDevices `json:"Devices,omitempty" yaml:"Devices,omitempty" toml:"Devices,omitempty"` +} + +// PluginLinuxDevices stores plugin linux device setting. +// +// See https://goo.gl/C4t7Tz for more details. +type PluginLinuxDevices struct { + Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` + Description string `json:"Documentation,omitempty" yaml:"Documentation,omitempty" toml:"Documentation,omitempty"` + Settable []string `json:"Settable,omitempty" yaml:"Settable,omitempty" toml:"Settable,omitempty"` + Path string `json:"Path,omitempty" yaml:"Path,omitempty" toml:"Path,omitempty"` +} + +// PluginEnv stores plugin environment. +// +// See https://goo.gl/C4t7Tz for more details. +type PluginEnv struct { + Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` + Description string `json:"Description,omitempty" yaml:"Description,omitempty" toml:"Description,omitempty"` + Settable []string `json:"Settable,omitempty" yaml:"Settable,omitempty" toml:"Settable,omitempty"` + Value string `json:"Value,omitempty" yaml:"Value,omitempty" toml:"Value,omitempty"` +} + +// PluginArgs stores plugin arguments. +// +// See https://goo.gl/C4t7Tz for more details. +type PluginArgs struct { + Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` + Description string `json:"Description,omitempty" yaml:"Description,omitempty" toml:"Description,omitempty"` + Settable []string `json:"Settable,omitempty" yaml:"Settable,omitempty" toml:"Settable,omitempty"` + Value []string `json:"Value,omitempty" yaml:"Value,omitempty" toml:"Value,omitempty"` +} + +// PluginUser stores plugin user. +// +// See https://goo.gl/C4t7Tz for more details. +type PluginUser struct { + UID int32 `json:"UID,omitempty" yaml:"UID,omitempty" toml:"UID,omitempty"` + GID int32 `json:"GID,omitempty" yaml:"GID,omitempty" toml:"GID,omitempty"` +} + +// PluginConfig stores plugin config. +// +// See https://goo.gl/C4t7Tz for more details. +type PluginConfig struct { + Description string `json:"Description,omitempty" yaml:"Description,omitempty" toml:"Description,omitempty"` + Documentation string + Interface PluginInterface `json:"Interface,omitempty" yaml:"Interface,omitempty" toml:"Interface,omitempty"` + Entrypoint []string `json:"Entrypoint,omitempty" yaml:"Entrypoint,omitempty" toml:"Entrypoint,omitempty"` + WorkDir string `json:"WorkDir,omitempty" yaml:"WorkDir,omitempty" toml:"WorkDir,omitempty"` + User PluginUser `json:"User,omitempty" yaml:"User,omitempty" toml:"User,omitempty"` + Network PluginNetwork `json:"Network,omitempty" yaml:"Network,omitempty" toml:"Network,omitempty"` + Linux PluginLinux `json:"Linux,omitempty" yaml:"Linux,omitempty" toml:"Linux,omitempty"` + PropagatedMount string `json:"PropagatedMount,omitempty" yaml:"PropagatedMount,omitempty" toml:"PropagatedMount,omitempty"` + Mounts []Mount `json:"Mounts,omitempty" yaml:"Mounts,omitempty" toml:"Mounts,omitempty"` + Env []PluginEnv `json:"Env,omitempty" yaml:"Env,omitempty" toml:"Env,omitempty"` + Args PluginArgs `json:"Args,omitempty" yaml:"Args,omitempty" toml:"Args,omitempty"` +} + +// PluginDetail specify results from the ListPlugins function. +// +// See https://goo.gl/C4t7Tz for more details. +type PluginDetail struct { + ID string `json:"Id,omitempty" yaml:"Id,omitempty" toml:"Id,omitempty"` + Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` + Tag string `json:"Tag,omitempty" yaml:"Tag,omitempty" toml:"Tag,omitempty"` + Active bool `json:"Active,omitempty" yaml:"Active,omitempty" toml:"Active,omitempty"` + Settings PluginSettings `json:"Settings,omitempty" yaml:"Settings,omitempty" toml:"Settings,omitempty"` + Config PluginConfig `json:"Config,omitempty" yaml:"Config,omitempty" toml:"Config,omitempty"` +} + +// ListPlugins returns pluginDetails or an error. +// +// See https://goo.gl/C4t7Tz for more details. +func (c *Client) ListPlugins(ctx context.Context) ([]PluginDetail, error) { + resp, err := c.do("GET", "/plugins", doOptions{ + context: ctx, + }) + if err != nil { + return nil, err + } + defer resp.Body.Close() + pluginDetails := make([]PluginDetail, 0) + if err := json.NewDecoder(resp.Body).Decode(&pluginDetails); err != nil { + return nil, err + } + return pluginDetails, nil +} + +// ListFilteredPluginsOptions specify parameters to the ListFilteredPlugins function. +// +// See https://goo.gl/C4t7Tz for more details. +type ListFilteredPluginsOptions struct { + Filters map[string][]string + Context context.Context +} + +// ListFilteredPlugins returns pluginDetails or an error. +// +// See https://goo.gl/rmdmWg for more details. +func (c *Client) ListFilteredPlugins(opts ListFilteredPluginsOptions) ([]PluginDetail, error) { + path := "/plugins/json?" + queryString(opts) + resp, err := c.do("GET", path, doOptions{ + context: opts.Context, + }) + if err != nil { + return nil, err + } + defer resp.Body.Close() + pluginDetails := make([]PluginDetail, 0) + if err := json.NewDecoder(resp.Body).Decode(&pluginDetails); err != nil { + return nil, err + } + return pluginDetails, nil +} + +// GetPluginPrivileges returns pulginPrivileges or an error. +// +// See https://goo.gl/C4t7Tz for more details. +func (c *Client) GetPluginPrivileges(name string, ctx context.Context) ([]PluginPrivilege, error) { + resp, err := c.do("GET", "/plugins/privileges?remote="+name, doOptions{ + context: ctx, + }) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var pluginPrivileges []PluginPrivilege + if err := json.NewDecoder(resp.Body).Decode(&pluginPrivileges); err != nil { + return nil, err + } + return pluginPrivileges, nil +} + +// InspectPlugins returns a pluginDetail or an error. +// +// See https://goo.gl/C4t7Tz for more details. +func (c *Client) InspectPlugins(name string, ctx context.Context) (*PluginDetail, error) { + resp, err := c.do("GET", "/plugins/"+name+"/json", doOptions{ + context: ctx, + }) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return nil, &NoSuchPlugin{ID: name} + } + return nil, err + } + resp.Body.Close() + var pluginDetail PluginDetail + if err := json.NewDecoder(resp.Body).Decode(&pluginDetail); err != nil { + return nil, err + } + return &pluginDetail, nil +} + +// RemovePluginOptions specify parameters to the RemovePlugin function. +// +// See https://goo.gl/C4t7Tz for more details. +type RemovePluginOptions struct { + // The Name of the plugin. + Name string `qs:"-"` + + Force bool `qs:"force"` + Context context.Context +} + +// RemovePlugin returns a PluginDetail or an error. +// +// See https://goo.gl/C4t7Tz for more details. +func (c *Client) RemovePlugin(opts RemovePluginOptions) (*PluginDetail, error) { + path := "/plugins/" + opts.Name + "?" + queryString(opts) + resp, err := c.do("DELETE", path, doOptions{context: opts.Context}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return nil, &NoSuchPlugin{ID: opts.Name} + } + return nil, err + } + resp.Body.Close() + var pluginDetail PluginDetail + if err := json.NewDecoder(resp.Body).Decode(&pluginDetail); err != nil { + return nil, err + } + return &pluginDetail, nil +} + +// EnablePluginOptions specify parameters to the EnablePlugin function. +// +// See https://goo.gl/C4t7Tz for more details. +type EnablePluginOptions struct { + // The Name of the plugin. + Name string `qs:"-"` + Timeout int64 `qs:"timeout"` + + Context context.Context +} + +// EnablePlugin enables plugin that opts point or returns an error. +// +// See https://goo.gl/C4t7Tz for more details. +func (c *Client) EnablePlugin(opts EnablePluginOptions) error { + path := "/plugins/" + opts.Name + "/enable?" + queryString(opts) + resp, err := c.do("POST", path, doOptions{context: opts.Context}) + defer resp.Body.Close() + if err != nil { + return err + } + resp.Body.Close() + return nil +} + +// DisablePluginOptions specify parameters to the DisablePlugin function. +// +// See https://goo.gl/C4t7Tz for more details. +type DisablePluginOptions struct { + // The Name of the plugin. + Name string `qs:"-"` + + Context context.Context +} + +// DisablePlugin disables plugin that opts point or returns an error. +// +// See https://goo.gl/C4t7Tz for more details. +func (c *Client) DisablePlugin(opts DisablePluginOptions) error { + path := "/plugins/" + opts.Name + "/disable" + resp, err := c.do("POST", path, doOptions{context: opts.Context}) + defer resp.Body.Close() + if err != nil { + return err + } + resp.Body.Close() + return nil +} + +// CreatePluginOptions specify parameters to the CreatePlugin function. +// +// See https://goo.gl/C4t7Tz for more details. +type CreatePluginOptions struct { + // The Name of the plugin. + Name string `qs:"name"` + // Path to tar containing plugin + Path string `qs:"-"` + + Context context.Context +} + +// CreatePlugin creates plugin that opts point or returns an error. +// +// See https://goo.gl/C4t7Tz for more details. +func (c *Client) CreatePlugin(opts CreatePluginOptions) (string, error) { + path := "/plugins/create?" + queryString(opts) + resp, err := c.do("POST", path, doOptions{ + data: opts.Path, + context: opts.Context}) + defer resp.Body.Close() + if err != nil { + return "", err + } + containerNameBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + return string(containerNameBytes), nil +} + +// PushPluginOptions specify parameters to PushPlugin function. +// +// See https://goo.gl/C4t7Tz for more details. +type PushPluginOptions struct { + // The Name of the plugin. + Name string + + Context context.Context +} + +// PushPlugin pushes plugin that opts point or returns an error. +// +// See https://goo.gl/C4t7Tz for more details. +func (c *Client) PushPlugin(opts PushPluginOptions) error { + path := "/plugins/" + opts.Name + "/push" + resp, err := c.do("POST", path, doOptions{context: opts.Context}) + defer resp.Body.Close() + if err != nil { + return err + } + return nil +} + +// ConfigurePluginOptions specify parameters to the ConfigurePlugin +// +// See https://goo.gl/C4t7Tz for more details. +type ConfigurePluginOptions struct { + // The Name of the plugin. + Name string `qs:"name"` + Envs []string + + Context context.Context +} + +// ConfigurePlugin configures plugin that opts point or returns an error. +// +// See https://goo.gl/C4t7Tz for more details. +func (c *Client) ConfigurePlugin(opts ConfigurePluginOptions) error { + path := "/plugins/" + opts.Name + "/set" + resp, err := c.do("POST", path, doOptions{ + data: opts.Envs, + context: opts.Context, + }) + defer resp.Body.Close() + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchPlugin{ID: opts.Name} + } + return err + } + return nil +} + +// NoSuchPlugin is the error returned when a given plugin does not exist. +type NoSuchPlugin struct { + ID string + Err error +} + +func (err *NoSuchPlugin) Error() string { + if err.Err != nil { + return err.Err.Error() + } + return "No such plugin: " + err.ID +} diff --git a/vendor/github.com/fsouza/go-dockerclient/plugins_test.go b/vendor/github.com/fsouza/go-dockerclient/plugins_test.go new file mode 100644 index 000000000..f66478275 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/plugins_test.go @@ -0,0 +1,318 @@ +// Copyright 2018 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "reflect" + + "testing" +) + +var ( + expectPluginDetail = PluginDetail{ + ID: "5724e2c8652da337ab2eedd19fc6fc0ec908e4bd907c7421bf6a8dfc70c4c078", + Name: "tiborvass/sample-volume-plugin", + Tag: "latest", + Active: true, + Settings: PluginSettings{ + Env: []string{"DEBUG=0"}, + Args: nil, + Devices: nil, + }, + Config: PluginConfig{ + Description: "A sample volume plugin for Docker", + Documentation: "https://docs.docker.com/engine/extend/plugins/", + Interface: PluginInterface{ + Types: []string{"docker.volumedriver/1.0"}, + Socket: "plugins.sock", + }, + Entrypoint: []string{ + "/usr/bin/sample-volume-plugin", + "/data", + }, + WorkDir: "", + User: PluginUser{}, + Network: PluginNetwork{Type: ""}, + Linux: PluginLinux{Capabilities: nil, AllowAllDevices: false, Devices: nil}, + Mounts: nil, + PropagatedMount: "/data", + Env: []PluginEnv{ + { + Name: "DEBUG", + Description: "If set, prints debug messages", + Settable: nil, + Value: "0", + }, + }, + Args: PluginArgs{ + Name: "args", + Description: "command line arguments", + Settable: nil, + Value: []string{}, + }, + }, + } +) + +const ( + jsonPluginDetail = `{ + "Id": "5724e2c8652da337ab2eedd19fc6fc0ec908e4bd907c7421bf6a8dfc70c4c078", + "Name": "tiborvass/sample-volume-plugin", + "Tag": "latest", + "Active": true, + "Settings": { + "Env": [ + "DEBUG=0" + ], + "Args": null, + "Devices": null + }, + "Config": { + "Description": "A sample volume plugin for Docker", + "Documentation": "https://docs.docker.com/engine/extend/plugins/", + "Interface": { + "Types": [ + "docker.volumedriver/1.0" + ], + "Socket": "plugins.sock" + }, + "Entrypoint": [ + "/usr/bin/sample-volume-plugin", + "/data" + ], + "WorkDir": "", + "User": {}, + "Network": { + "Type": "" + }, + "Linux": { + "Capabilities": null, + "AllowAllDevices": false, + "Devices": null + }, + "Mounts": null, + "PropagatedMount": "/data", + "Env": [ + { + "Name": "DEBUG", + "Description": "If set, prints debug messages", + "Settable": null, + "Value": "0" + } + ], + "Args": { + "Name": "args", + "Description": "command line arguments", + "Settable": null, + "Value": [] + } + } + }` +) + +func TestListPlugins(t *testing.T) { + t.Parallel() + jsonPlugins := fmt.Sprintf("[%s]", jsonPluginDetail) + var expected []PluginDetail + err := json.Unmarshal([]byte(jsonPlugins), &expected) + if err != nil { + t.Fatal(err) + } + client := newTestClient(&FakeRoundTripper{message: jsonPlugins, status: http.StatusOK}) + pluginDetails, err := client.ListPlugins(context.Background()) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(pluginDetails, expected) { + t.Errorf("ListPlugins: Expected %#v. Got %#v.", expected, pluginDetails) + } +} + +func TestListFilteredPlugins(t *testing.T) { + t.Parallel() + jsonPlugins := fmt.Sprintf("[%s]", jsonPluginDetail) + var expected []PluginDetail + err := json.Unmarshal([]byte(jsonPlugins), &expected) + if err != nil { + t.Fatal(err) + } + client := newTestClient(&FakeRoundTripper{message: jsonPlugins, status: http.StatusOK}) + + pluginDetails, err := client.ListFilteredPlugins( + ListFilteredPluginsOptions{ + Filters: map[string][]string{ + "capability": {"volumedriver"}, + "enabled": {"true"}, + }, + Context: context.Background()}) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(pluginDetails, expected) { + t.Errorf("ListPlugins: Expected %#v. Got %#v.", expected, pluginDetails) + } +} + +func TestListFilteredPluginsFailure(t *testing.T) { + t.Parallel() + var tests = []struct { + status int + message string + }{ + {400, "bad parameter"}, + {500, "internal server error"}, + } + for _, tt := range tests { + client := newTestClient(&FakeRoundTripper{message: tt.message, status: tt.status}) + expected := Error{Status: tt.status, Message: tt.message} + pluginDetails, err := client.ListFilteredPlugins(ListFilteredPluginsOptions{}) + if !reflect.DeepEqual(expected, *err.(*Error)) { + t.Errorf("Wrong error in ListFilteredPlugins. Want %#v. Got %#v.", expected, err) + } + if len(pluginDetails) > 0 { + t.Errorf("ListFilteredPlugins failure. Expected empty list. Got %#v.", pluginDetails) + } + } +} + +func TestGetPluginPrivileges(t *testing.T) { + t.Parallel() + name := "test_plugin" + jsonPluginPrivileges := `[ { "Name": "network", "Description": "", "Value": [ "host" ] }]` + fakeRT := &FakeRoundTripper{message: jsonPluginPrivileges, status: http.StatusNoContent} + client := newTestClient(fakeRT) + expected := []PluginPrivilege{ + { + Name: "network", + Description: "", + Value: []string{"host"}, + }} + pluginPrivileges, err := client.GetPluginPrivileges(name, context.Background()) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(pluginPrivileges, expected) { + t.Errorf("PluginPrivileges: Expected %#v. Got %#v.", expected, pluginPrivileges) + } +} + +func TestInstallPlugins(t *testing.T) { + opts := InstallPluginOptions{ + Remote: "", Name: "test", + Plugins: []PluginPrivilege{ + { + Name: "network", + Description: "", + Value: []string{"host"}, + }, + }, + Context: context.Background(), + Auth: AuthConfiguration{}, + } + + client := newTestClient(&FakeRoundTripper{message: "", status: http.StatusOK}) + err := client.InstallPlugins(opts) + if err != nil { + t.Fatal(err) + } +} + +func TestInspectPlugin(t *testing.T) { + name := "test_plugin" + fakeRT := &FakeRoundTripper{message: jsonPluginDetail, status: http.StatusNoContent} + client := newTestClient(fakeRT) + pluginPrivileges, err := client.InspectPlugins(name, context.Background()) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(pluginPrivileges, &expectPluginDetail) { + t.Errorf("InspectPlugins: Expected %#v. Got %#v.", &expectPluginDetail, pluginPrivileges) + } +} + +func TestRemovePlugin(t *testing.T) { + opts := RemovePluginOptions{ + Name: "test_plugin", + Force: false, + Context: context.Background(), + } + fakeRT := &FakeRoundTripper{message: jsonPluginDetail, status: http.StatusNoContent} + client := newTestClient(fakeRT) + pluginPrivileges, err := client.RemovePlugin(opts) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(pluginPrivileges, &expectPluginDetail) { + t.Errorf("RemovePlugin: Expected %#v. Got %#v.", &expectPluginDetail, pluginPrivileges) + } +} + +func TestEnablePlugin(t *testing.T) { + opts := EnablePluginOptions{ + Name: "test", + Timeout: 5, + Context: context.Background(), + } + client := newTestClient(&FakeRoundTripper{message: "", status: http.StatusOK}) + err := client.EnablePlugin(opts) + if err != nil { + t.Fatal(err) + } +} + +func TestDisablePlugin(t *testing.T) { + opts := DisablePluginOptions{ + Name: "test", + Context: context.Background(), + } + client := newTestClient(&FakeRoundTripper{message: "", status: http.StatusOK}) + err := client.DisablePlugin(opts) + if err != nil { + t.Fatal(err) + } +} + +func TestCreatePlugin(t *testing.T) { + opts := CreatePluginOptions{ + Name: "test", + Path: "", + Context: context.Background(), + } + client := newTestClient(&FakeRoundTripper{message: "", status: http.StatusOK}) + _, err := client.CreatePlugin(opts) + if err != nil { + t.Fatal(err) + } +} + +func TestPushPlugin(t *testing.T) { + opts := PushPluginOptions{ + Name: "test", + Context: context.Background(), + } + client := newTestClient(&FakeRoundTripper{message: "", status: http.StatusOK}) + err := client.PushPlugin(opts) + if err != nil { + t.Fatal(err) + } +} + +func TestConfigurePlugin(t *testing.T) { + opts := ConfigurePluginOptions{ + Name: "test", + Envs: []string{}, + Context: context.Background(), + } + client := newTestClient(&FakeRoundTripper{message: "", status: http.StatusOK}) + err := client.ConfigurePlugin(opts) + if err != nil { + t.Fatal(err) + } +} diff --git a/vendor/github.com/fsouza/go-dockerclient/swarm.go b/vendor/github.com/fsouza/go-dockerclient/swarm.go index 6d9086a55..a257758fc 100644 --- a/vendor/github.com/fsouza/go-dockerclient/swarm.go +++ b/vendor/github.com/fsouza/go-dockerclient/swarm.go @@ -5,6 +5,7 @@ package docker import ( + "context" "encoding/json" "errors" "net/http" @@ -12,7 +13,6 @@ import ( "strconv" "github.com/docker/docker/api/types/swarm" - "golang.org/x/net/context" ) var ( diff --git a/vendor/github.com/fsouza/go-dockerclient/swarm_configs.go b/vendor/github.com/fsouza/go-dockerclient/swarm_configs.go new file mode 100644 index 000000000..fb73ab2ef --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/swarm_configs.go @@ -0,0 +1,171 @@ +// Copyright 2017 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "context" + "encoding/json" + "net/http" + "net/url" + "strconv" + + "github.com/docker/docker/api/types/swarm" +) + +// NoSuchConfig is the error returned when a given config does not exist. +type NoSuchConfig struct { + ID string + Err error +} + +func (err *NoSuchConfig) Error() string { + if err.Err != nil { + return err.Err.Error() + } + return "No such config: " + err.ID +} + +// CreateConfigOptions specify parameters to the CreateConfig function. +// +// See https://goo.gl/KrVjHz for more details. +type CreateConfigOptions struct { + Auth AuthConfiguration `qs:"-"` + swarm.ConfigSpec + Context context.Context +} + +// CreateConfig creates a new config, returning the config instance +// or an error in case of failure. +// +// See https://goo.gl/KrVjHz for more details. +func (c *Client) CreateConfig(opts CreateConfigOptions) (*swarm.Config, error) { + headers, err := headersWithAuth(opts.Auth) + if err != nil { + return nil, err + } + path := "/configs/create?" + queryString(opts) + resp, err := c.do("POST", path, doOptions{ + headers: headers, + data: opts.ConfigSpec, + forceJSON: true, + context: opts.Context, + }) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var config swarm.Config + if err := json.NewDecoder(resp.Body).Decode(&config); err != nil { + return nil, err + } + return &config, nil +} + +// RemoveConfigOptions encapsulates options to remove a config. +// +// See https://goo.gl/Tqrtya for more details. +type RemoveConfigOptions struct { + ID string `qs:"-"` + Context context.Context +} + +// RemoveConfig removes a config, returning an error in case of failure. +// +// See https://goo.gl/Tqrtya for more details. +func (c *Client) RemoveConfig(opts RemoveConfigOptions) error { + path := "/configs/" + opts.ID + resp, err := c.do("DELETE", path, doOptions{context: opts.Context}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchConfig{ID: opts.ID} + } + return err + } + resp.Body.Close() + return nil +} + +// UpdateConfigOptions specify parameters to the UpdateConfig function. +// +// See https://goo.gl/wu3MmS for more details. +type UpdateConfigOptions struct { + Auth AuthConfiguration `qs:"-"` + swarm.ConfigSpec + Context context.Context + Version uint64 +} + +// UpdateConfig updates the config at ID with the options +// +// Only label can be updated +// https://docs.docker.com/engine/api/v1.33/#operation/ConfigUpdate +// See https://goo.gl/wu3MmS for more details. +func (c *Client) UpdateConfig(id string, opts UpdateConfigOptions) error { + headers, err := headersWithAuth(opts.Auth) + if err != nil { + return err + } + params := make(url.Values) + params.Set("version", strconv.FormatUint(opts.Version, 10)) + resp, err := c.do("POST", "/configs/"+id+"/update?"+params.Encode(), doOptions{ + headers: headers, + data: opts.ConfigSpec, + forceJSON: true, + context: opts.Context, + }) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchConfig{ID: id} + } + return err + } + defer resp.Body.Close() + return nil +} + +// InspectConfig returns information about a config by its ID. +// +// See https://goo.gl/dHmr75 for more details. +func (c *Client) InspectConfig(id string) (*swarm.Config, error) { + path := "/configs/" + id + resp, err := c.do("GET", path, doOptions{}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return nil, &NoSuchConfig{ID: id} + } + return nil, err + } + defer resp.Body.Close() + var config swarm.Config + if err := json.NewDecoder(resp.Body).Decode(&config); err != nil { + return nil, err + } + return &config, nil +} + +// ListConfigsOptions specify parameters to the ListConfigs function. +// +// See https://goo.gl/DwvNMd for more details. +type ListConfigsOptions struct { + Filters map[string][]string + Context context.Context +} + +// ListConfigs returns a slice of configs matching the given criteria. +// +// See https://goo.gl/DwvNMd for more details. +func (c *Client) ListConfigs(opts ListConfigsOptions) ([]swarm.Config, error) { + path := "/configs?" + queryString(opts) + resp, err := c.do("GET", path, doOptions{context: opts.Context}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var configs []swarm.Config + if err := json.NewDecoder(resp.Body).Decode(&configs); err != nil { + return nil, err + } + return configs, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/swarm_configs_test.go b/vendor/github.com/fsouza/go-dockerclient/swarm_configs_test.go new file mode 100644 index 000000000..f8b445fd8 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/swarm_configs_test.go @@ -0,0 +1,251 @@ +// Copyright 2017 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "encoding/base64" + "encoding/json" + "net/http" + "net/url" + "reflect" + "testing" + + "github.com/docker/docker/api/types/swarm" +) + +func TestCreateConfig(t *testing.T) { + t.Parallel() + result := `{ + "Id": "d1c00f91353ab0fe368363fab76d124cc764f2db8e11832f89f5ce21c2ece675" +}` + var expected swarm.Config + err := json.Unmarshal([]byte(result), &expected) + if err != nil { + t.Fatal(err) + } + fakeRT := &FakeRoundTripper{message: result, status: http.StatusOK} + client := newTestClient(fakeRT) + opts := CreateConfigOptions{} + config, err := client.CreateConfig(opts) + if err != nil { + t.Fatal(err) + } + id := "d1c00f91353ab0fe368363fab76d124cc764f2db8e11832f89f5ce21c2ece675" + if config.ID != id { + t.Errorf("CreateConfig: wrong ID. Want %q. Got %q.", id, config.ID) + } + req := fakeRT.requests[0] + if req.Method != "POST" { + t.Errorf("CreateConfig: wrong HTTP method. Want %q. Got %q.", "POST", req.Method) + } + expectedURL, _ := url.Parse(client.getURL("/configs/create")) + if gotPath := req.URL.Path; gotPath != expectedURL.Path { + t.Errorf("CreateConfig: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath) + } + var gotBody Config + err = json.NewDecoder(req.Body).Decode(&gotBody) + if err != nil { + t.Fatal(err) + } +} + +func TestRemoveConfig(t *testing.T) { + t.Parallel() + fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} + client := newTestClient(fakeRT) + id := "d1c00f91353ab0fe368363fab76d124cc764f2db8e11832f89f5ce21c2ece675" + opts := RemoveConfigOptions{ID: id} + err := client.RemoveConfig(opts) + if err != nil { + t.Fatal(err) + } + req := fakeRT.requests[0] + if req.Method != "DELETE" { + t.Errorf("RemoveConfig(%q): wrong HTTP method. Want %q. Got %q.", id, "DELETE", req.Method) + } + expectedURL, _ := url.Parse(client.getURL("/configs/" + id)) + if gotPath := req.URL.Path; gotPath != expectedURL.Path { + t.Errorf("RemoveConfig(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) + } +} + +func TestRemoveConfigNotFound(t *testing.T) { + t.Parallel() + client := newTestClient(&FakeRoundTripper{message: "no such config", status: http.StatusNotFound}) + err := client.RemoveConfig(RemoveConfigOptions{ID: "a2334"}) + expected := &NoSuchConfig{ID: "a2334"} + if !reflect.DeepEqual(err, expected) { + t.Errorf("RemoveConfig: Wrong error returned. Want %#v. Got %#v.", expected, err) + } +} + +func TestUpdateConfig(t *testing.T) { + t.Parallel() + fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} + client := newTestClient(fakeRT) + id := "d1c00f91353ab0fe368363fab76d124cc764f2db8e11832f89f5ce21c2ece675" + update := UpdateConfigOptions{Version: 23} + err := client.UpdateConfig(id, update) + if err != nil { + t.Fatal(err) + } + req := fakeRT.requests[0] + if req.Method != "POST" { + t.Errorf("UpdateConfig: wrong HTTP method. Want %q. Got %q.", "POST", req.Method) + } + expectedURL, _ := url.Parse(client.getURL("/configs/" + id + "/update?version=23")) + if gotURI := req.URL.RequestURI(); gotURI != expectedURL.RequestURI() { + t.Errorf("UpdateConfig: Wrong path in request. Want %q. Got %q.", expectedURL.RequestURI(), gotURI) + } + expectedContentType := "application/json" + if contentType := req.Header.Get("Content-Type"); contentType != expectedContentType { + t.Errorf("UpdateConfig: Wrong content-type in request. Want %q. Got %q.", expectedContentType, contentType) + } + var out UpdateConfigOptions + if err := json.NewDecoder(req.Body).Decode(&out); err != nil { + t.Fatal(err) + } + update.Version = 0 + if !reflect.DeepEqual(out, update) { + t.Errorf("UpdateConfig: wrong body\ngot %#v\nwant %#v", out, update) + } +} + +func TestUpdateConfigWithAuthentication(t *testing.T) { + t.Parallel() + fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} + client := newTestClient(fakeRT) + id := "d1c00f91353ab0fe368363fab76d124cc764f2db8e11832f89f5ce21c2ece675" + update := UpdateConfigOptions{Version: 23} + update.Auth = AuthConfiguration{ + Username: "gopher", + Password: "gopher123", + Email: "gopher@tsuru.io", + } + + err := client.UpdateConfig(id, update) + if err != nil { + t.Fatal(err) + } + req := fakeRT.requests[0] + if req.Method != "POST" { + t.Errorf("UpdateConfig: wrong HTTP method. Want %q. Got %q.", "POST", req.Method) + } + expectedURL, _ := url.Parse(client.getURL("/configs/" + id + "/update?version=23")) + if gotURI := req.URL.RequestURI(); gotURI != expectedURL.RequestURI() { + t.Errorf("UpdateConfig: Wrong path in request. Want %q. Got %q.", expectedURL.RequestURI(), gotURI) + } + expectedContentType := "application/json" + if contentType := req.Header.Get("Content-Type"); contentType != expectedContentType { + t.Errorf("UpdateConfig: Wrong content-type in request. Want %q. Got %q.", expectedContentType, contentType) + } + var out UpdateConfigOptions + if err := json.NewDecoder(req.Body).Decode(&out); err != nil { + t.Fatal(err) + } + var updateAuth AuthConfiguration + + auth, err := base64.URLEncoding.DecodeString(req.Header.Get("X-Registry-Auth")) + if err != nil { + t.Errorf("UpdateConfig: caught error decoding auth. %#v", err.Error()) + } + + err = json.Unmarshal(auth, &updateAuth) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(updateAuth, update.Auth) { + t.Errorf("UpdateConfig: wrong auth configuration. Want %#v. Got %#v", update.Auth, updateAuth) + } +} + +func TestUpdateConfigNotFound(t *testing.T) { + t.Parallel() + client := newTestClient(&FakeRoundTripper{message: "no such Config", status: http.StatusNotFound}) + update := UpdateConfigOptions{} + err := client.UpdateConfig("notfound", update) + expected := &NoSuchConfig{ID: "notfound"} + if !reflect.DeepEqual(err, expected) { + t.Errorf("UpdateConfig: Wrong error returned. Want %#v. Got %#v.", expected, err) + } +} + +func TestInspectConfigNotFound(t *testing.T) { + t.Parallel() + client := newTestClient(&FakeRoundTripper{message: "no such config", status: http.StatusNotFound}) + config, err := client.InspectConfig("notfound") + if config != nil { + t.Errorf("InspectConfig: Expected Config, got %#v", config) + } + expected := &NoSuchConfig{ID: "notfound"} + if !reflect.DeepEqual(err, expected) { + t.Errorf("InspectConfig: Wrong error returned. Want %#v. Got %#v.", expected, err) + } +} + +func TestInspectConfig(t *testing.T) { + t.Parallel() + jsonConfig := `{ + "ID": "ktnbjxoalbkvbvedmg1urrz8h", + "Version": { + "Index": 11 + }, + "CreatedAt": "2016-11-05T01:20:17.327670065Z", + "UpdatedAt": "2016-11-05T01:20:17.327670065Z", + "Spec": { + "Name": "app-dev.crt" + } +}` + var expected swarm.Config + err := json.Unmarshal([]byte(jsonConfig), &expected) + if err != nil { + t.Fatal(err) + } + fakeRT := &FakeRoundTripper{message: jsonConfig, status: http.StatusOK} + client := newTestClient(fakeRT) + id := "ktnbjxoalbkvbvedmg1urrz8h" + config, err := client.InspectConfig(id) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(*config, expected) { + t.Errorf("InspectConfig(%q): Expected %#v. Got %#v.", id, expected, config) + } + expectedURL, _ := url.Parse(client.getURL("/configs/ktnbjxoalbkvbvedmg1urrz8h")) + if gotPath := fakeRT.requests[0].URL.Path; gotPath != expectedURL.Path { + t.Errorf("InspectConfig(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) + } +} + +func TestListConfigs(t *testing.T) { + t.Parallel() + jsonConfigs := `[ + { + "ID": "ktnbjxoalbkvbvedmg1urrz8h", + "Version": { + "Index": 11 + }, + "CreatedAt": "2016-11-05T01:20:17.327670065Z", + "UpdatedAt": "2016-11-05T01:20:17.327670065Z", + "Spec": { + "Name": "server.conf" + } + } +]` + var expected []swarm.Config + err := json.Unmarshal([]byte(jsonConfigs), &expected) + if err != nil { + t.Fatal(err) + } + client := newTestClient(&FakeRoundTripper{message: jsonConfigs, status: http.StatusOK}) + configs, err := client.ListConfigs(ListConfigsOptions{}) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(configs, expected) { + t.Errorf("ListConfigs: Expected %#v. Got %#v.", expected, configs) + } +} diff --git a/vendor/github.com/fsouza/go-dockerclient/node.go b/vendor/github.com/fsouza/go-dockerclient/swarm_node.go similarity index 99% rename from vendor/github.com/fsouza/go-dockerclient/node.go rename to vendor/github.com/fsouza/go-dockerclient/swarm_node.go index 843402541..095653cd9 100644 --- a/vendor/github.com/fsouza/go-dockerclient/node.go +++ b/vendor/github.com/fsouza/go-dockerclient/swarm_node.go @@ -5,13 +5,13 @@ package docker import ( + "context" "encoding/json" "net/http" "net/url" "strconv" "github.com/docker/docker/api/types/swarm" - "golang.org/x/net/context" ) // NoSuchNode is the error returned when a given node does not exist. diff --git a/vendor/github.com/fsouza/go-dockerclient/node_test.go b/vendor/github.com/fsouza/go-dockerclient/swarm_node_test.go similarity index 100% rename from vendor/github.com/fsouza/go-dockerclient/node_test.go rename to vendor/github.com/fsouza/go-dockerclient/swarm_node_test.go diff --git a/vendor/github.com/fsouza/go-dockerclient/swarm_secrets.go b/vendor/github.com/fsouza/go-dockerclient/swarm_secrets.go new file mode 100644 index 000000000..5a3b82ca5 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/swarm_secrets.go @@ -0,0 +1,171 @@ +// Copyright 2016 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "context" + "encoding/json" + "net/http" + "net/url" + "strconv" + + "github.com/docker/docker/api/types/swarm" +) + +// NoSuchSecret is the error returned when a given secret does not exist. +type NoSuchSecret struct { + ID string + Err error +} + +func (err *NoSuchSecret) Error() string { + if err.Err != nil { + return err.Err.Error() + } + return "No such secret: " + err.ID +} + +// CreateSecretOptions specify parameters to the CreateSecret function. +// +// See https://goo.gl/KrVjHz for more details. +type CreateSecretOptions struct { + Auth AuthConfiguration `qs:"-"` + swarm.SecretSpec + Context context.Context +} + +// CreateSecret creates a new secret, returning the secret instance +// or an error in case of failure. +// +// See https://goo.gl/KrVjHz for more details. +func (c *Client) CreateSecret(opts CreateSecretOptions) (*swarm.Secret, error) { + headers, err := headersWithAuth(opts.Auth) + if err != nil { + return nil, err + } + path := "/secrets/create?" + queryString(opts) + resp, err := c.do("POST", path, doOptions{ + headers: headers, + data: opts.SecretSpec, + forceJSON: true, + context: opts.Context, + }) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var secret swarm.Secret + if err := json.NewDecoder(resp.Body).Decode(&secret); err != nil { + return nil, err + } + return &secret, nil +} + +// RemoveSecretOptions encapsulates options to remove a secret. +// +// See https://goo.gl/Tqrtya for more details. +type RemoveSecretOptions struct { + ID string `qs:"-"` + Context context.Context +} + +// RemoveSecret removes a secret, returning an error in case of failure. +// +// See https://goo.gl/Tqrtya for more details. +func (c *Client) RemoveSecret(opts RemoveSecretOptions) error { + path := "/secrets/" + opts.ID + resp, err := c.do("DELETE", path, doOptions{context: opts.Context}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchSecret{ID: opts.ID} + } + return err + } + resp.Body.Close() + return nil +} + +// UpdateSecretOptions specify parameters to the UpdateSecret function. +// +// Only label can be updated +// See https://docs.docker.com/engine/api/v1.33/#operation/SecretUpdate +// See https://goo.gl/wu3MmS for more details. +type UpdateSecretOptions struct { + Auth AuthConfiguration `qs:"-"` + swarm.SecretSpec + Context context.Context + Version uint64 +} + +// UpdateSecret updates the secret at ID with the options +// +// See https://goo.gl/wu3MmS for more details. +func (c *Client) UpdateSecret(id string, opts UpdateSecretOptions) error { + headers, err := headersWithAuth(opts.Auth) + if err != nil { + return err + } + params := make(url.Values) + params.Set("version", strconv.FormatUint(opts.Version, 10)) + resp, err := c.do("POST", "/secrets/"+id+"/update?"+params.Encode(), doOptions{ + headers: headers, + data: opts.SecretSpec, + forceJSON: true, + context: opts.Context, + }) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchSecret{ID: id} + } + return err + } + defer resp.Body.Close() + return nil +} + +// InspectSecret returns information about a secret by its ID. +// +// See https://goo.gl/dHmr75 for more details. +func (c *Client) InspectSecret(id string) (*swarm.Secret, error) { + path := "/secrets/" + id + resp, err := c.do("GET", path, doOptions{}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return nil, &NoSuchSecret{ID: id} + } + return nil, err + } + defer resp.Body.Close() + var secret swarm.Secret + if err := json.NewDecoder(resp.Body).Decode(&secret); err != nil { + return nil, err + } + return &secret, nil +} + +// ListSecretsOptions specify parameters to the ListSecrets function. +// +// See https://goo.gl/DwvNMd for more details. +type ListSecretsOptions struct { + Filters map[string][]string + Context context.Context +} + +// ListSecrets returns a slice of secrets matching the given criteria. +// +// See https://goo.gl/DwvNMd for more details. +func (c *Client) ListSecrets(opts ListSecretsOptions) ([]swarm.Secret, error) { + path := "/secrets?" + queryString(opts) + resp, err := c.do("GET", path, doOptions{context: opts.Context}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var secrets []swarm.Secret + if err := json.NewDecoder(resp.Body).Decode(&secrets); err != nil { + return nil, err + } + return secrets, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/swarm_secrets_test.go b/vendor/github.com/fsouza/go-dockerclient/swarm_secrets_test.go new file mode 100644 index 000000000..6b4e6f4b1 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/swarm_secrets_test.go @@ -0,0 +1,285 @@ +// Copyright 2017 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "encoding/base64" + "encoding/json" + "net/http" + "net/url" + "reflect" + "testing" + + "github.com/docker/docker/api/types/swarm" +) + +func TestCreateSecret(t *testing.T) { + t.Parallel() + result := `{ + "Id": "13417726f7654bc286201f7c9accc98ccbd190efcc4753bf8ecfc0b61ef3dde8" +}` + var expected swarm.Secret + err := json.Unmarshal([]byte(result), &expected) + if err != nil { + t.Fatal(err) + } + fakeRT := &FakeRoundTripper{message: result, status: http.StatusOK} + client := newTestClient(fakeRT) + opts := CreateSecretOptions{} + secret, err := client.CreateSecret(opts) + if err != nil { + t.Fatal(err) + } + id := "13417726f7654bc286201f7c9accc98ccbd190efcc4753bf8ecfc0b61ef3dde8" + if secret.ID != id { + t.Errorf("CreateSecret: wrong ID. Want %q. Got %q.", id, secret.ID) + } + req := fakeRT.requests[0] + if req.Method != "POST" { + t.Errorf("CreateSecret: wrong HTTP method. Want %q. Got %q.", "POST", req.Method) + } + expectedURL, _ := url.Parse(client.getURL("/secrets/create")) + if gotPath := req.URL.Path; gotPath != expectedURL.Path { + t.Errorf("CreateSecret: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath) + } + var gotBody Config + err = json.NewDecoder(req.Body).Decode(&gotBody) + if err != nil { + t.Fatal(err) + } +} + +func TestRemoveSecret(t *testing.T) { + t.Parallel() + fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} + client := newTestClient(fakeRT) + id := "13417726f7654bc286201f7c9accc98ccbd190efcc4753bf8ecfc0b61ef3dde8" + opts := RemoveSecretOptions{ID: id} + err := client.RemoveSecret(opts) + if err != nil { + t.Fatal(err) + } + req := fakeRT.requests[0] + if req.Method != "DELETE" { + t.Errorf("RemoveSecret(%q): wrong HTTP method. Want %q. Got %q.", id, "DELETE", req.Method) + } + expectedURL, _ := url.Parse(client.getURL("/secrets/" + id)) + if gotPath := req.URL.Path; gotPath != expectedURL.Path { + t.Errorf("RemoveSecret(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) + } +} + +func TestRemoveSecretNotFound(t *testing.T) { + t.Parallel() + client := newTestClient(&FakeRoundTripper{message: "no such secret", status: http.StatusNotFound}) + err := client.RemoveSecret(RemoveSecretOptions{ID: "a2334"}) + expected := &NoSuchSecret{ID: "a2334"} + if !reflect.DeepEqual(err, expected) { + t.Errorf("RemoveSecret: Wrong error returned. Want %#v. Got %#v.", expected, err) + } +} + +func TestUpdateSecret(t *testing.T) { + t.Parallel() + fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} + client := newTestClient(fakeRT) + id := "13417726f7654bc286201f7c9accc98ccbd190efcc4753bf8ecfc0b61ef3dde8" + update := UpdateSecretOptions{Version: 23} + err := client.UpdateSecret(id, update) + if err != nil { + t.Fatal(err) + } + req := fakeRT.requests[0] + if req.Method != "POST" { + t.Errorf("UpdateSecret: wrong HTTP method. Want %q. Got %q.", "POST", req.Method) + } + expectedURL, _ := url.Parse(client.getURL("/secrets/" + id + "/update?version=23")) + if gotURI := req.URL.RequestURI(); gotURI != expectedURL.RequestURI() { + t.Errorf("UpdateSecret: Wrong path in request. Want %q. Got %q.", expectedURL.RequestURI(), gotURI) + } + expectedContentType := "application/json" + if contentType := req.Header.Get("Content-Type"); contentType != expectedContentType { + t.Errorf("UpdateSecret: Wrong content-type in request. Want %q. Got %q.", expectedContentType, contentType) + } + var out UpdateSecretOptions + if err := json.NewDecoder(req.Body).Decode(&out); err != nil { + t.Fatal(err) + } + update.Version = 0 + if !reflect.DeepEqual(out, update) { + t.Errorf("UpdateSecret: wrong body\ngot %#v\nwant %#v", out, update) + } +} + +func TestUpdateSecretWithAuthentication(t *testing.T) { + t.Parallel() + fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} + client := newTestClient(fakeRT) + id := "13417726f7654bc286201f7c9accc98ccbd190efcc4753bf8ecfc0b61ef3dde8" + update := UpdateSecretOptions{Version: 23} + update.Auth = AuthConfiguration{ + Username: "gopher", + Password: "gopher123", + Email: "gopher@tsuru.io", + } + + err := client.UpdateSecret(id, update) + if err != nil { + t.Fatal(err) + } + req := fakeRT.requests[0] + if req.Method != "POST" { + t.Errorf("UpdateSecret: wrong HTTP method. Want %q. Got %q.", "POST", req.Method) + } + expectedURL, _ := url.Parse(client.getURL("/secrets/" + id + "/update?version=23")) + if gotURI := req.URL.RequestURI(); gotURI != expectedURL.RequestURI() { + t.Errorf("UpdateSecret: Wrong path in request. Want %q. Got %q.", expectedURL.RequestURI(), gotURI) + } + expectedContentType := "application/json" + if contentType := req.Header.Get("Content-Type"); contentType != expectedContentType { + t.Errorf("UpdateSecret: Wrong content-type in request. Want %q. Got %q.", expectedContentType, contentType) + } + var out UpdateSecretOptions + if err := json.NewDecoder(req.Body).Decode(&out); err != nil { + t.Fatal(err) + } + var updateAuth AuthConfiguration + + auth, err := base64.URLEncoding.DecodeString(req.Header.Get("X-Registry-Auth")) + if err != nil { + t.Errorf("UpdateSecret: caught error decoding auth. %#v", err.Error()) + } + + err = json.Unmarshal(auth, &updateAuth) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(updateAuth, update.Auth) { + t.Errorf("UpdateSecret: wrong auth configuration. Want %#v. Got %#v", update.Auth, updateAuth) + } +} + +func TestUpdateSecretNotFound(t *testing.T) { + t.Parallel() + client := newTestClient(&FakeRoundTripper{message: "no such Secret", status: http.StatusNotFound}) + update := UpdateSecretOptions{} + err := client.UpdateSecret("notfound", update) + expected := &NoSuchSecret{ID: "notfound"} + if !reflect.DeepEqual(err, expected) { + t.Errorf("UpdateSecret: Wrong error returned. Want %#v. Got %#v.", expected, err) + } +} + +func TestInspectSecretNotFound(t *testing.T) { + t.Parallel() + client := newTestClient(&FakeRoundTripper{message: "no such secret", status: http.StatusNotFound}) + secret, err := client.InspectSecret("notfound") + if secret != nil { + t.Errorf("InspectSecret: Expected Secret, got %#v", secret) + } + expected := &NoSuchSecret{ID: "notfound"} + if !reflect.DeepEqual(err, expected) { + t.Errorf("InspectSecret: Wrong error returned. Want %#v. Got %#v.", expected, err) + } +} + +func TestInspectSecret(t *testing.T) { + t.Parallel() + jsonSecret := `{ + "ID": "ak7w3gjqoa3kuz8xcpnyy0pvl", + "Version": { + "Index": 11 + }, + "CreatedAt": "2016-11-05T01:20:17.327670065Z", + "UpdatedAt": "2016-11-05T01:20:17.327670065Z", + "Spec": { + "Name": "app-dev.crt", + "Labels": { + "foo": "bar" + }, + "Driver": { + "Name": "secret-bucket", + "Options": { + "OptionA": "value for driver option A", + "OptionB": "value for driver option B" + } + } + } +}` + var expected swarm.Secret + err := json.Unmarshal([]byte(jsonSecret), &expected) + if err != nil { + t.Fatal(err) + } + fakeRT := &FakeRoundTripper{message: jsonSecret, status: http.StatusOK} + client := newTestClient(fakeRT) + id := "ak7w3gjqoa3kuz8xcpnyy0pvl" + secret, err := client.InspectSecret(id) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(*secret, expected) { + t.Errorf("InspectSecret(%q): Expected %#v. Got %#v.", id, expected, secret) + } + expectedURL, _ := url.Parse(client.getURL("/secrets/ak7w3gjqoa3kuz8xcpnyy0pvl")) + if gotPath := fakeRT.requests[0].URL.Path; gotPath != expectedURL.Path { + t.Errorf("InspectSecret(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) + } +} + +func TestListSecrets(t *testing.T) { + t.Parallel() + jsonSecrets := `[ + { + "ID": "blt1owaxmitz71s9v5zh81zun", + "Version": { + "Index": 85 + }, + "CreatedAt": "2017-07-20T13:55:28.678958722Z", + "UpdatedAt": "2017-07-20T13:55:28.678958722Z", + "Spec": { + "Name": "mysql-passwd", + "Labels": { + "some.label": "some.value" + }, + "Driver": { + "Name": "secret-bucket", + "Options": { + "OptionA": "value for driver option A", + "OptionB": "value for driver option B" + } + } + } + }, + { + "ID": "ktnbjxoalbkvbvedmg1urrz8h", + "Version": { + "Index": 11 + }, + "CreatedAt": "2016-11-05T01:20:17.327670065Z", + "UpdatedAt": "2016-11-05T01:20:17.327670065Z", + "Spec": { + "Name": "app-dev.crt", + "Labels": { + "foo": "bar" + } + } + } +]` + var expected []swarm.Secret + err := json.Unmarshal([]byte(jsonSecrets), &expected) + if err != nil { + t.Fatal(err) + } + client := newTestClient(&FakeRoundTripper{message: jsonSecrets, status: http.StatusOK}) + secrets, err := client.ListSecrets(ListSecretsOptions{}) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(secrets, expected) { + t.Errorf("ListSecrets: Expected %#v. Got %#v.", expected, secrets) + } +} diff --git a/vendor/github.com/fsouza/go-dockerclient/service.go b/vendor/github.com/fsouza/go-dockerclient/swarm_service.go similarity index 94% rename from vendor/github.com/fsouza/go-dockerclient/service.go rename to vendor/github.com/fsouza/go-dockerclient/swarm_service.go index 33af547c6..ca7e23725 100644 --- a/vendor/github.com/fsouza/go-dockerclient/service.go +++ b/vendor/github.com/fsouza/go-dockerclient/swarm_service.go @@ -5,15 +5,13 @@ package docker import ( + "context" "encoding/json" "io" "net/http" - "net/url" - "strconv" "time" "github.com/docker/docker/api/types/swarm" - "golang.org/x/net/context" ) // NoSuchService is the error returned when a given service does not exist. @@ -93,10 +91,11 @@ func (c *Client) RemoveService(opts RemoveServiceOptions) error { // // See https://goo.gl/wu3MmS for more details. type UpdateServiceOptions struct { - Auth AuthConfiguration `qs:"-"` - swarm.ServiceSpec - Context context.Context - Version uint64 + Auth AuthConfiguration `qs:"-"` + swarm.ServiceSpec `qs:"-"` + Context context.Context + Version uint64 + Rollback string } // UpdateService updates the service at ID with the options @@ -107,9 +106,7 @@ func (c *Client) UpdateService(id string, opts UpdateServiceOptions) error { if err != nil { return err } - params := make(url.Values) - params.Set("version", strconv.FormatUint(opts.Version, 10)) - resp, err := c.do("POST", "/services/"+id+"/update?"+params.Encode(), doOptions{ + resp, err := c.do("POST", "/services/"+id+"/update?"+queryString(opts), doOptions{ headers: headers, data: opts.ServiceSpec, forceJSON: true, diff --git a/vendor/github.com/fsouza/go-dockerclient/service_test.go b/vendor/github.com/fsouza/go-dockerclient/swarm_service_test.go similarity index 94% rename from vendor/github.com/fsouza/go-dockerclient/service_test.go rename to vendor/github.com/fsouza/go-dockerclient/swarm_service_test.go index 6abb28df5..f5eca0d86 100644 --- a/vendor/github.com/fsouza/go-dockerclient/service_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/swarm_service_test.go @@ -181,6 +181,29 @@ func TestUpdateService(t *testing.T) { } } +func TestUpdateServiceRollback(t *testing.T) { + t.Parallel() + fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} + client := newTestClient(fakeRT) + id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" + update := UpdateServiceOptions{Version: 23, Rollback: "previous"} + err := client.UpdateService(id, update) + if err != nil { + t.Fatal(err) + } + req := fakeRT.requests[0] + if req.Method != "POST" { + t.Errorf("UpdateService: wrong HTTP method. Want %q. Got %q.", "POST", req.Method) + } + expectedURL, _ := url.Parse(client.getURL("/services/" + id + "/update?version=23&rollback=previous")) + if req.URL.Path != expectedURL.Path { + t.Errorf("UpdateService: Wrong path in request. Want %q. Got %q.", expectedURL.Path, req.URL.Path) + } + if !reflect.DeepEqual(req.URL.Query(), expectedURL.Query()) { + t.Errorf("UpdateService: Wrong querystring in request. Want %v. Got %v.", expectedURL.Query(), req.URL.Query()) + } +} + func TestUpdateServiceWithAuthentication(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} diff --git a/vendor/github.com/fsouza/go-dockerclient/task.go b/vendor/github.com/fsouza/go-dockerclient/swarm_task.go similarity index 98% rename from vendor/github.com/fsouza/go-dockerclient/task.go rename to vendor/github.com/fsouza/go-dockerclient/swarm_task.go index b1dad4b23..3b1161ab9 100644 --- a/vendor/github.com/fsouza/go-dockerclient/task.go +++ b/vendor/github.com/fsouza/go-dockerclient/swarm_task.go @@ -5,11 +5,11 @@ package docker import ( + "context" "encoding/json" "net/http" "github.com/docker/docker/api/types/swarm" - "golang.org/x/net/context" ) // NoSuchTask is the error returned when a given task does not exist. diff --git a/vendor/github.com/fsouza/go-dockerclient/task_test.go b/vendor/github.com/fsouza/go-dockerclient/swarm_task_test.go similarity index 100% rename from vendor/github.com/fsouza/go-dockerclient/task_test.go rename to vendor/github.com/fsouza/go-dockerclient/swarm_task_test.go diff --git a/vendor/github.com/fsouza/go-dockerclient/tar.go b/vendor/github.com/fsouza/go-dockerclient/tar.go index be4dfa573..611da8c9e 100644 --- a/vendor/github.com/fsouza/go-dockerclient/tar.go +++ b/vendor/github.com/fsouza/go-dockerclient/tar.go @@ -13,11 +13,16 @@ import ( "path/filepath" "strings" - "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/fileutils" + "github.com/fsouza/go-dockerclient/internal/archive" ) func createTarStream(srcPath, dockerfilePath string) (io.ReadCloser, error) { + srcPath, err := filepath.Abs(srcPath) + if err != nil { + return nil, err + } + excludes, err := parseDockerignore(srcPath) if err != nil { return nil, err diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/server.go b/vendor/github.com/fsouza/go-dockerclient/testing/server.go index 81e520084..e34f6417e 100644 --- a/vendor/github.com/fsouza/go-dockerclient/testing/server.go +++ b/vendor/github.com/fsouza/go-dockerclient/testing/server.go @@ -42,12 +42,13 @@ var nameRegexp = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_.-]+$`) // // For more details on the remote API, check http://goo.gl/G3plxW. type DockerServer struct { - containers []*docker.Container + containers map[string]*docker.Container + contNameToID map[string]string uploadedFiles map[string]string execs []*docker.ExecInspect execMut sync.RWMutex cMut sync.RWMutex - images []docker.Image + images map[string]docker.Image iMut sync.RWMutex imgIDs map[string]string networks []*docker.Network @@ -80,18 +81,25 @@ type volumeCounter struct { count int } -func buildDockerServer(listener net.Listener, containerChan chan<- *docker.Container, hook func(*http.Request)) *DockerServer { - server := DockerServer{ - listener: listener, +func baseDockerServer() DockerServer { + return DockerServer{ + containers: make(map[string]*docker.Container), + contNameToID: make(map[string]string), imgIDs: make(map[string]string), - hook: hook, + images: make(map[string]docker.Image), failures: make(map[string]string), execCallbacks: make(map[string]func()), statsCallbacks: make(map[string]func(string) docker.Stats), customHandlers: make(map[string]http.Handler), uploadedFiles: make(map[string]string), - cChan: containerChan, } +} + +func buildDockerServer(listener net.Listener, containerChan chan<- *docker.Container, hook func(*http.Request)) *DockerServer { + server := baseDockerServer() + server.listener = listener + server.hook = hook + server.cChan = containerChan server.buildMuxer() return &server } @@ -196,6 +204,7 @@ func (s *DockerServer) buildMuxer() { s.mux.Path("/networks/{id:.*}").Methods("GET").HandlerFunc(s.handlerWrapper(s.networkInfo)) s.mux.Path("/networks/{id:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeNetwork)) s.mux.Path("/networks/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.createNetwork)) + s.mux.Path("/networks/{id:.*}/connect").Methods("POST").HandlerFunc(s.handlerWrapper(s.networksConnect)) s.mux.Path("/volumes").Methods("GET").HandlerFunc(s.handlerWrapper(s.listVolumes)) s.mux.Path("/volumes/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.createVolume)) s.mux.Path("/volumes/{name:.*}").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectVolume)) @@ -299,11 +308,9 @@ func (s *DockerServer) CustomHandler(path string, handler http.Handler) { func (s *DockerServer) MutateContainer(id string, state docker.State) error { s.cMut.Lock() defer s.cMut.Unlock() - for _, container := range s.containers { - if container.ID == id { - container.State = state - return nil - } + if container, ok := s.containers[id]; ok { + container.State = state + return nil } return errors.New("container not found") } @@ -381,14 +388,36 @@ func (s *DockerServer) handlerWrapper(f http.HandlerFunc) http.HandlerFunc { func (s *DockerServer) listContainers(w http.ResponseWriter, r *http.Request) { all := r.URL.Query().Get("all") + filtersRaw := r.FormValue("filters") + filters := make(map[string][]string) + json.Unmarshal([]byte(filtersRaw), &filters) + labelFilters := make(map[string]*string) + for _, f := range filters["label"] { + parts := strings.Split(f, "=") + if len(parts) == 2 { + labelFilters[parts[0]] = &parts[1] + continue + } + labelFilters[parts[0]] = nil + } s.cMut.RLock() result := make([]docker.APIContainers, 0, len(s.containers)) +loop: for _, container := range s.containers { if all == "1" || container.State.Running { var ports []docker.APIPort if container.NetworkSettings != nil { ports = container.NetworkSettings.PortMappingAPI() } + for l, fv := range labelFilters { + lv, ok := container.Config.Labels[l] + if !ok { + continue loop + } + if fv != nil && lv != *fv { + continue loop + } + } result = append(result, docker.APIContainers{ ID: container.ID, Image: container.Image, @@ -410,7 +439,8 @@ func (s *DockerServer) listContainers(w http.ResponseWriter, r *http.Request) { func (s *DockerServer) listImages(w http.ResponseWriter, r *http.Request) { s.cMut.RLock() result := make([]docker.APIImages, len(s.images)) - for i, image := range s.images { + i := 0 + for _, image := range s.images { result[i] = docker.APIImages{ ID: image.ID, Created: image.Created.Unix(), @@ -420,6 +450,7 @@ func (s *DockerServer) listImages(w http.ResponseWriter, r *http.Request) { result[i].RepoTags = append(result[i].RepoTags, tag) } } + i++ } s.cMut.RUnlock() w.Header().Set("Content-Type", "application/json") @@ -434,19 +465,10 @@ func (s *DockerServer) findImage(id string) (string, error) { if ok { return image, nil } - image, _, err := s.findImageByID(id) - return image, err -} - -func (s *DockerServer) findImageByID(id string) (string, int, error) { - s.iMut.RLock() - defer s.iMut.RUnlock() - for i, image := range s.images { - if image.ID == id { - return image.ID, i, nil - } + if _, ok := s.images[id]; ok { + return id, nil } - return "", -1, errors.New("No such image") + return "", errors.New("No such image") } func (s *DockerServer) createContainer(w http.ResponseWriter, r *http.Request) { @@ -517,15 +539,14 @@ func (s *DockerServer) createContainer(w http.ResponseWriter, r *http.Request) { s.uploadedFiles[container.ID] = val } if container.Name != "" { - for _, c := range s.containers { - if c.Name == container.Name { - defer s.cMut.Unlock() - http.Error(w, "there's already a container with this name", http.StatusConflict) - return - } + _, err = s.findContainerWithLock(container.Name, false) + if err == nil { + defer s.cMut.Unlock() + http.Error(w, "there's already a container with this name", http.StatusConflict) + return } } - s.containers = append(s.containers, &container) + s.addContainer(&container) s.cMut.Unlock() w.WriteHeader(http.StatusCreated) s.notify(&container) @@ -533,6 +554,13 @@ func (s *DockerServer) createContainer(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(container) } +func (s *DockerServer) addContainer(container *docker.Container) { + s.containers[container.ID] = container + if container.Name != "" { + s.contNameToID[container.Name] = container.ID + } +} + func (s *DockerServer) generateID() string { var buf [16]byte rand.Read(buf[:]) @@ -541,36 +569,36 @@ func (s *DockerServer) generateID() string { func (s *DockerServer) renameContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] - container, index, err := s.findContainer(id) + s.cMut.Lock() + defer s.cMut.Unlock() + container, err := s.findContainerWithLock(id, false) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } - copy := *container - copy.Name = r.URL.Query().Get("name") - s.cMut.Lock() - defer s.cMut.Unlock() - if s.containers[index].ID == copy.ID { - s.containers[index] = © - } + delete(s.contNameToID, container.Name) + container.Name = r.URL.Query().Get("name") + s.contNameToID[container.Name] = container.ID w.WriteHeader(http.StatusNoContent) } func (s *DockerServer) inspectContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] - container, _, err := s.findContainer(id) + container, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) + s.cMut.RLock() + defer s.cMut.RUnlock() json.NewEncoder(w).Encode(container) } func (s *DockerServer) statsContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] - _, _, err := s.findContainer(id) + _, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return @@ -594,7 +622,7 @@ func (s *DockerServer) statsContainer(w http.ResponseWriter, r *http.Request) { func (s *DockerServer) uploadToContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] - _, _, err := s.findContainer(id) + _, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return @@ -614,7 +642,7 @@ func (s *DockerServer) uploadToContainer(w http.ResponseWriter, r *http.Request) func (s *DockerServer) downloadFromContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] - _, _, err := s.findContainer(id) + _, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return @@ -634,11 +662,13 @@ func (s *DockerServer) downloadFromContainer(w http.ResponseWriter, r *http.Requ func (s *DockerServer) topContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] - container, _, err := s.findContainer(id) + container, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } + s.cMut.RLock() + defer s.cMut.RUnlock() if !container.State.Running { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, "Container %s is not running", id) @@ -657,7 +687,7 @@ func (s *DockerServer) topContainer(w http.ResponseWriter, r *http.Request) { func (s *DockerServer) startContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] - container, _, err := s.findContainer(id) + container, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return @@ -708,7 +738,7 @@ func (s *DockerServer) startContainer(w http.ResponseWriter, r *http.Request) { func (s *DockerServer) stopContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] - container, _, err := s.findContainer(id) + container, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return @@ -726,7 +756,7 @@ func (s *DockerServer) stopContainer(w http.ResponseWriter, r *http.Request) { func (s *DockerServer) pauseContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] - container, _, err := s.findContainer(id) + container, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return @@ -743,7 +773,7 @@ func (s *DockerServer) pauseContainer(w http.ResponseWriter, r *http.Request) { func (s *DockerServer) unpauseContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] - container, _, err := s.findContainer(id) + container, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return @@ -760,7 +790,7 @@ func (s *DockerServer) unpauseContainer(w http.ResponseWriter, r *http.Request) func (s *DockerServer) attachContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] - container, _, err := s.findContainer(id) + container, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return @@ -786,11 +816,13 @@ func (s *DockerServer) attachContainer(w http.ResponseWriter, r *http.Request) { }() } outStream := stdcopy.NewStdWriter(conn, stdcopy.Stdout) + s.cMut.RLock() if container.State.Running { fmt.Fprintf(outStream, "Container is running\n") } else { fmt.Fprintf(outStream, "Container is not running\n") } + s.cMut.RUnlock() fmt.Fprintln(outStream, "What happened?") fmt.Fprintln(outStream, "Something happened") wg.Wait() @@ -810,21 +842,23 @@ func (s *DockerServer) attachContainer(w http.ResponseWriter, r *http.Request) { func (s *DockerServer) waitContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] - container, _, err := s.findContainer(id) + container, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } + var exitCode int for { time.Sleep(1e6) s.cMut.RLock() if !container.State.Running { + exitCode = container.State.ExitCode s.cMut.RUnlock() break } s.cMut.RUnlock() } - result := map[string]int{"StatusCode": container.State.ExitCode} + result := map[string]int{"StatusCode": exitCode} json.NewEncoder(w).Encode(result) } @@ -833,7 +867,7 @@ func (s *DockerServer) removeContainer(w http.ResponseWriter, r *http.Request) { force := r.URL.Query().Get("force") s.cMut.Lock() defer s.cMut.Unlock() - container, index, err := s.findContainerWithLock(id, false) + container, err := s.findContainerWithLock(id, false) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return @@ -844,13 +878,13 @@ func (s *DockerServer) removeContainer(w http.ResponseWriter, r *http.Request) { return } w.WriteHeader(http.StatusNoContent) - s.containers[index] = s.containers[len(s.containers)-1] - s.containers = s.containers[:len(s.containers)-1] + delete(s.containers, container.ID) + delete(s.contNameToID, container.Name) } func (s *DockerServer) commitContainer(w http.ResponseWriter, r *http.Request) { id := r.URL.Query().Get("container") - container, _, err := s.findContainer(id) + container, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return @@ -876,7 +910,7 @@ func (s *DockerServer) commitContainer(w http.ResponseWriter, r *http.Request) { repository := r.URL.Query().Get("repo") tag := r.URL.Query().Get("tag") s.iMut.Lock() - s.images = append(s.images, image) + s.images[image.ID] = image if repository != "" { if tag != "" { repository += ":" + tag @@ -892,37 +926,40 @@ func (s *DockerServer) commitContainer(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, `{"ID":%q}`, image.ID) } -func (s *DockerServer) findContainer(idOrName string) (*docker.Container, int, error) { +func (s *DockerServer) findContainer(idOrName string) (*docker.Container, error) { return s.findContainerWithLock(idOrName, true) } -func (s *DockerServer) findContainerWithLock(idOrName string, shouldLock bool) (*docker.Container, int, error) { +func (s *DockerServer) findContainerWithLock(idOrName string, shouldLock bool) (*docker.Container, error) { if shouldLock { s.cMut.RLock() defer s.cMut.RUnlock() } - for i, container := range s.containers { - if container.ID == idOrName || container.Name == idOrName { - return container, i, nil - } + if contID, ok := s.contNameToID[idOrName]; ok { + idOrName = contID } - return nil, -1, errors.New("No such container") + if cont, ok := s.containers[idOrName]; ok { + return cont, nil + } + return nil, errors.New("No such container") } func (s *DockerServer) logContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] - container, _, err := s.findContainer(id) + container, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } w.Header().Set("Content-Type", "application/vnd.docker.raw-stream") w.WriteHeader(http.StatusOK) + s.cMut.RLock() if container.State.Running { fmt.Fprintf(w, "Container is running\n") } else { fmt.Fprintf(w, "Container is not running\n") } + s.cMut.RUnlock() fmt.Fprintln(w, "What happened?") fmt.Fprintln(w, "Something happened") if r.URL.Query().Get("follow") == "1" { @@ -969,7 +1006,7 @@ func (s *DockerServer) buildImage(w http.ResponseWriter, r *http.Request) { repository = t } s.iMut.Lock() - s.images = append(s.images, image) + s.images[image.ID] = image s.imgIDs[repository] = image.ID s.iMut.Unlock() w.Write([]byte(fmt.Sprintf("Successfully built %s", image.ID))) @@ -978,12 +1015,6 @@ func (s *DockerServer) buildImage(w http.ResponseWriter, r *http.Request) { func (s *DockerServer) pullImage(w http.ResponseWriter, r *http.Request) { fromImageName := r.URL.Query().Get("fromImage") tag := r.URL.Query().Get("tag") - image := docker.Image{ - ID: s.generateID(), - Config: &docker.Config{}, - } - s.iMut.Lock() - s.images = append(s.images, image) if fromImageName != "" { if tag != "" { separator := ":" @@ -992,7 +1023,17 @@ func (s *DockerServer) pullImage(w http.ResponseWriter, r *http.Request) { } fromImageName = fmt.Sprintf("%s%s%s", fromImageName, separator, tag) } - s.imgIDs[fromImageName] = image.ID + } + image := docker.Image{ + ID: s.generateID(), + Config: &docker.Config{}, + } + s.iMut.Lock() + if _, exists := s.imgIDs[fromImageName]; fromImageName == "" || !exists { + s.images[image.ID] = image + if fromImageName != "" { + s.imgIDs[fromImageName] = image.ID + } } s.iMut.Unlock() } @@ -1016,13 +1057,11 @@ func (s *DockerServer) pushImage(w http.ResponseWriter, r *http.Request) { func (s *DockerServer) tagImage(w http.ResponseWriter, r *http.Request) { name := mux.Vars(r)["name"] - s.iMut.RLock() - if _, ok := s.imgIDs[name]; !ok { - s.iMut.RUnlock() + id, err := s.findImage(name) + if err != nil { http.Error(w, "No such image", http.StatusNotFound) return } - s.iMut.RUnlock() s.iMut.Lock() defer s.iMut.Unlock() newRepo := r.URL.Query().Get("repo") @@ -1030,13 +1069,14 @@ func (s *DockerServer) tagImage(w http.ResponseWriter, r *http.Request) { if newTag != "" { newRepo += ":" + newTag } - s.imgIDs[newRepo] = s.imgIDs[name] + s.imgIDs[newRepo] = id w.WriteHeader(http.StatusCreated) } func (s *DockerServer) removeImage(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] - s.iMut.RLock() + s.iMut.Lock() + defer s.iMut.Unlock() var tag string if img, ok := s.imgIDs[id]; ok { id, tag = img, id @@ -1047,21 +1087,28 @@ func (s *DockerServer) removeImage(w http.ResponseWriter, r *http.Request) { tags = append(tags, tag) } } - s.iMut.RUnlock() - _, index, err := s.findImageByID(id) - if err != nil { - http.Error(w, err.Error(), http.StatusNotFound) + _, ok := s.images[id] + if !ok { + http.Error(w, "No such image", http.StatusNotFound) + return + } + if tag == "" && len(tags) > 1 { + http.Error(w, "image is referenced in multiple repositories", http.StatusConflict) return } w.WriteHeader(http.StatusNoContent) - s.iMut.Lock() - defer s.iMut.Unlock() - if len(tags) < 2 { - s.images[index] = s.images[len(s.images)-1] - s.images = s.images[:len(s.images)-1] - } - if tag != "" { + if tag == "" { + // delete called with image ID + for _, t := range tags { + delete(s.imgIDs, t) + } + delete(s.images, id) + } else { + // delete called with image repository name delete(s.imgIDs, tag) + if len(tags) == 1 { + delete(s.images, id) + } } } @@ -1070,16 +1117,16 @@ func (s *DockerServer) inspectImage(w http.ResponseWriter, r *http.Request) { s.iMut.RLock() defer s.iMut.RUnlock() if id, ok := s.imgIDs[name]; ok { - for _, img := range s.images { - if img.ID == id { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(img) - return - } - } + name = id } - http.Error(w, "not found", http.StatusNotFound) + img, ok := s.images[name] + if !ok { + http.Error(w, "not found", http.StatusNotFound) + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(img) } func (s *DockerServer) listEvents(w http.ResponseWriter, r *http.Request) { @@ -1136,14 +1183,16 @@ func (s *DockerServer) getImage(w http.ResponseWriter, r *http.Request) { func (s *DockerServer) createExecContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] - container, _, err := s.findContainer(id) + container, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } execID := s.generateID() + s.cMut.Lock() container.ExecIDs = append(container.ExecIDs, execID) + s.cMut.Unlock() exec := docker.ExecInspect{ ID: execID, @@ -1293,9 +1342,10 @@ func (s *DockerServer) createNetwork(w http.ResponseWriter, r *http.Request) { generatedID := s.generateID() network := docker.Network{ - Name: config.Name, - ID: generatedID, - Driver: config.Driver, + Name: config.Name, + ID: generatedID, + Driver: config.Driver, + Containers: map[string]docker.Endpoint{}, } s.netMut.Lock() s.networks = append(s.networks, &network) @@ -1319,6 +1369,35 @@ func (s *DockerServer) removeNetwork(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) } +func (s *DockerServer) networksConnect(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + var config *docker.NetworkConnectionOptions + defer r.Body.Close() + err := json.NewDecoder(r.Body).Decode(&config) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + network, index, _ := s.findNetwork(id) + container, _ := s.findContainer(config.Container) + if network == nil || container == nil { + http.Error(w, "network or container not found", http.StatusNotFound) + return + } + + if _, found := network.Containers[container.ID]; found == true { + http.Error(w, "endpoint already exists in network", http.StatusBadRequest) + return + } + + s.netMut.Lock() + s.networks[index].Containers[config.Container] = docker.Endpoint{} + s.netMut.Unlock() + + w.WriteHeader(http.StatusOK) +} + func (s *DockerServer) listVolumes(w http.ResponseWriter, r *http.Request) { s.volMut.RLock() result := make([]docker.Volume, 0, len(s.volStore)) diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/server_test.go b/vendor/github.com/fsouza/go-dockerclient/testing/server_test.go index 928510ad5..2e9fee3fb 100644 --- a/vendor/github.com/fsouza/go-dockerclient/testing/server_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/testing/server_test.go @@ -17,6 +17,7 @@ import ( "net/http/httptest" "os" "reflect" + "sort" "strings" "sync" "testing" @@ -87,7 +88,7 @@ func TestServerStop(t *testing.T) { func TestServerStopNoListener(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.Stop() } @@ -106,7 +107,7 @@ func TestServerURL(t *testing.T) { func TestServerURLNoListener(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() url := server.URL() if url != "" { t.Errorf("DockerServer.URL(): Expected empty URL on handler mode, got %q.", url) @@ -184,8 +185,8 @@ func TestCustomHandlerRegexp(t *testing.T) { func TestListContainers(t *testing.T) { t.Parallel() - server := DockerServer{} - addContainers(&server, 2) + server := baseDockerServer() + containers := addContainers(&server, 2) server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest("GET", "/containers/json?all=1", nil) @@ -194,7 +195,7 @@ func TestListContainers(t *testing.T) { t.Errorf("ListContainers: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } expected := make([]docker.APIContainers, 2) - for i, container := range server.containers { + for i, container := range containers { expected[i] = docker.APIContainers{ ID: container.ID, Image: container.Image, @@ -206,11 +207,17 @@ func TestListContainers(t *testing.T) { State: container.State.StateString(), } } + sort.Slice(expected, func(i, j int) bool { + return expected[i].ID < expected[j].ID + }) var got []docker.APIContainers err := json.NewDecoder(recorder.Body).Decode(&got) if err != nil { t.Fatal(err) } + sort.Slice(got, func(i, j int) bool { + return got[i].ID < got[j].ID + }) if !reflect.DeepEqual(got, expected) { t.Errorf("ListContainers. Want %#v. Got %#v.", expected, got) } @@ -218,7 +225,7 @@ func TestListContainers(t *testing.T) { func TestListRunningContainers(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() addContainers(&server, 2) server.buildMuxer() recorder := httptest.NewRecorder() @@ -237,9 +244,54 @@ func TestListRunningContainers(t *testing.T) { } } +func TestListContainersFilterLabels(t *testing.T) { + t.Parallel() + server := baseDockerServer() + addContainers(&server, 3) + server.buildMuxer() + recorder := httptest.NewRecorder() + request, _ := http.NewRequest("GET", `/containers/json?all=1&filters={"label": ["key=val-1"]}`, nil) + server.ServeHTTP(recorder, request) + if recorder.Code != http.StatusOK { + t.Errorf("TestListContainersFilterLabels: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) + } + var got []docker.APIContainers + err := json.NewDecoder(recorder.Body).Decode(&got) + if err != nil { + t.Fatal(err) + } + if len(got) != 1 { + t.Errorf("TestListContainersFilterLabels: Want 1. Got %d.", len(got)) + } + request, _ = http.NewRequest("GET", `/containers/json?all=1&filters={"label": ["key="]}`, nil) + server.ServeHTTP(recorder, request) + if recorder.Code != http.StatusOK { + t.Errorf("TestListContainersFilterLabels: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) + } + err = json.NewDecoder(recorder.Body).Decode(&got) + if err != nil { + t.Fatal(err) + } + if len(got) != 0 { + t.Errorf("TestListContainersFilterLabels: Want 0. Got %d.", len(got)) + } + request, _ = http.NewRequest("GET", `/containers/json?all=1&filters={"label": ["key"]}`, nil) + server.ServeHTTP(recorder, request) + if recorder.Code != http.StatusOK { + t.Errorf("TestListContainersFilterLabels: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) + } + err = json.NewDecoder(recorder.Body).Decode(&got) + if err != nil { + t.Fatal(err) + } + if len(got) != 3 { + t.Errorf("TestListContainersFilterLabels: Want 3. Got %d.", len(got)) + } +} + func TestCreateContainer(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.imgIDs = map[string]string{"base": "a1234"} server.uploadedFiles = map[string]string{"a1234": "/abcd"} server.buildMuxer() @@ -256,7 +308,7 @@ func TestCreateContainer(t *testing.T) { if err != nil { t.Fatal(err) } - stored := server.containers[0] + stored := getContainer(&server) if returned.ID != stored.ID { t.Errorf("CreateContainer: ID mismatch. Stored: %q. Returned: %q.", stored.ID, returned.ID) } @@ -283,10 +335,17 @@ func TestCreateContainer(t *testing.T) { } } +func getContainer(server *DockerServer) *docker.Container { + var cont *docker.Container + for _, cont = range server.containers { + } + return cont +} + func TestCreateContainerWithNotifyChannel(t *testing.T) { t.Parallel() ch := make(chan *docker.Container, 1) - server := DockerServer{} + server := baseDockerServer() server.imgIDs = map[string]string{"base": "a1234"} server.cChan = ch server.buildMuxer() @@ -298,14 +357,14 @@ func TestCreateContainerWithNotifyChannel(t *testing.T) { if recorder.Code != http.StatusCreated { t.Errorf("CreateContainer: wrong status. Want %d. Got %d.", http.StatusCreated, recorder.Code) } - if notified := <-ch; notified != server.containers[0] { - t.Errorf("CreateContainer: did not notify the proper container. Want %q. Got %q.", server.containers[0].ID, notified.ID) + if notified := <-ch; notified != getContainer(&server) { + t.Errorf("CreateContainer: did not notify the proper container. Want %q. Got %q.", getContainer(&server).ID, notified.ID) } } func TestCreateContainerInvalidBody(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest("POST", "/containers/create", strings.NewReader("whaaaaaat---")) @@ -317,11 +376,12 @@ func TestCreateContainerInvalidBody(t *testing.T) { func TestCreateContainerDuplicateName(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() server.imgIDs = map[string]string{"base": "a1234"} - addContainers(&server, 1) - server.containers[0].Name = "mycontainer" + containers := addContainers(&server, 1) + containers[0].Name = "mycontainer" + server.contNameToID[containers[0].Name] = containers[0].ID recorder := httptest.NewRecorder() body := `{"Hostname":"", "User":"ubuntu", "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":"","HostConfig":{"Binds":["/var/run/docker.sock:/var/run/docker.sock:rw"]}}` @@ -334,11 +394,11 @@ func TestCreateContainerDuplicateName(t *testing.T) { func TestCreateMultipleContainersEmptyName(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() server.imgIDs = map[string]string{"base": "a1234"} addContainers(&server, 1) - server.containers[0].Name = "" + getContainer(&server).Name = "" recorder := httptest.NewRecorder() body := `{"Hostname":"", "User":"ubuntu", "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":"","HostConfig":{"Binds":["/var/run/docker.sock:/var/run/docker.sock:rw"]}}` @@ -352,7 +412,10 @@ func TestCreateMultipleContainersEmptyName(t *testing.T) { if err != nil { t.Fatal(err) } - stored := server.containers[1] + stored, err := server.findContainer(returned.ID) + if err != nil { + t.Fatal(err) + } if returned.ID != stored.ID { t.Errorf("CreateContainer: ID mismatch. Stored: %q. Returned: %q.", stored.ID, returned.ID) } @@ -370,7 +433,7 @@ func TestCreateMultipleContainersEmptyName(t *testing.T) { func TestCreateContainerInvalidName(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() body := `{"Hostname":"", "User":"", "Memory":0, "MemorySwap":0, "AttachStdin":false, "AttachStdout":true, "AttachStderr":true, @@ -389,7 +452,7 @@ func TestCreateContainerInvalidName(t *testing.T) { func TestCreateContainerImageNotFound(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() body := `{"Hostname":"", "User":"", "Memory":0, "MemorySwap":0, "AttachStdin":false, "AttachStdout":true, "AttachStderr":true, @@ -404,18 +467,18 @@ func TestCreateContainerImageNotFound(t *testing.T) { func TestRenameContainer(t *testing.T) { t.Parallel() - server := DockerServer{} - addContainers(&server, 2) + server := baseDockerServer() + containers := addContainers(&server, 2) server.buildMuxer() recorder := httptest.NewRecorder() - newName := server.containers[0].Name + "abc" - path := fmt.Sprintf("/containers/%s/rename?name=%s", server.containers[0].ID, newName) + newName := containers[0].Name + "abc" + path := fmt.Sprintf("/containers/%s/rename?name=%s", containers[0].ID, newName) request, _ := http.NewRequest("POST", path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNoContent { t.Errorf("RenameContainer: wrong status. Want %d. Got %d.", http.StatusNoContent, recorder.Code) } - container := server.containers[0] + container := containers[0] if container.Name != newName { t.Errorf("RenameContainer: did not rename the container. Want %q. Got %q.", newName, container.Name) } @@ -423,7 +486,7 @@ func TestRenameContainer(t *testing.T) { func TestRenameContainerNotFound(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest("POST", "/containers/blabla/rename?name=something", nil) @@ -435,24 +498,28 @@ func TestRenameContainerNotFound(t *testing.T) { func TestCommitContainer(t *testing.T) { t.Parallel() - server := DockerServer{} - addContainers(&server, 2) - server.uploadedFiles = map[string]string{server.containers[0].ID: "/abcd"} + server := baseDockerServer() + containers := addContainers(&server, 2) + server.uploadedFiles = map[string]string{containers[0].ID: "/abcd"} server.buildMuxer() recorder := httptest.NewRecorder() - request, _ := http.NewRequest("POST", "/commit?container="+server.containers[0].ID, nil) + request, _ := http.NewRequest("POST", "/commit?container="+containers[0].ID, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("CommitContainer: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } - expected := fmt.Sprintf(`{"ID":"%s"}`, server.images[0].ID) + if len(server.images) != 1 { + t.Errorf("CommitContainer: wrong images len in server. Want 1. Got %q.", len(server.images)) + } + imgID := fmt.Sprintf("img-%s", containers[0].ID) + expected := fmt.Sprintf(`{"ID":"%s"}`, imgID) if got := recorder.Body.String(); got != expected { t.Errorf("CommitContainer: wrong response body. Want %q. Got %q.", expected, got) } - if server.images[0].Config == nil { + if server.images[imgID].Config == nil { t.Error("CommitContainer: image Config should not be nil.") } - if val, ok := server.uploadedFiles[server.images[0].ID]; !ok { + if val, ok := server.uploadedFiles[server.images[imgID].ID]; !ok { t.Error("CommitContainer: uploadedFiles should exist.") } else if val != "/abcd" { t.Errorf("CommitContainer: wrong uploadedFile. Want '/abcd', got %s.", val) @@ -461,21 +528,21 @@ func TestCommitContainer(t *testing.T) { func TestCommitContainerComplete(t *testing.T) { t.Parallel() - server := DockerServer{} - server.imgIDs = make(map[string]string) - addContainers(&server, 2) + server := baseDockerServer() + containers := addContainers(&server, 2) server.buildMuxer() recorder := httptest.NewRecorder() - queryString := "container=" + server.containers[0].ID + "&repo=tsuru/python&m=saving&author=developers" + queryString := "container=" + containers[0].ID + "&repo=tsuru/python&m=saving&author=developers" queryString += `&run={"Cmd": ["cat", "/world"],"PortSpecs":["22"]}` request, _ := http.NewRequest("POST", "/commit?"+queryString, nil) server.ServeHTTP(recorder, request) - image := server.images[0] - if image.Parent != server.containers[0].Image { - t.Errorf("CommitContainer: wrong parent image. Want %q. Got %q.", server.containers[0].Image, image.Parent) + imgID := fmt.Sprintf("img-%s", containers[0].ID) + image := server.images[imgID] + if image.Parent != containers[0].Image { + t.Errorf("CommitContainer: wrong parent image. Want %q. Got %q.", containers[0].Image, image.Parent) } - if image.Container != server.containers[0].ID { - t.Errorf("CommitContainer: wrong container. Want %q. Got %q.", server.containers[0].ID, image.Container) + if image.Container != containers[0].ID { + t.Errorf("CommitContainer: wrong container. Want %q. Got %q.", containers[0].ID, image.Container) } message := "saving" if image.Comment != message { @@ -500,20 +567,20 @@ func TestCommitContainerComplete(t *testing.T) { func TestCommitContainerWithTag(t *testing.T) { t.Parallel() - server := DockerServer{} - server.imgIDs = make(map[string]string) - addContainers(&server, 2) + server := baseDockerServer() + containers := addContainers(&server, 2) server.buildMuxer() recorder := httptest.NewRecorder() - queryString := "container=" + server.containers[0].ID + "&repo=tsuru/python&tag=v1" + queryString := "container=" + containers[0].ID + "&repo=tsuru/python&tag=v1" request, _ := http.NewRequest("POST", "/commit?"+queryString, nil) server.ServeHTTP(recorder, request) - image := server.images[0] - if image.Parent != server.containers[0].Image { - t.Errorf("CommitContainer: wrong parent image. Want %q. Got %q.", server.containers[0].Image, image.Parent) + imgID := fmt.Sprintf("img-%s", containers[0].ID) + image := server.images[imgID] + if image.Parent != containers[0].Image { + t.Errorf("CommitContainer: wrong parent image. Want %q. Got %q.", containers[0].Image, image.Parent) } - if image.Container != server.containers[0].ID { - t.Errorf("CommitContainer: wrong container. Want %q. Got %q.", server.containers[0].ID, image.Container) + if image.Container != containers[0].ID { + t.Errorf("CommitContainer: wrong container. Want %q. Got %q.", containers[0].ID, image.Container) } if id := server.imgIDs["tsuru/python:v1"]; id != image.ID { t.Errorf("CommitContainer: wrong ID saved for repository. Want %q. Got %q.", image.ID, id) @@ -522,11 +589,11 @@ func TestCommitContainerWithTag(t *testing.T) { func TestCommitContainerInvalidRun(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() addContainers(&server, 1) server.buildMuxer() recorder := httptest.NewRecorder() - request, _ := http.NewRequest("POST", "/commit?container="+server.containers[0].ID+"&run=abc---", nil) + request, _ := http.NewRequest("POST", "/commit?container="+getContainer(&server).ID+"&run=abc---", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusBadRequest { t.Errorf("CommitContainer. Wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code) @@ -535,7 +602,7 @@ func TestCommitContainerInvalidRun(t *testing.T) { func TestCommitContainerNotFound(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest("POST", "/commit?container=abc123", nil) @@ -547,17 +614,17 @@ func TestCommitContainerNotFound(t *testing.T) { func TestInspectContainer(t *testing.T) { t.Parallel() - server := DockerServer{} - addContainers(&server, 2) + server := baseDockerServer() + containers := addContainers(&server, 2) server.buildMuxer() recorder := httptest.NewRecorder() - path := fmt.Sprintf("/containers/%s/json", server.containers[0].ID) + path := fmt.Sprintf("/containers/%s/json", containers[0].ID) request, _ := http.NewRequest("GET", path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("InspectContainer: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } - expected := server.containers[0] + expected := containers[0] var got docker.Container err := json.NewDecoder(recorder.Body).Decode(&got) if err != nil { @@ -581,7 +648,7 @@ func TestInspectContainer(t *testing.T) { func TestInspectContainerNotFound(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest("GET", "/containers/abc123/json", nil) @@ -593,12 +660,12 @@ func TestInspectContainerNotFound(t *testing.T) { func TestTopContainer(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() addContainers(&server, 1) - server.containers[0].State.Running = true + getContainer(&server).State.Running = true server.buildMuxer() recorder := httptest.NewRecorder() - path := fmt.Sprintf("/containers/%s/top", server.containers[0].ID) + path := fmt.Sprintf("/containers/%s/top", getContainer(&server).ID) request, _ := http.NewRequest("GET", path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { @@ -622,7 +689,7 @@ func TestTopContainer(t *testing.T) { func TestTopContainerNotFound(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest("GET", "/containers/xyz/top", nil) @@ -634,11 +701,11 @@ func TestTopContainerNotFound(t *testing.T) { func TestTopContainerStopped(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() addContainers(&server, 1) server.buildMuxer() recorder := httptest.NewRecorder() - path := fmt.Sprintf("/containers/%s/top", server.containers[0].ID) + path := fmt.Sprintf("/containers/%s/top", getContainer(&server).ID) request, _ := http.NewRequest("GET", path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusInternalServerError { @@ -648,7 +715,7 @@ func TestTopContainerStopped(t *testing.T) { func TestStartContainer(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() addContainers(&server, 1) server.buildMuxer() memory := int64(536870912) @@ -658,49 +725,49 @@ func TestStartContainer(t *testing.T) { t.Fatal(err) } recorder := httptest.NewRecorder() - path := fmt.Sprintf("/containers/%s/start", server.containers[0].ID) + path := fmt.Sprintf("/containers/%s/start", getContainer(&server).ID) request, _ := http.NewRequest("POST", path, bytes.NewBuffer(configBytes)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("StartContainer: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } - if !server.containers[0].State.Running { + if !getContainer(&server).State.Running { t.Error("StartContainer: did not set the container to running state") } - if server.containers[0].State.StartedAt.IsZero() { + if getContainer(&server).State.StartedAt.IsZero() { t.Error("StartContainer: did not set the startedAt container state") } - if gotMemory := server.containers[0].HostConfig.Memory; gotMemory != memory { + if gotMemory := getContainer(&server).HostConfig.Memory; gotMemory != memory { t.Errorf("StartContainer: wrong HostConfig. Wants %d of memory. Got %d", memory, gotMemory) } } func TestStartContainerNoHostConfig(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() addContainers(&server, 1) server.buildMuxer() memory := int64(536870912) hostConfig := docker.HostConfig{Memory: memory} - server.containers[0].HostConfig = &hostConfig + getContainer(&server).HostConfig = &hostConfig recorder := httptest.NewRecorder() - path := fmt.Sprintf("/containers/%s/start", server.containers[0].ID) + path := fmt.Sprintf("/containers/%s/start", getContainer(&server).ID) request, _ := http.NewRequest("POST", path, strings.NewReader("")) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("StartContainer: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } - if !server.containers[0].State.Running { + if !getContainer(&server).State.Running { t.Error("StartContainer: did not set the container to running state") } - if gotMemory := server.containers[0].HostConfig.Memory; gotMemory != memory { + if gotMemory := getContainer(&server).HostConfig.Memory; gotMemory != memory { t.Errorf("StartContainer: wrong HostConfig. Wants %d of memory. Got %d", memory, gotMemory) } } func TestStartContainerChangeNetwork(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() addContainers(&server, 1) server.buildMuxer() hostConfig := docker.HostConfig{ @@ -713,16 +780,16 @@ func TestStartContainerChangeNetwork(t *testing.T) { t.Fatal(err) } recorder := httptest.NewRecorder() - path := fmt.Sprintf("/containers/%s/start", server.containers[0].ID) + path := fmt.Sprintf("/containers/%s/start", getContainer(&server).ID) request, _ := http.NewRequest("POST", path, bytes.NewBuffer(configBytes)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("StartContainer: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } - if !server.containers[0].State.Running { + if !getContainer(&server).State.Running { t.Error("StartContainer: did not set the container to running state") } - portMapping := server.containers[0].NetworkSettings.Ports["8888/tcp"] + portMapping := getContainer(&server).NetworkSettings.Ports["8888/tcp"] expected := []docker.PortBinding{{HostIP: "0.0.0.0", HostPort: "12345"}} if !reflect.DeepEqual(portMapping, expected) { t.Errorf("StartContainer: network not updated. Wants %#v ports. Got %#v", expected, portMapping) @@ -732,26 +799,25 @@ func TestStartContainerChangeNetwork(t *testing.T) { func TestStartContainerWithNotifyChannel(t *testing.T) { t.Parallel() ch := make(chan *docker.Container, 1) - server := DockerServer{} + server := baseDockerServer() server.cChan = ch - addContainers(&server, 1) - addContainers(&server, 1) + containers := addContainers(&server, 2) server.buildMuxer() recorder := httptest.NewRecorder() - path := fmt.Sprintf("/containers/%s/start", server.containers[1].ID) + path := fmt.Sprintf("/containers/%s/start", containers[1].ID) request, _ := http.NewRequest("POST", path, bytes.NewBuffer([]byte("{}"))) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("StartContainer: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } - if notified := <-ch; notified != server.containers[1] { - t.Errorf("StartContainer: did not notify the proper container. Want %q. Got %q.", server.containers[1].ID, notified.ID) + if notified := <-ch; notified != containers[1] { + t.Errorf("StartContainer: did not notify the proper container. Want %q. Got %q.", containers[1].ID, notified.ID) } } func TestStartContainerNotFound(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() path := "/containers/abc123/start" @@ -764,12 +830,12 @@ func TestStartContainerNotFound(t *testing.T) { func TestStartContainerAlreadyRunning(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() addContainers(&server, 1) - server.containers[0].State.Running = true + getContainer(&server).State.Running = true server.buildMuxer() recorder := httptest.NewRecorder() - path := fmt.Sprintf("/containers/%s/start", server.containers[0].ID) + path := fmt.Sprintf("/containers/%s/start", getContainer(&server).ID) request, _ := http.NewRequest("POST", path, bytes.NewBuffer([]byte("null"))) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotModified { @@ -779,36 +845,36 @@ func TestStartContainerAlreadyRunning(t *testing.T) { func TestStopContainer(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() addContainers(&server, 1) - server.containers[0].State.Running = true + getContainer(&server).State.Running = true server.buildMuxer() recorder := httptest.NewRecorder() - path := fmt.Sprintf("/containers/%s/stop", server.containers[0].ID) + path := fmt.Sprintf("/containers/%s/stop", getContainer(&server).ID) request, _ := http.NewRequest("POST", path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNoContent { t.Errorf("StopContainer: wrong status code. Want %d. Got %d.", http.StatusNoContent, recorder.Code) } - if server.containers[0].State.Running { + if getContainer(&server).State.Running { t.Error("StopContainer: did not stop the container") } } func TestKillContainer(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() addContainers(&server, 1) - server.containers[0].State.Running = true + getContainer(&server).State.Running = true server.buildMuxer() recorder := httptest.NewRecorder() - path := fmt.Sprintf("/containers/%s/kill", server.containers[0].ID) + path := fmt.Sprintf("/containers/%s/kill", getContainer(&server).ID) request, _ := http.NewRequest("POST", path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNoContent { t.Errorf("KillContainer: wrong status code. Want %d. Got %d.", http.StatusNoContent, recorder.Code) } - if server.containers[0].State.Running { + if getContainer(&server).State.Running { t.Error("KillContainer: did not stop the container") } } @@ -816,27 +882,26 @@ func TestKillContainer(t *testing.T) { func TestStopContainerWithNotifyChannel(t *testing.T) { t.Parallel() ch := make(chan *docker.Container, 1) - server := DockerServer{} + server := baseDockerServer() server.cChan = ch - addContainers(&server, 1) - addContainers(&server, 1) - server.containers[1].State.Running = true + containers := addContainers(&server, 2) + containers[1].State.Running = true server.buildMuxer() recorder := httptest.NewRecorder() - path := fmt.Sprintf("/containers/%s/stop", server.containers[1].ID) + path := fmt.Sprintf("/containers/%s/stop", containers[1].ID) request, _ := http.NewRequest("POST", path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNoContent { t.Errorf("StopContainer: wrong status code. Want %d. Got %d.", http.StatusNoContent, recorder.Code) } - if notified := <-ch; notified != server.containers[1] { - t.Errorf("StopContainer: did not notify the proper container. Want %q. Got %q.", server.containers[1].ID, notified.ID) + if notified := <-ch; notified != containers[1] { + t.Errorf("StopContainer: did not notify the proper container. Want %q. Got %q.", containers[1].ID, notified.ID) } } func TestStopContainerNotFound(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() path := "/containers/abc123/stop" @@ -849,11 +914,11 @@ func TestStopContainerNotFound(t *testing.T) { func TestStopContainerNotRunning(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() addContainers(&server, 1) server.buildMuxer() recorder := httptest.NewRecorder() - path := fmt.Sprintf("/containers/%s/stop", server.containers[0].ID) + path := fmt.Sprintf("/containers/%s/stop", getContainer(&server).ID) request, _ := http.NewRequest("POST", path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusBadRequest { @@ -863,29 +928,29 @@ func TestStopContainerNotRunning(t *testing.T) { func TestPauseContainer(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() addContainers(&server, 1) server.buildMuxer() recorder := httptest.NewRecorder() - path := fmt.Sprintf("/containers/%s/pause", server.containers[0].ID) + path := fmt.Sprintf("/containers/%s/pause", getContainer(&server).ID) request, _ := http.NewRequest("POST", path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNoContent { t.Errorf("PauseContainer: wrong status code. Want %d. Got %d.", http.StatusNoContent, recorder.Code) } - if !server.containers[0].State.Paused { + if !getContainer(&server).State.Paused { t.Error("PauseContainer: did not pause the container") } } func TestPauseContainerAlreadyPaused(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() addContainers(&server, 1) - server.containers[0].State.Paused = true + getContainer(&server).State.Paused = true server.buildMuxer() recorder := httptest.NewRecorder() - path := fmt.Sprintf("/containers/%s/pause", server.containers[0].ID) + path := fmt.Sprintf("/containers/%s/pause", getContainer(&server).ID) request, _ := http.NewRequest("POST", path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusBadRequest { @@ -895,7 +960,7 @@ func TestPauseContainerAlreadyPaused(t *testing.T) { func TestPauseContainerNotFound(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() path := "/containers/abc123/pause" @@ -908,29 +973,29 @@ func TestPauseContainerNotFound(t *testing.T) { func TestUnpauseContainer(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() addContainers(&server, 1) - server.containers[0].State.Paused = true + getContainer(&server).State.Paused = true server.buildMuxer() recorder := httptest.NewRecorder() - path := fmt.Sprintf("/containers/%s/unpause", server.containers[0].ID) + path := fmt.Sprintf("/containers/%s/unpause", getContainer(&server).ID) request, _ := http.NewRequest("POST", path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNoContent { t.Errorf("UnpauseContainer: wrong status code. Want %d. Got %d.", http.StatusNoContent, recorder.Code) } - if server.containers[0].State.Paused { + if getContainer(&server).State.Paused { t.Error("UnpauseContainer: did not unpause the container") } } func TestUnpauseContainerNotPaused(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() addContainers(&server, 1) server.buildMuxer() recorder := httptest.NewRecorder() - path := fmt.Sprintf("/containers/%s/unpause", server.containers[0].ID) + path := fmt.Sprintf("/containers/%s/unpause", getContainer(&server).ID) request, _ := http.NewRequest("POST", path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusBadRequest { @@ -940,7 +1005,7 @@ func TestUnpauseContainerNotPaused(t *testing.T) { func TestUnpauseContainerNotFound(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() path := "/containers/abc123/unpause" @@ -953,16 +1018,16 @@ func TestUnpauseContainerNotFound(t *testing.T) { func TestWaitContainer(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() addContainers(&server, 1) - server.containers[0].State.Running = true + getContainer(&server).State.Running = true server.buildMuxer() recorder := httptest.NewRecorder() - path := fmt.Sprintf("/containers/%s/wait", server.containers[0].ID) + path := fmt.Sprintf("/containers/%s/wait", getContainer(&server).ID) request, _ := http.NewRequest("POST", path, nil) go func() { server.cMut.Lock() - server.containers[0].State.Running = false + getContainer(&server).State.Running = false server.cMut.Unlock() }() server.ServeHTTP(recorder, request) @@ -977,12 +1042,12 @@ func TestWaitContainer(t *testing.T) { func TestWaitContainerStatus(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() addContainers(&server, 1) server.buildMuxer() - server.containers[0].State.ExitCode = 63 + getContainer(&server).State.ExitCode = 63 recorder := httptest.NewRecorder() - path := fmt.Sprintf("/containers/%s/wait", server.containers[0].ID) + path := fmt.Sprintf("/containers/%s/wait", getContainer(&server).ID) request, _ := http.NewRequest("POST", path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { @@ -996,7 +1061,7 @@ func TestWaitContainerStatus(t *testing.T) { func TestWaitContainerNotFound(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() path := "/containers/abc123/wait" @@ -1028,12 +1093,12 @@ func (r *HijackableResponseRecorder) HijackBuffer() string { func TestLogContainer(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() addContainers(&server, 1) - server.containers[0].State.Running = true + getContainer(&server).State.Running = true server.buildMuxer() recorder := httptest.NewRecorder() - path := fmt.Sprintf("/containers/%s/logs", server.containers[0].ID) + path := fmt.Sprintf("/containers/%s/logs", getContainer(&server).ID) request, _ := http.NewRequest("GET", path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { @@ -1043,7 +1108,7 @@ func TestLogContainer(t *testing.T) { func TestLogContainerNotFound(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() path := "/containers/abc123/logs" @@ -1056,12 +1121,12 @@ func TestLogContainerNotFound(t *testing.T) { func TestAttachContainer(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() addContainers(&server, 1) - server.containers[0].State.Running = true + getContainer(&server).State.Running = true server.buildMuxer() recorder := &HijackableResponseRecorder{} - path := fmt.Sprintf("/containers/%s/attach?logs=1", server.containers[0].ID) + path := fmt.Sprintf("/containers/%s/attach?logs=1", getContainer(&server).ID) request, _ := http.NewRequest("POST", path, nil) server.ServeHTTP(recorder, request) lines := []string{ @@ -1077,7 +1142,7 @@ func TestAttachContainer(t *testing.T) { func TestAttachContainerNotFound(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() recorder := &HijackableResponseRecorder{} path := "/containers/abc123/attach?logs=1" @@ -1090,11 +1155,11 @@ func TestAttachContainerNotFound(t *testing.T) { func TestAttachContainerWithStreamBlocks(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() addContainers(&server, 1) - server.containers[0].State.Running = true + getContainer(&server).State.Running = true server.buildMuxer() - path := fmt.Sprintf("/containers/%s/attach?logs=1&stdout=1&stream=1", server.containers[0].ID) + path := fmt.Sprintf("/containers/%s/attach?logs=1&stdout=1&stream=1", getContainer(&server).ID) request, _ := http.NewRequest("POST", path, nil) done := make(chan string) go func() { @@ -1108,7 +1173,7 @@ func TestAttachContainerWithStreamBlocks(t *testing.T) { case <-time.After(500 * time.Millisecond): } server.cMut.Lock() - server.containers[0].State.Running = false + getContainer(&server).State.Running = false server.cMut.Unlock() var body string select { @@ -1129,12 +1194,12 @@ func TestAttachContainerWithStreamBlocks(t *testing.T) { func TestAttachContainerWithStreamBlocksOnCreatedContainers(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() addContainers(&server, 1) - server.containers[0].State.Running = false - server.containers[0].State.StartedAt = time.Time{} + getContainer(&server).State.Running = false + getContainer(&server).State.StartedAt = time.Time{} server.buildMuxer() - path := fmt.Sprintf("/containers/%s/attach?logs=1&stdout=1&stream=1", server.containers[0].ID) + path := fmt.Sprintf("/containers/%s/attach?logs=1&stdout=1&stream=1", getContainer(&server).ID) request, _ := http.NewRequest("POST", path, nil) done := make(chan string) go func() { @@ -1148,7 +1213,7 @@ func TestAttachContainerWithStreamBlocksOnCreatedContainers(t *testing.T) { case <-time.After(500 * time.Millisecond): } server.cMut.Lock() - server.containers[0].State.StartedAt = time.Now() + getContainer(&server).State.StartedAt = time.Now() server.cMut.Unlock() var body string select { @@ -1169,11 +1234,11 @@ func TestAttachContainerWithStreamBlocksOnCreatedContainers(t *testing.T) { func TestRemoveContainer(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() addContainers(&server, 1) server.buildMuxer() recorder := httptest.NewRecorder() - path := fmt.Sprintf("/containers/%s", server.containers[0].ID) + path := fmt.Sprintf("/containers/%s", getContainer(&server).ID) request, _ := http.NewRequest("DELETE", path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNoContent { @@ -1186,11 +1251,11 @@ func TestRemoveContainer(t *testing.T) { func TestRemoveContainerByName(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() addContainers(&server, 1) server.buildMuxer() recorder := httptest.NewRecorder() - path := fmt.Sprintf("/containers/%s", server.containers[0].Name) + path := fmt.Sprintf("/containers/%s", getContainer(&server).Name) request, _ := http.NewRequest("DELETE", path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNoContent { @@ -1203,7 +1268,7 @@ func TestRemoveContainerByName(t *testing.T) { func TestRemoveContainerNotFound(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() path := fmt.Sprintf("/containers/abc123") @@ -1216,12 +1281,12 @@ func TestRemoveContainerNotFound(t *testing.T) { func TestRemoveContainerRunning(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() addContainers(&server, 1) - server.containers[0].State.Running = true + getContainer(&server).State.Running = true server.buildMuxer() recorder := httptest.NewRecorder() - path := fmt.Sprintf("/containers/%s", server.containers[0].ID) + path := fmt.Sprintf("/containers/%s", getContainer(&server).ID) request, _ := http.NewRequest("DELETE", path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusInternalServerError { @@ -1234,12 +1299,12 @@ func TestRemoveContainerRunning(t *testing.T) { func TestRemoveContainerRunningForce(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() addContainers(&server, 1) - server.containers[0].State.Running = true + getContainer(&server).State.Running = true server.buildMuxer() recorder := httptest.NewRecorder() - path := fmt.Sprintf("/containers/%s?%s", server.containers[0].ID, "force=1") + path := fmt.Sprintf("/containers/%s?%s", getContainer(&server).ID, "force=1") request, _ := http.NewRequest("DELETE", path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNoContent { @@ -1252,7 +1317,7 @@ func TestRemoveContainerRunningForce(t *testing.T) { func TestPullImage(t *testing.T) { t.Parallel() - server := DockerServer{imgIDs: make(map[string]string)} + server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest("POST", "/images/create?fromImage=base", nil) @@ -1266,14 +1331,17 @@ func TestPullImage(t *testing.T) { if _, ok := server.imgIDs["base"]; !ok { t.Error("PullImage: Repository should not be empty.") } - if server.images[0].Config == nil { + var image docker.Image + for _, image = range server.images { + } + if image.Config == nil { t.Error("PullImage: Image Config should not be nil.") } } func TestPullImageWithTag(t *testing.T) { t.Parallel() - server := DockerServer{imgIDs: make(map[string]string)} + server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest("POST", "/images/create?fromImage=base&tag=tag", nil) @@ -1291,7 +1359,7 @@ func TestPullImageWithTag(t *testing.T) { func TestPullImageWithShaTag(t *testing.T) { t.Parallel() - server := DockerServer{imgIDs: make(map[string]string)} + server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest("POST", "/images/create?fromImage=base&tag=sha256:deadc0de", nil) @@ -1307,9 +1375,45 @@ func TestPullImageWithShaTag(t *testing.T) { } } +func TestPullImageExisting(t *testing.T) { + t.Parallel() + server := baseDockerServer() + server.buildMuxer() + recorder := httptest.NewRecorder() + request, _ := http.NewRequest("POST", "/images/create?fromImage=base", nil) + server.ServeHTTP(recorder, request) + if recorder.Code != http.StatusOK { + t.Errorf("PullImage: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) + } + if len(server.images) != 1 { + t.Errorf("PullImage: Want 1 image. Got %d.", len(server.images)) + } + if _, ok := server.imgIDs["base"]; !ok { + t.Error("PullImage: Repository should not be empty.") + } + oldID := server.imgIDs["base"] + recorder = httptest.NewRecorder() + request, _ = http.NewRequest("POST", "/images/create?fromImage=base", nil) + server.ServeHTTP(recorder, request) + if recorder.Code != http.StatusOK { + t.Errorf("PullImage: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) + } + if len(server.images) != 1 { + t.Errorf("PullImage: Want 1 image. Got %d.", len(server.images)) + } + if _, ok := server.imgIDs["base"]; !ok { + t.Error("PullImage: Repository should not be empty.") + } + newID := server.imgIDs["base"] + if oldID != newID { + t.Error("PullImage: Image ID should be the same after second pull.") + } +} + func TestPushImage(t *testing.T) { t.Parallel() - server := DockerServer{imgIDs: map[string]string{"tsuru/python": "a123"}} + server := baseDockerServer() + server.imgIDs = map[string]string{"tsuru/python": "a123"} server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest("POST", "/images/tsuru/python/push", nil) @@ -1321,7 +1425,8 @@ func TestPushImage(t *testing.T) { func TestPushImageWithTag(t *testing.T) { t.Parallel() - server := DockerServer{imgIDs: map[string]string{"tsuru/python:v1": "a123"}} + server := baseDockerServer() + server.imgIDs = map[string]string{"tsuru/python:v1": "a123"} server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest("POST", "/images/tsuru/python/push?tag=v1", nil) @@ -1333,7 +1438,7 @@ func TestPushImageWithTag(t *testing.T) { func TestPushImageNotFound(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest("POST", "/images/tsuru/python/push", nil) @@ -1345,7 +1450,8 @@ func TestPushImageNotFound(t *testing.T) { func TestTagImage(t *testing.T) { t.Parallel() - server := DockerServer{imgIDs: map[string]string{"tsuru/python": "a123"}} + server := baseDockerServer() + server.imgIDs = map[string]string{"tsuru/python": "a123"} server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest("POST", "/images/tsuru/python/tag?repo=tsuru/new-python", nil) @@ -1360,7 +1466,8 @@ func TestTagImage(t *testing.T) { func TestTagImageWithRepoAndTag(t *testing.T) { t.Parallel() - server := DockerServer{imgIDs: map[string]string{"tsuru/python": "a123"}} + server := baseDockerServer() + server.imgIDs = map[string]string{"tsuru/python": "a123"} server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest("POST", "/images/tsuru/python/tag?repo=tsuru/new-python&tag=v1", nil) @@ -1373,9 +1480,25 @@ func TestTagImageWithRepoAndTag(t *testing.T) { } } +func TestTagImageWithID(t *testing.T) { + t.Parallel() + server := baseDockerServer() + server.images = map[string]docker.Image{"myimgid": {ID: "myimgid"}} + server.buildMuxer() + recorder := httptest.NewRecorder() + request, _ := http.NewRequest("POST", "/images/myimgid/tag?repo=tsuru/new-python", nil) + server.ServeHTTP(recorder, request) + if recorder.Code != http.StatusCreated { + t.Errorf("TagImage: wrong status. Want %d. Got %d.", http.StatusCreated, recorder.Code) + } + if server.imgIDs["tsuru/new-python"] != "myimgid" { + t.Errorf("TagImage: did not tag the image") + } +} + func TestTagImageNotFound(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest("POST", "/images/tsuru/python/tag", nil) @@ -1385,9 +1508,73 @@ func TestTagImageNotFound(t *testing.T) { } } -func addContainers(server *DockerServer, n int) { +func TestInspectImage(t *testing.T) { + t.Parallel() + server := baseDockerServer() + server.imgIDs = map[string]string{"tsuru/python": "a123"} + server.images = map[string]docker.Image{"a123": {ID: "a123", Author: "me"}} + server.buildMuxer() + recorder := httptest.NewRecorder() + request, _ := http.NewRequest("GET", "/images/tsuru/python/json", nil) + server.ServeHTTP(recorder, request) + if recorder.Code != http.StatusOK { + t.Errorf("InspectImage: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) + } + var img docker.Image + err := json.NewDecoder(recorder.Body).Decode(&img) + if err != nil { + t.Fatal(err) + } + expected := docker.Image{ + ID: "a123", + Author: "me", + } + if !reflect.DeepEqual(img, expected) { + t.Errorf("InspectImage: wrong image returned, expected %#v, got: %#v", expected, img) + } +} + +func TestInspectImageWithID(t *testing.T) { + t.Parallel() + server := baseDockerServer() + server.images = map[string]docker.Image{"myimgid": {ID: "myimgid", Author: "me"}} + server.buildMuxer() + recorder := httptest.NewRecorder() + request, _ := http.NewRequest("GET", "/images/myimgid/json", nil) + server.ServeHTTP(recorder, request) + if recorder.Code != http.StatusOK { + t.Errorf("InspectImage: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) + } + var img docker.Image + err := json.NewDecoder(recorder.Body).Decode(&img) + if err != nil { + t.Fatal(err) + } + expected := docker.Image{ + ID: "myimgid", + Author: "me", + } + if !reflect.DeepEqual(img, expected) { + t.Errorf("InspectImage: wrong image returned, expected %#v, got: %#v", expected, img) + } +} + +func TestInspectImageNotFound(t *testing.T) { + t.Parallel() + server := baseDockerServer() + server.buildMuxer() + recorder := httptest.NewRecorder() + request, _ := http.NewRequest("GET", "/images/tsuru/python/json", nil) + server.ServeHTTP(recorder, request) + if recorder.Code != http.StatusNotFound { + t.Errorf("InspectImage: wrong status. Want %d. Got %d.", http.StatusNotFound, recorder.Code) + } +} + +func addContainers(server *DockerServer, n int) []*docker.Container { server.cMut.Lock() defer server.cMut.Unlock() + var addedContainers []*docker.Container for i := 0; i < n; i++ { date := time.Now().Add(time.Duration((rand.Int() % (i + 1))) * time.Hour) container := docker.Container{ @@ -1403,6 +1590,7 @@ func addContainers(server *DockerServer, n int) { Env: []string{"ME=you", fmt.Sprintf("NUMBER=%d", i)}, Cmd: []string{"ls", "-la", ".."}, Image: "base", + Labels: map[string]string{"key": fmt.Sprintf("val-%d", i)}, }, State: docker.State{ Running: false, @@ -1427,33 +1615,41 @@ func addContainers(server *DockerServer, n int) { }, ResolvConfPath: "/etc/resolv.conf", } - server.containers = append(server.containers, &container) + server.addContainer(&container) + addedContainers = append(addedContainers, &container) } + return addedContainers } -func addImages(server *DockerServer, n int, repo bool) { +func addImages(server *DockerServer, n int, repo bool) []docker.Image { server.iMut.Lock() defer server.iMut.Unlock() if server.imgIDs == nil { server.imgIDs = make(map[string]string) } + if server.images == nil { + server.images = make(map[string]docker.Image) + } + var addedImages []docker.Image for i := 0; i < n; i++ { date := time.Now().Add(time.Duration((rand.Int() % (i + 1))) * time.Hour) image := docker.Image{ ID: fmt.Sprintf("%x", rand.Int()%10000), Created: date, } - server.images = append(server.images, image) + addedImages = append(addedImages, image) + server.images[image.ID] = image if repo { repo := "docker/python-" + image.ID server.imgIDs[repo] = image.ID } } + return addedImages } func TestListImages(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() addImages(&server, 2, true) server.buildMuxer() recorder := httptest.NewRecorder() @@ -1463,18 +1659,26 @@ func TestListImages(t *testing.T) { t.Errorf("ListImages: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } expected := make([]docker.APIImages, 2) - for i, image := range server.images { + i := 0 + for _, image := range server.images { expected[i] = docker.APIImages{ ID: image.ID, Created: image.Created.Unix(), RepoTags: []string{"docker/python-" + image.ID}, } + i++ } + sort.Slice(expected, func(i, j int) bool { + return expected[i].ID < expected[j].ID + }) var got []docker.APIImages err := json.NewDecoder(recorder.Body).Decode(&got) if err != nil { t.Fatal(err) } + sort.Slice(got, func(i, j int) bool { + return got[i].ID < got[j].ID + }) if !reflect.DeepEqual(got, expected) { t.Errorf("ListImages. Want %#v. Got %#v.", expected, got) } @@ -1482,11 +1686,11 @@ func TestListImages(t *testing.T) { func TestRemoveImage(t *testing.T) { t.Parallel() - server := DockerServer{} - addImages(&server, 1, false) + server := baseDockerServer() + images := addImages(&server, 1, false) server.buildMuxer() recorder := httptest.NewRecorder() - path := fmt.Sprintf("/images/%s", server.images[0].ID) + path := fmt.Sprintf("/images/%s", images[0].ID) request, _ := http.NewRequest("DELETE", path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNoContent { @@ -1499,11 +1703,11 @@ func TestRemoveImage(t *testing.T) { func TestRemoveImageByName(t *testing.T) { t.Parallel() - server := DockerServer{} - addImages(&server, 1, true) + server := baseDockerServer() + images := addImages(&server, 1, true) server.buildMuxer() recorder := httptest.NewRecorder() - imgName := "docker/python-" + server.images[0].ID + imgName := "docker/python-" + images[0].ID path := "/images/" + imgName request, _ := http.NewRequest("DELETE", path, nil) server.ServeHTTP(recorder, request) @@ -1521,10 +1725,10 @@ func TestRemoveImageByName(t *testing.T) { func TestRemoveImageWithMultipleTags(t *testing.T) { t.Parallel() - server := DockerServer{} - addImages(&server, 1, true) + server := baseDockerServer() + images := addImages(&server, 1, true) server.buildMuxer() - imgID := server.images[0].ID + imgID := images[0].ID imgName := "docker/python-" + imgID server.imgIDs["docker/python-wat"] = imgID recorder := httptest.NewRecorder() @@ -1545,14 +1749,53 @@ func TestRemoveImageWithMultipleTags(t *testing.T) { if len(server.images) < 1 { t.Fatal("RemoveImage: removed the image, but should keep it") } - if server.images[0].ID != imgID { + if server.images[imgID].ID != imgID { t.Error("RemoveImage: changed the ID of the image!") } } +func TestRemoveImageByIDWithMultipleTags(t *testing.T) { + t.Parallel() + server := baseDockerServer() + images := addImages(&server, 1, true) + server.buildMuxer() + imgID := images[0].ID + server.imgIDs["docker/python-wat"] = imgID + recorder := httptest.NewRecorder() + path := fmt.Sprintf("/images/%s", imgID) + request, _ := http.NewRequest("DELETE", path, nil) + server.ServeHTTP(recorder, request) + if recorder.Code != http.StatusConflict { + t.Errorf("RemoveImage: wrong status. Want %d. Got %d.", http.StatusConflict, recorder.Code) + } +} + +func TestRemoveImageByIDWithSingleTag(t *testing.T) { + t.Parallel() + server := baseDockerServer() + images := addImages(&server, 1, true) + server.buildMuxer() + imgID := images[0].ID + recorder := httptest.NewRecorder() + path := fmt.Sprintf("/images/%s", imgID) + request, _ := http.NewRequest("DELETE", path, nil) + server.ServeHTTP(recorder, request) + if recorder.Code != http.StatusNoContent { + t.Errorf("RemoveImage: wrong status. Want %d. Got %d.", http.StatusNoContent, recorder.Code) + } + if len(server.images) > 0 { + t.Error("RemoveImage: did not remove the image.") + } + imgName := "docker/python-" + imgID + _, ok := server.imgIDs[imgName] + if ok { + t.Error("RemoveImage: did not remove image tag name.") + } +} + func TestPrepareFailure(t *testing.T) { t.Parallel() - server := DockerServer{failures: make(map[string]string)} + server := baseDockerServer() server.buildMuxer() errorID := "my_error" server.PrepareFailure(errorID, "containers/json") @@ -1569,7 +1812,7 @@ func TestPrepareFailure(t *testing.T) { func TestPrepareMultiFailures(t *testing.T) { t.Parallel() - server := DockerServer{multiFailures: []map[string]string{}} + server := baseDockerServer() server.buildMuxer() errorID := "multi error" server.PrepareMultiFailures(errorID, "containers/json") @@ -1605,7 +1848,7 @@ func TestPrepareMultiFailures(t *testing.T) { func TestRemoveFailure(t *testing.T) { t.Parallel() - server := DockerServer{failures: make(map[string]string)} + server := baseDockerServer() server.buildMuxer() errorID := "my_error" server.PrepareFailure(errorID, "containers/json") @@ -1626,7 +1869,7 @@ func TestRemoveFailure(t *testing.T) { func TestResetMultiFailures(t *testing.T) { t.Parallel() - server := DockerServer{multiFailures: []map[string]string{}} + server := baseDockerServer() server.buildMuxer() errorID := "multi error" server.PrepareMultiFailures(errorID, "containers/json") @@ -1642,23 +1885,23 @@ func TestResetMultiFailures(t *testing.T) { func TestMutateContainer(t *testing.T) { t.Parallel() - server := DockerServer{failures: make(map[string]string)} + server := baseDockerServer() server.buildMuxer() - server.containers = append(server.containers, &docker.Container{ID: "id123"}) + server.addContainer(&docker.Container{ID: "id123"}) state := docker.State{Running: false, ExitCode: 1} err := server.MutateContainer("id123", state) if err != nil { t.Fatal(err) } - if !reflect.DeepEqual(server.containers[0].State, state) { + if !reflect.DeepEqual(getContainer(&server).State, state) { t.Errorf("Wrong state after mutation.\nWant %#v.\nGot %#v.", - state, server.containers[0].State) + state, getContainer(&server).State) } } func TestMutateContainerNotFound(t *testing.T) { t.Parallel() - server := DockerServer{failures: make(map[string]string)} + server := baseDockerServer() server.buildMuxer() state := docker.State{Running: false, ExitCode: 1} err := server.MutateContainer("id123", state) @@ -1672,7 +1915,7 @@ func TestMutateContainerNotFound(t *testing.T) { func TestBuildImageWithContentTypeTar(t *testing.T) { t.Parallel() - server := DockerServer{imgIDs: make(map[string]string)} + server := baseDockerServer() imageName := "teste" recorder := httptest.NewRecorder() tarFile, err := os.Open("data/dockerfile.tar") @@ -1694,7 +1937,7 @@ func TestBuildImageWithContentTypeTar(t *testing.T) { func TestBuildImageWithRemoteDockerfile(t *testing.T) { t.Parallel() - server := DockerServer{imgIDs: make(map[string]string)} + server := baseDockerServer() imageName := "teste" recorder := httptest.NewRecorder() request, _ := http.NewRequest("POST", "/build?t=teste&remote=http://localhost/Dockerfile", nil) @@ -1706,7 +1949,7 @@ func TestBuildImageWithRemoteDockerfile(t *testing.T) { func TestPing(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() recorder := httptest.NewRecorder() request, _ := http.NewRequest("GET", "/_ping", nil) server.pingDocker(recorder, request) @@ -1732,12 +1975,12 @@ func TestDefaultHandler(t *testing.T) { func TestCreateExecContainer(t *testing.T) { t.Parallel() - server := DockerServer{} - addContainers(&server, 2) + server := baseDockerServer() + containers := addContainers(&server, 2) server.buildMuxer() recorder := httptest.NewRecorder() body := `{"Cmd": ["bash", "-c", "ls"]}` - path := fmt.Sprintf("/containers/%s/exec", server.containers[0].ID) + path := fmt.Sprintf("/containers/%s/exec", containers[0].ID) request, _ := http.NewRequest("POST", path, strings.NewReader(body)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { @@ -1759,7 +2002,7 @@ func TestCreateExecContainer(t *testing.T) { EntryPoint: "bash", Arguments: []string{"-c", "ls"}, }, - ContainerID: server.containers[0].ID, + ContainerID: containers[0].ID, } if !reflect.DeepEqual(*serverExec, expected) { @@ -1769,12 +2012,12 @@ func TestCreateExecContainer(t *testing.T) { func TestInspectExecContainer(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() addContainers(&server, 1) server.buildMuxer() recorder := httptest.NewRecorder() body := `{"Cmd": ["bash", "-c", "ls"]}` - path := fmt.Sprintf("/containers/%s/exec", server.containers[0].ID) + path := fmt.Sprintf("/containers/%s/exec", getContainer(&server).ID) request, _ := http.NewRequest("POST", path, strings.NewReader(body)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { @@ -1802,7 +2045,7 @@ func TestInspectExecContainer(t *testing.T) { EntryPoint: "bash", Arguments: []string{"-c", "ls"}, }, - ContainerID: server.containers[0].ID, + ContainerID: getContainer(&server).ID, } if !reflect.DeepEqual(got2, expected) { @@ -1818,7 +2061,7 @@ func TestStartExecContainer(t *testing.T) { server.buildMuxer() recorder := httptest.NewRecorder() body := `{"Cmd": ["bash", "-c", "ls"]}` - path := fmt.Sprintf("/containers/%s/exec", server.containers[0].ID) + path := fmt.Sprintf("/containers/%s/exec", getContainer(server).ID) request, _ := http.NewRequest("POST", path, strings.NewReader(body)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { @@ -1873,7 +2116,7 @@ func TestStartExecContainerWildcardCallback(t *testing.T) { server.buildMuxer() recorder := httptest.NewRecorder() body := `{"Cmd": ["bash", "-c", "ls"]}` - path := fmt.Sprintf("/containers/%s/exec", server.containers[0].ID) + path := fmt.Sprintf("/containers/%s/exec", getContainer(server).ID) request, _ := http.NewRequest("POST", path, strings.NewReader(body)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { @@ -1952,15 +2195,15 @@ func TestStatsContainer(t *testing.T) { t.Fatal(err) } defer server.Stop() - addContainers(server, 2) + containers := addContainers(server, 2) server.buildMuxer() expected := docker.Stats{} expected.CPUStats.CPUUsage.TotalUsage = 20 - server.PrepareStats(server.containers[0].ID, func(id string) docker.Stats { + server.PrepareStats(containers[0].ID, func(id string) docker.Stats { return expected }) recorder := httptest.NewRecorder() - path := fmt.Sprintf("/containers/%s/stats?stream=false", server.containers[0].ID) + path := fmt.Sprintf("/containers/%s/stats?stream=false", containers[0].ID) request, _ := http.NewRequest("GET", path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { @@ -1997,18 +2240,18 @@ func TestStatsContainerStream(t *testing.T) { t.Fatal(err) } defer server.Stop() - addContainers(server, 2) + containers := addContainers(server, 2) server.buildMuxer() expected := docker.Stats{} expected.CPUStats.CPUUsage.TotalUsage = 20 - server.PrepareStats(server.containers[0].ID, func(id string) docker.Stats { + server.PrepareStats(containers[0].ID, func(id string) docker.Stats { time.Sleep(50 * time.Millisecond) return expected }) recorder := &safeWriter{ ResponseRecorder: httptest.NewRecorder(), } - path := fmt.Sprintf("/containers/%s/stats?stream=true", server.containers[0].ID) + path := fmt.Sprintf("/containers/%s/stats?stream=true", containers[0].ID) request, _ := http.NewRequest("GET", path, nil) go func() { server.ServeHTTP(recorder, request) @@ -2055,7 +2298,7 @@ func addNetworks(server *DockerServer, n int) { func TestListNetworks(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() addNetworks(&server, 2) server.buildMuxer() recorder := httptest.NewRecorder() @@ -2089,7 +2332,7 @@ type createNetworkResponse struct { func TestCreateNetwork(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() netid := fmt.Sprintf("%x", rand.Int()%10000) @@ -2114,7 +2357,7 @@ func TestCreateNetwork(t *testing.T) { func TestCreateNetworkInvalidBody(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest("POST", "/networks/create", strings.NewReader("whaaaaaat---")) @@ -2126,7 +2369,7 @@ func TestCreateNetworkInvalidBody(t *testing.T) { func TestCreateNetworkDuplicateName(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() addNetworks(&server, 1) server.networks[0].Name = "mynetwork" @@ -2141,7 +2384,7 @@ func TestCreateNetworkDuplicateName(t *testing.T) { func TestRemoveNetwork(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() server.networks = []*docker.Network{ @@ -2168,9 +2411,29 @@ func TestRemoveNetwork(t *testing.T) { } } +func TestNetworkConnect(t *testing.T) { + t.Parallel() + server := baseDockerServer() + server.buildMuxer() + addNetworks(&server, 1) + server.networks[0].ID = fmt.Sprintf("%x", rand.Int()%10000) + server.imgIDs = map[string]string{"base": "a1234"} + containers := addContainers(&server, 1) + containers[0].ID = fmt.Sprintf("%x", rand.Int()%10000) + server.addContainer(containers[0]) + + recorder := httptest.NewRecorder() + body := fmt.Sprintf(`{"Container": "%s" }`, containers[0].ID) + request, _ := http.NewRequest("POST", fmt.Sprintf("/networks/%s/connect", server.networks[0].ID), strings.NewReader(body)) + server.ServeHTTP(recorder, request) + if recorder.Code != http.StatusOK { + t.Errorf("NetworkConnect: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) + } +} + func TestListVolumes(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() expected := []docker.Volume{{ Name: "test-vol-1", @@ -2207,7 +2470,7 @@ func TestListVolumes(t *testing.T) { func TestCreateVolume(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() body := `{"Name":"test-volume"}` @@ -2234,7 +2497,7 @@ func TestCreateVolume(t *testing.T) { func TestCreateVolumeAlreadExists(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() server.volStore = make(map[string]*volumeCounter) server.volStore["test-volume"] = &volumeCounter{ @@ -2270,7 +2533,7 @@ func TestCreateVolumeAlreadExists(t *testing.T) { func TestInspectVolume(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() expected := docker.Volume{ @@ -2308,7 +2571,7 @@ func TestInspectVolume(t *testing.T) { func TestInspectVolumeNotFound(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest("GET", "/volumes/test-volume", nil) @@ -2320,7 +2583,7 @@ func TestInspectVolumeNotFound(t *testing.T) { func TestRemoveVolume(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() server.volStore = make(map[string]*volumeCounter) server.volStore["test-volume"] = &volumeCounter{ @@ -2341,7 +2604,7 @@ func TestRemoveVolume(t *testing.T) { func TestRemoveMissingVolume(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest("DELETE", "/volumes/test-volume", nil) @@ -2353,7 +2616,7 @@ func TestRemoveMissingVolume(t *testing.T) { func TestRemoveVolumeInuse(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() server.volStore = make(map[string]*volumeCounter) server.volStore["test-volume"] = &volumeCounter{ @@ -2374,7 +2637,7 @@ func TestRemoveVolumeInuse(t *testing.T) { func TestUploadToContainer(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() cont := &docker.Container{ ID: "id123", @@ -2383,7 +2646,7 @@ func TestUploadToContainer(t *testing.T) { ExitCode: 0, }, } - server.containers = append(server.containers, cont) + server.addContainer(cont) server.uploadedFiles = make(map[string]string) recorder := httptest.NewRecorder() request, _ := http.NewRequest("PUT", fmt.Sprintf("/containers/%s/archive?path=abcd", cont.ID), nil) @@ -2400,7 +2663,7 @@ func TestUploadToContainer(t *testing.T) { func TestUploadToContainerWithBodyTarFile(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() cont := &docker.Container{ ID: "id123", @@ -2420,7 +2683,7 @@ func TestUploadToContainerWithBodyTarFile(t *testing.T) { tw.WriteHeader(hdr) tw.Write([]byte("something")) tw.Close() - server.containers = append(server.containers, cont) + server.addContainer(cont) server.uploadedFiles = make(map[string]string) recorder := httptest.NewRecorder() request, _ := http.NewRequest("PUT", fmt.Sprintf("/containers/%s/archive?path=abcd", cont.ID), buf) @@ -2437,7 +2700,7 @@ func TestUploadToContainerWithBodyTarFile(t *testing.T) { func TestUploadToContainerBodyNotTarFile(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() cont := &docker.Container{ ID: "id123", @@ -2447,7 +2710,7 @@ func TestUploadToContainerBodyNotTarFile(t *testing.T) { }, } buf := bytes.NewBufferString("something") - server.containers = append(server.containers, cont) + server.addContainer(cont) server.uploadedFiles = make(map[string]string) recorder := httptest.NewRecorder() request, _ := http.NewRequest("PUT", fmt.Sprintf("/containers/%s/archive?path=abcd", cont.ID), buf) @@ -2464,7 +2727,7 @@ func TestUploadToContainerBodyNotTarFile(t *testing.T) { func TestUploadToContainerMissingContainer(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest("PUT", "/containers/missing-container/archive?path=abcd", nil) @@ -2542,7 +2805,7 @@ func TestVersionDocker(t *testing.T) { func TestDownloadFromContainer(t *testing.T) { t.Parallel() - server := DockerServer{} + server := baseDockerServer() server.buildMuxer() cont := &docker.Container{ ID: "id123", @@ -2551,7 +2814,7 @@ func TestDownloadFromContainer(t *testing.T) { ExitCode: 0, }, } - server.containers = append(server.containers, cont) + server.addContainer(cont) server.uploadedFiles = make(map[string]string) server.uploadedFiles[cont.ID] = "abcd" recorder := httptest.NewRecorder() diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/swarm.go b/vendor/github.com/fsouza/go-dockerclient/testing/swarm.go index 555302471..921cc1ae7 100644 --- a/vendor/github.com/fsouza/go-dockerclient/testing/swarm.go +++ b/vendor/github.com/fsouza/go-dockerclient/testing/swarm.go @@ -255,6 +255,9 @@ func (s *DockerServer) setServiceEndpoint(service *swarm.Service) { } func (s *DockerServer) addTasks(service *swarm.Service, update bool) { + if service.Spec.TaskTemplate.ContainerSpec == nil { + return + } containerCount := 1 if service.Spec.Mode.Global != nil { containerCount = len(s.nodes) @@ -277,7 +280,7 @@ func (s *DockerServer) addTasks(service *swarm.Service, update bool) { NodeID: chosenNode.ID, Status: swarm.TaskStatus{ State: swarm.TaskStateReady, - ContainerStatus: swarm.ContainerStatus{ + ContainerStatus: &swarm.ContainerStatus{ ContainerID: container.ID, }, }, @@ -285,7 +288,7 @@ func (s *DockerServer) addTasks(service *swarm.Service, update bool) { Spec: service.Spec.TaskTemplate, } s.tasks = append(s.tasks, &task) - s.containers = append(s.containers, container) + s.addContainer(container) s.notify(container) } } @@ -441,9 +444,10 @@ func (s *DockerServer) serviceDelete(w http.ResponseWriter, r *http.Request) { s.services = s.services[:len(s.services)-1] for i := 0; i < len(s.tasks); i++ { if s.tasks[i].ServiceID == toDelete.ID { - _, contIdx, _ := s.findContainerWithLock(s.tasks[i].Status.ContainerStatus.ContainerID, false) - if contIdx != -1 { - s.containers = append(s.containers[:contIdx], s.containers[contIdx+1:]...) + cont, _ := s.findContainerWithLock(s.tasks[i].Status.ContainerStatus.ContainerID, false) + if cont != nil { + delete(s.containers, cont.ID) + delete(s.contNameToID, cont.Name) } s.tasks = append(s.tasks[:i], s.tasks[i+1:]...) i-- @@ -457,6 +461,7 @@ func (s *DockerServer) serviceDelete(w http.ResponseWriter, r *http.Request) { } func (s *DockerServer) serviceUpdate(w http.ResponseWriter, r *http.Request) { + start := time.Now() s.swarmMut.Lock() defer s.swarmMut.Unlock() s.cMut.Lock() @@ -484,14 +489,21 @@ func (s *DockerServer) serviceUpdate(w http.ResponseWriter, r *http.Request) { return } toUpdate.Spec = newSpec + end := time.Now() + toUpdate.UpdateStatus = &swarm.UpdateStatus{ + State: swarm.UpdateStateCompleted, + CompletedAt: &end, + StartedAt: &start, + } s.setServiceEndpoint(toUpdate) for i := 0; i < len(s.tasks); i++ { if s.tasks[i].ServiceID != toUpdate.ID { continue } - _, contIdx, _ := s.findContainerWithLock(s.tasks[i].Status.ContainerStatus.ContainerID, false) - if contIdx != -1 { - s.containers = append(s.containers[:contIdx], s.containers[contIdx+1:]...) + cont, _ := s.findContainerWithLock(s.tasks[i].Status.ContainerStatus.ContainerID, false) + if cont != nil { + delete(s.containers, cont.ID) + delete(s.contNameToID, cont.Name) } s.tasks = append(s.tasks[:i], s.tasks[i+1:]...) i-- diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/swarm_test.go b/vendor/github.com/fsouza/go-dockerclient/testing/swarm_test.go index 1059071a7..123efd3af 100644 --- a/vendor/github.com/fsouza/go-dockerclient/testing/swarm_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/testing/swarm_test.go @@ -373,7 +373,7 @@ func TestServiceCreate(t *testing.T) { if len(server.services) != 1 || len(server.tasks) != 1 || len(server.containers) != 1 { t.Fatalf("ServiceCreate: wrong item count. Want 1. Got services: %d, tasks: %d, containers: %d.", len(server.services), len(server.tasks), len(server.containers)) } - cont := server.containers[0] + cont := getContainer(server) expectedContainer := &docker.Container{ ID: cont.ID, Created: cont.Created, @@ -414,7 +414,7 @@ func TestServiceCreate(t *testing.T) { NodeID: server.nodes[0].ID, Status: swarm.TaskStatus{ State: swarm.TaskStateReady, - ContainerStatus: swarm.ContainerStatus{ + ContainerStatus: &swarm.ContainerStatus{ ContainerID: cont.ID, }, }, @@ -508,6 +508,34 @@ func TestServiceCreateMultipleServers(t *testing.T) { } } +func TestServiceCreateNoContainers(t *testing.T) { + server, unused := setUpSwarm(t) + defer server.Stop() + defer unused.Stop() + recorder := httptest.NewRecorder() + serviceCreateOpts := docker.CreateServiceOptions{ + ServiceSpec: swarm.ServiceSpec{ + Annotations: swarm.Annotations{ + Name: "test", + }, + }, + } + buf, err := json.Marshal(serviceCreateOpts) + if err != nil { + t.Fatalf("ServiceCreate error: %s", err.Error()) + } + var params io.Reader + params = bytes.NewBuffer(buf) + request, _ := http.NewRequest("POST", "/services/create", params) + server.ServeHTTP(recorder, request) + if recorder.Code != http.StatusOK { + t.Fatalf("ServiceCreate: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) + } + if len(server.services) != 1 || len(server.tasks) != 0 || len(server.containers) != 0 { + t.Fatalf("ServiceCreate: wrong item count. Want 1 service and 0 tasks. Got services: %d, tasks: %d, containers: %d.", len(server.services), len(server.tasks), len(server.containers)) + } +} + func compareServices(srv1 *swarm.Service, srv2 *swarm.Service) bool { srv1.CreatedAt = srv2.CreatedAt srv1.UpdatedAt = srv2.UpdatedAt @@ -1021,7 +1049,7 @@ func TestServiceUpdate(t *testing.T) { if len(server.services) != 1 || len(server.tasks) != 1 || len(server.containers) != 1 { t.Fatalf("ServiceUpdate: wrong item count. Want 1. Got services: %d, tasks: %d, containers: %d.", len(server.services), len(server.tasks), len(server.containers)) } - cont := server.containers[0] + cont := getContainer(server) expectedContainer := &docker.Container{ ID: cont.ID, Created: cont.Created, @@ -1050,7 +1078,12 @@ func TestServiceUpdate(t *testing.T) { Spec: *updateOpts.EndpointSpec, Ports: []swarm.PortConfig{{Protocol: "tcp", TargetPort: 80, PublishedPort: 80}}, }, + UpdateStatus: &swarm.UpdateStatus{ + State: swarm.UpdateStateCompleted, + }, } + srv.UpdateStatus.CompletedAt = nil + srv.UpdateStatus.StartedAt = nil if !reflect.DeepEqual(srv, expectedService) { t.Fatalf("ServiceUpdate: wrong service. Want\n%#v\nGot\n%#v", expectedService, srv) } @@ -1061,7 +1094,7 @@ func TestServiceUpdate(t *testing.T) { NodeID: server.nodes[1].ID, Status: swarm.TaskStatus{ State: swarm.TaskStateReady, - ContainerStatus: swarm.ContainerStatus{ + ContainerStatus: &swarm.ContainerStatus{ ContainerID: cont.ID, }, }, diff --git a/vendor/github.com/fsouza/go-dockerclient/volume.go b/vendor/github.com/fsouza/go-dockerclient/volume.go index 3c7bdeaa7..021a262b7 100644 --- a/vendor/github.com/fsouza/go-dockerclient/volume.go +++ b/vendor/github.com/fsouza/go-dockerclient/volume.go @@ -5,11 +5,10 @@ package docker import ( + "context" "encoding/json" "errors" "net/http" - - "golang.org/x/net/context" ) var ( @@ -22,17 +21,18 @@ var ( // Volume represents a volume. // -// See https://goo.gl/FZA4BK for more details. +// See https://goo.gl/3wgTsd for more details. type Volume struct { Name string `json:"Name" yaml:"Name" toml:"Name"` Driver string `json:"Driver,omitempty" yaml:"Driver,omitempty" toml:"Driver,omitempty"` Mountpoint string `json:"Mountpoint,omitempty" yaml:"Mountpoint,omitempty" toml:"Mountpoint,omitempty"` Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty" toml:"Labels,omitempty"` + Options map[string]string `json:"Options,omitempty" yaml:"Options,omitempty" toml:"Options,omitempty"` } // ListVolumesOptions specify parameters to the ListVolumes function. // -// See https://goo.gl/FZA4BK for more details. +// See https://goo.gl/3wgTsd for more details. type ListVolumesOptions struct { Filters map[string][]string Context context.Context @@ -40,7 +40,7 @@ type ListVolumesOptions struct { // ListVolumes returns a list of available volumes in the server. // -// See https://goo.gl/FZA4BK for more details. +// See https://goo.gl/3wgTsd for more details. func (c *Client) ListVolumes(opts ListVolumesOptions) ([]Volume, error) { resp, err := c.do("GET", "/volumes?"+queryString(opts), doOptions{ context: opts.Context, @@ -70,7 +70,7 @@ func (c *Client) ListVolumes(opts ListVolumesOptions) ([]Volume, error) { // CreateVolumeOptions specify parameters to the CreateVolume function. // -// See https://goo.gl/pBUbZ9 for more details. +// See https://goo.gl/qEhmEC for more details. type CreateVolumeOptions struct { Name string Driver string @@ -81,7 +81,7 @@ type CreateVolumeOptions struct { // CreateVolume creates a volume on the server. // -// See https://goo.gl/pBUbZ9 for more details. +// See https://goo.gl/qEhmEC for more details. func (c *Client) CreateVolume(opts CreateVolumeOptions) (*Volume, error) { resp, err := c.do("POST", "/volumes/create", doOptions{ data: opts, @@ -100,7 +100,7 @@ func (c *Client) CreateVolume(opts CreateVolumeOptions) (*Volume, error) { // InspectVolume returns a volume by its name. // -// See https://goo.gl/0g9A6i for more details. +// See https://goo.gl/GMjsMc for more details. func (c *Client) InspectVolume(name string) (*Volume, error) { resp, err := c.do("GET", "/volumes/"+name, doOptions{}) if err != nil { @@ -119,9 +119,28 @@ func (c *Client) InspectVolume(name string) (*Volume, error) { // RemoveVolume removes a volume by its name. // -// See https://goo.gl/79GNQz for more details. +// Deprecated: Use RemoveVolumeWithOptions instead. func (c *Client) RemoveVolume(name string) error { - resp, err := c.do("DELETE", "/volumes/"+name, doOptions{}) + return c.RemoveVolumeWithOptions(RemoveVolumeOptions{Name: name}) +} + +// RemoveVolumeOptions specify parameters to the RemoveVolumeWithOptions +// function. +// +// See https://goo.gl/nvd6qj for more details. +type RemoveVolumeOptions struct { + Context context.Context + Name string `qs:"-"` + Force bool +} + +// RemoveVolumeWithOptions removes a volume by its name and takes extra +// parameters. +// +// See https://goo.gl/nvd6qj for more details. +func (c *Client) RemoveVolumeWithOptions(opts RemoveVolumeOptions) error { + path := "/volumes/" + opts.Name + resp, err := c.do("DELETE", path+"?"+queryString(opts), doOptions{context: opts.Context}) if err != nil { if e, ok := err.(*Error); ok { if e.Status == http.StatusNotFound { @@ -131,7 +150,7 @@ func (c *Client) RemoveVolume(name string) error { return ErrVolumeInUse } } - return nil + return err } defer resp.Body.Close() return nil @@ -139,7 +158,7 @@ func (c *Client) RemoveVolume(name string) error { // PruneVolumesOptions specify parameters to the PruneVolumes function. // -// See https://goo.gl/pFN1Hj for more details. +// See https://goo.gl/f9XDem for more details. type PruneVolumesOptions struct { Filters map[string][]string Context context.Context @@ -147,7 +166,7 @@ type PruneVolumesOptions struct { // PruneVolumesResults specify results from the PruneVolumes function. // -// See https://goo.gl/pFN1Hj for more details. +// See https://goo.gl/f9XDem for more details. type PruneVolumesResults struct { VolumesDeleted []string SpaceReclaimed int64 @@ -155,7 +174,7 @@ type PruneVolumesResults struct { // PruneVolumes deletes volumes which are unused. // -// See https://goo.gl/pFN1Hj for more details. +// See https://goo.gl/f9XDem for more details. func (c *Client) PruneVolumes(opts PruneVolumesOptions) (*PruneVolumesResults, error) { path := "/volumes/prune?" + queryString(opts) resp, err := c.do("POST", path, doOptions{context: opts.Context}) diff --git a/vendor/github.com/fsouza/go-dockerclient/volume_test.go b/vendor/github.com/fsouza/go-dockerclient/volume_test.go index d042251a5..d30ae1814 100644 --- a/vendor/github.com/fsouza/go-dockerclient/volume_test.go +++ b/vendor/github.com/fsouza/go-dockerclient/volume_test.go @@ -85,7 +85,10 @@ func TestInspectVolume(t *testing.T) { body := `{ "Name": "tardis", "Driver": "local", - "Mountpoint": "/var/lib/docker/volumes/tardis" + "Mountpoint": "/var/lib/docker/volumes/tardis", + "Options": { + "foo": "bar" + } }` var expected Volume if err := json.Unmarshal([]byte(body), &expected); err != nil { @@ -131,6 +134,28 @@ func TestRemoveVolume(t *testing.T) { } } +func TestRemoveVolumeWithOptions(t *testing.T) { + t.Parallel() + name := "test" + fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent} + client := newTestClient(fakeRT) + if err := client.RemoveVolumeWithOptions(RemoveVolumeOptions{ + Name: name, + Force: true, + }); err != nil { + t.Fatal(err) + } + req := fakeRT.requests[0] + expectedMethod := "DELETE" + if req.Method != expectedMethod { + t.Errorf("RemoveVolume(%q): Wrong HTTP method. Want %s. Got %s.", name, expectedMethod, req.Method) + } + u, _ := url.Parse(client.getURL("/volumes/" + name + "?force=1")) + if req.URL.RequestURI() != u.RequestURI() { + t.Errorf("RemoveVolume(%q): Wrong request path. Want %q. Got %q.", name, u.RequestURI(), req.URL.RequestURI()) + } +} + func TestRemoveVolumeNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such volume", status: http.StatusNotFound}) @@ -139,6 +164,14 @@ func TestRemoveVolumeNotFound(t *testing.T) { } } +func TestRemoveVolumeInternalError(t *testing.T) { + t.Parallel() + client := newTestClient(&FakeRoundTripper{message: "something went wrong", status: http.StatusInternalServerError}) + if err := client.RemoveVolume("test:test"); err == nil { + t.Error("RemoveVolume: unexpected error") + } +} + func TestRemoveVolumeInUse(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "volume in use and cannot be removed", status: http.StatusConflict})