summaryrefslogtreecommitdiff
path: root/vendor/github.com/fsouza
diff options
context:
space:
mode:
authorbaude <bbaude@redhat.com>2018-04-25 13:26:52 -0500
committerAtomic Bot <atomic-devel@projectatomic.io>2018-04-27 20:51:07 +0000
commita824186ac9803ef5f7548df790988a4ebd2d9c07 (patch)
tree63c64e9be4d9c44bd160dd974b740231497eabcd /vendor/github.com/fsouza
parent4e468ce83d69e9748e80eb98a6f5bd3c5114cc7d (diff)
downloadpodman-a824186ac9803ef5f7548df790988a4ebd2d9c07.tar.gz
podman-a824186ac9803ef5f7548df790988a4ebd2d9c07.tar.bz2
podman-a824186ac9803ef5f7548df790988a4ebd2d9c07.zip
Use buildah commit and bud in podman
Vendor in buildah and use as much of commit and bug as possible for podman build and commit. Resolves #586 Signed-off-by: baude <bbaude@redhat.com> Closes: #681 Approved by: mheon
Diffstat (limited to 'vendor/github.com/fsouza')
-rw-r--r--vendor/github.com/fsouza/go-dockerclient/DOCKER-LICENSE6
-rw-r--r--vendor/github.com/fsouza/go-dockerclient/LICENSE22
-rw-r--r--vendor/github.com/fsouza/go-dockerclient/README.markdown133
-rw-r--r--vendor/github.com/fsouza/go-dockerclient/auth.go185
-rw-r--r--vendor/github.com/fsouza/go-dockerclient/change.go43
-rw-r--r--vendor/github.com/fsouza/go-dockerclient/client.go1092
-rw-r--r--vendor/github.com/fsouza/go-dockerclient/client_unix.go32
-rw-r--r--vendor/github.com/fsouza/go-dockerclient/client_windows.go45
-rw-r--r--vendor/github.com/fsouza/go-dockerclient/container.go1623
-rw-r--r--vendor/github.com/fsouza/go-dockerclient/distribution.go26
-rw-r--r--vendor/github.com/fsouza/go-dockerclient/env.go172
-rw-r--r--vendor/github.com/fsouza/go-dockerclient/event.go410
-rw-r--r--vendor/github.com/fsouza/go-dockerclient/exec.go213
-rw-r--r--vendor/github.com/fsouza/go-dockerclient/image.go720
-rw-r--r--vendor/github.com/fsouza/go-dockerclient/misc.go188
-rw-r--r--vendor/github.com/fsouza/go-dockerclient/network.go321
-rw-r--r--vendor/github.com/fsouza/go-dockerclient/plugin.go418
-rw-r--r--vendor/github.com/fsouza/go-dockerclient/signal.go49
-rw-r--r--vendor/github.com/fsouza/go-dockerclient/swarm.go156
-rw-r--r--vendor/github.com/fsouza/go-dockerclient/swarm_configs.go171
-rw-r--r--vendor/github.com/fsouza/go-dockerclient/swarm_node.go130
-rw-r--r--vendor/github.com/fsouza/go-dockerclient/swarm_secrets.go171
-rw-r--r--vendor/github.com/fsouza/go-dockerclient/swarm_service.go213
-rw-r--r--vendor/github.com/fsouza/go-dockerclient/swarm_task.go70
-rw-r--r--vendor/github.com/fsouza/go-dockerclient/tar.go122
-rw-r--r--vendor/github.com/fsouza/go-dockerclient/tls.go118
-rw-r--r--vendor/github.com/fsouza/go-dockerclient/volume.go190
27 files changed, 7039 insertions, 0 deletions
diff --git a/vendor/github.com/fsouza/go-dockerclient/DOCKER-LICENSE b/vendor/github.com/fsouza/go-dockerclient/DOCKER-LICENSE
new file mode 100644
index 000000000..706634474
--- /dev/null
+++ b/vendor/github.com/fsouza/go-dockerclient/DOCKER-LICENSE
@@ -0,0 +1,6 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+You can find the Docker license at the following link:
+https://raw.githubusercontent.com/docker/docker/master/LICENSE
diff --git a/vendor/github.com/fsouza/go-dockerclient/LICENSE b/vendor/github.com/fsouza/go-dockerclient/LICENSE
new file mode 100644
index 000000000..f3ce3a9aa
--- /dev/null
+++ b/vendor/github.com/fsouza/go-dockerclient/LICENSE
@@ -0,0 +1,22 @@
+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.
+
+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
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/fsouza/go-dockerclient/README.markdown b/vendor/github.com/fsouza/go-dockerclient/README.markdown
new file mode 100644
index 000000000..86824d6c5
--- /dev/null
+++ b/vendor/github.com/fsouza/go-dockerclient/README.markdown
@@ -0,0 +1,133 @@
+# go-dockerclient
+
+[![Travis Build Status](https://travis-ci.org/fsouza/go-dockerclient.svg?branch=master)](https://travis-ci.org/fsouza/go-dockerclient)
+[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/4m374pti06ubg2l7?svg=true)](https://ci.appveyor.com/project/fsouza/go-dockerclient)
+[![GoDoc](https://img.shields.io/badge/api-Godoc-blue.svg?style=flat-square)](https://godoc.org/github.com/fsouza/go-dockerclient)
+
+This package presents a client for the Docker remote API. It also provides
+support for the extensions in the [Swarm API](https://docs.docker.com/swarm/swarm-api/).
+
+This package also provides support for docker's network API, which is a simple
+passthrough to the libnetwork remote API. Note that docker's network API is
+only available in docker 1.8 and above, and only enabled in docker if
+DOCKER_EXPERIMENTAL is defined during the docker build process.
+
+For more details, check the [remote API
+documentation](http://docs.docker.com/engine/reference/api/docker_remote_api/).
+
+## Example
+
+```go
+package main
+
+import (
+ "fmt"
+
+ "github.com/fsouza/go-dockerclient"
+)
+
+func main() {
+ endpoint := "unix:///var/run/docker.sock"
+ client, err := docker.NewClient(endpoint)
+ if err != nil {
+ panic(err)
+ }
+ imgs, err := client.ListImages(docker.ListImagesOptions{All: false})
+ if err != nil {
+ panic(err)
+ }
+ for _, img := range imgs {
+ fmt.Println("ID: ", img.ID)
+ fmt.Println("RepoTags: ", img.RepoTags)
+ fmt.Println("Created: ", img.Created)
+ fmt.Println("Size: ", img.Size)
+ fmt.Println("VirtualSize: ", img.VirtualSize)
+ fmt.Println("ParentId: ", img.ParentID)
+ }
+}
+```
+
+## Using with TLS
+
+In order to instantiate the client for a TLS-enabled daemon, you should use
+NewTLSClient, passing the endpoint and path for key and certificates as
+parameters.
+
+```go
+package main
+
+import (
+ "fmt"
+
+ "github.com/fsouza/go-dockerclient"
+)
+
+func main() {
+ endpoint := "tcp://[ip]:[port]"
+ path := os.Getenv("DOCKER_CERT_PATH")
+ ca := fmt.Sprintf("%s/ca.pem", path)
+ cert := fmt.Sprintf("%s/cert.pem", path)
+ key := fmt.Sprintf("%s/key.pem", path)
+ client, _ := docker.NewTLSClient(endpoint, cert, key, ca)
+ // use client
+}
+```
+
+If using [docker-machine](https://docs.docker.com/machine/), or another
+application that exports environment variables `DOCKER_HOST`,
+`DOCKER_TLS_VERIFY`, `DOCKER_CERT_PATH`, you can use NewClientFromEnv.
+
+
+```go
+package main
+
+import (
+ "fmt"
+
+ "github.com/fsouza/go-dockerclient"
+)
+
+func main() {
+ client, _ := docker.NewClientFromEnv()
+ // use client
+}
+```
+
+See the documentation for more details.
+
+## Developing
+
+All development commands can be seen in the [Makefile](Makefile).
+
+Commited code must pass:
+
+* [golint](https://github.com/golang/lint) (with some exceptions, see the Makefile).
+* [go vet](https://golang.org/cmd/vet/)
+* [gofmt](https://golang.org/cmd/gofmt)
+* [go test](https://golang.org/cmd/go/#hdr-Test_packages)
+
+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
+compiling go-dockerclient with Go 1.4), the tag name is ``docker-1.9/go-1.4``.
+
+The instructions below can be used to get a version of go-dockerclient that compiles with Go 1.4:
+
+```
+% git clone -b docker-1.9/go-1.4 https://github.com/fsouza/go-dockerclient.git $GOPATH/src/github.com/fsouza/go-dockerclient
+% git clone -b v1.9.1 https://github.com/docker/docker.git $GOPATH/src/github.com/docker/docker
+% go get github.com/fsouza/go-dockerclient
+```
diff --git a/vendor/github.com/fsouza/go-dockerclient/auth.go b/vendor/github.com/fsouza/go-dockerclient/auth.go
new file mode 100644
index 000000000..c58de8671
--- /dev/null
+++ b/vendor/github.com/fsouza/go-dockerclient/auth.go
@@ -0,0 +1,185 @@
+// Copyright 2015 go-dockerclient authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package docker
+
+import (
+ "bytes"
+ "encoding/base64"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path"
+ "strings"
+)
+
+// ErrCannotParseDockercfg is the error returned by NewAuthConfigurations when the dockercfg cannot be parsed.
+var ErrCannotParseDockercfg = errors.New("Failed to read authentication from dockercfg")
+
+// AuthConfiguration represents authentication options to use in the PushImage
+// method. It represents the authentication in the Docker index server.
+type AuthConfiguration struct {
+ Username string `json:"username,omitempty"`
+ Password string `json:"password,omitempty"`
+ Email string `json:"email,omitempty"`
+ ServerAddress string `json:"serveraddress,omitempty"`
+}
+
+// AuthConfigurations represents authentication options to use for the
+// PushImage method accommodating the new X-Registry-Config header
+type AuthConfigurations struct {
+ Configs map[string]AuthConfiguration `json:"configs"`
+}
+
+// AuthConfigurations119 is used to serialize a set of AuthConfigurations
+// for Docker API >= 1.19.
+type AuthConfigurations119 map[string]AuthConfiguration
+
+// dockerConfig represents a registry authentation configuration from the
+// .dockercfg file.
+type dockerConfig struct {
+ Auth string `json:"auth"`
+ Email string `json:"email"`
+}
+
+// NewAuthConfigurationsFromFile returns AuthConfigurations from a path containing JSON
+// in the same format as the .dockercfg file.
+func NewAuthConfigurationsFromFile(path string) (*AuthConfigurations, error) {
+ r, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ return NewAuthConfigurations(r)
+}
+
+func cfgPaths(dockerConfigEnv string, homeEnv string) []string {
+ var paths []string
+ if dockerConfigEnv != "" {
+ paths = append(paths, path.Join(dockerConfigEnv, "config.json"))
+ }
+ if homeEnv != "" {
+ paths = append(paths, path.Join(homeEnv, ".docker", "config.json"))
+ paths = append(paths, path.Join(homeEnv, ".dockercfg"))
+ }
+ return paths
+}
+
+// NewAuthConfigurationsFromDockerCfg returns AuthConfigurations from
+// system config files. The following files are checked in the order listed:
+// - $DOCKER_CONFIG/config.json if DOCKER_CONFIG set in the environment,
+// - $HOME/.docker/config.json
+// - $HOME/.dockercfg
+func NewAuthConfigurationsFromDockerCfg() (*AuthConfigurations, error) {
+ err := fmt.Errorf("No docker configuration found")
+ var auths *AuthConfigurations
+
+ pathsToTry := cfgPaths(os.Getenv("DOCKER_CONFIG"), os.Getenv("HOME"))
+ for _, path := range pathsToTry {
+ auths, err = NewAuthConfigurationsFromFile(path)
+ if err == nil {
+ return auths, nil
+ }
+ }
+ return auths, err
+}
+
+// NewAuthConfigurations returns AuthConfigurations from a JSON encoded string in the
+// same format as the .dockercfg file.
+func NewAuthConfigurations(r io.Reader) (*AuthConfigurations, error) {
+ var auth *AuthConfigurations
+ confs, err := parseDockerConfig(r)
+ if err != nil {
+ return nil, err
+ }
+ auth, err = authConfigs(confs)
+ if err != nil {
+ return nil, err
+ }
+ return auth, nil
+}
+
+func parseDockerConfig(r io.Reader) (map[string]dockerConfig, error) {
+ buf := new(bytes.Buffer)
+ buf.ReadFrom(r)
+ byteData := buf.Bytes()
+
+ confsWrapper := struct {
+ Auths map[string]dockerConfig `json:"auths"`
+ }{}
+ if err := json.Unmarshal(byteData, &confsWrapper); err == nil {
+ if len(confsWrapper.Auths) > 0 {
+ return confsWrapper.Auths, nil
+ }
+ }
+
+ var confs map[string]dockerConfig
+ if err := json.Unmarshal(byteData, &confs); err != nil {
+ return nil, err
+ }
+ return confs, nil
+}
+
+// authConfigs converts a dockerConfigs map to a AuthConfigurations object.
+func authConfigs(confs map[string]dockerConfig) (*AuthConfigurations, error) {
+ c := &AuthConfigurations{
+ 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
+ }
+ userpass := strings.SplitN(string(data), ":", 2)
+ if len(userpass) != 2 {
+ return nil, ErrCannotParseDockercfg
+ }
+ c.Configs[reg] = AuthConfiguration{
+ Email: conf.Email,
+ Username: userpass[0],
+ Password: userpass[1],
+ ServerAddress: reg,
+ }
+ }
+ return c, nil
+}
+
+// AuthStatus returns the authentication status for Docker API versions >= 1.23.
+type AuthStatus struct {
+ Status string `json:"Status,omitempty" yaml:"Status,omitempty" toml:"Status,omitempty"`
+ IdentityToken string `json:"IdentityToken,omitempty" yaml:"IdentityToken,omitempty" toml:"IdentityToken,omitempty"`
+}
+
+// AuthCheck validates the given credentials. It returns nil if successful.
+//
+// For Docker API versions >= 1.23, the AuthStatus struct will be populated, otherwise it will be empty.`
+//
+// See https://goo.gl/6nsZkH for more details.
+func (c *Client) AuthCheck(conf *AuthConfiguration) (AuthStatus, error) {
+ var authStatus AuthStatus
+ if conf == nil {
+ return authStatus, errors.New("conf is nil")
+ }
+ resp, err := c.do("POST", "/auth", doOptions{data: conf})
+ if err != nil {
+ return authStatus, err
+ }
+ defer resp.Body.Close()
+ data, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return authStatus, err
+ }
+ if len(data) == 0 {
+ return authStatus, nil
+ }
+ if err := json.Unmarshal(data, &authStatus); err != nil {
+ return authStatus, err
+ }
+ return authStatus, nil
+}
diff --git a/vendor/github.com/fsouza/go-dockerclient/change.go b/vendor/github.com/fsouza/go-dockerclient/change.go
new file mode 100644
index 000000000..3f936b223
--- /dev/null
+++ b/vendor/github.com/fsouza/go-dockerclient/change.go
@@ -0,0 +1,43 @@
+// Copyright 2014 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 "fmt"
+
+// ChangeType is a type for constants indicating the type of change
+// in a container
+type ChangeType int
+
+const (
+ // ChangeModify is the ChangeType for container modifications
+ ChangeModify ChangeType = iota
+
+ // ChangeAdd is the ChangeType for additions to a container
+ ChangeAdd
+
+ // ChangeDelete is the ChangeType for deletions from a container
+ ChangeDelete
+)
+
+// Change represents a change in a container.
+//
+// See https://goo.gl/Wo0JJp for more details.
+type Change struct {
+ Path string
+ Kind ChangeType
+}
+
+func (change *Change) String() string {
+ var kind string
+ switch change.Kind {
+ case ChangeModify:
+ kind = "C"
+ case ChangeAdd:
+ kind = "A"
+ case ChangeDelete:
+ kind = "D"
+ }
+ return fmt.Sprintf("%s %s", kind, change.Path)
+}
diff --git a/vendor/github.com/fsouza/go-dockerclient/client.go b/vendor/github.com/fsouza/go-dockerclient/client.go
new file mode 100644
index 000000000..6b754f271
--- /dev/null
+++ b/vendor/github.com/fsouza/go-dockerclient/client.go
@@ -0,0 +1,1092 @@
+// Copyright 2013 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 provides a client for the Docker remote API.
+//
+// See https://goo.gl/o2v3rk for more details on the remote API.
+package docker
+
+import (
+ "bufio"
+ "bytes"
+ "context"
+ "crypto/tls"
+ "crypto/x509"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "net/http/httputil"
+ "net/url"
+ "os"
+ "path/filepath"
+ "reflect"
+ "runtime"
+ "strconv"
+ "strings"
+ "sync/atomic"
+ "time"
+
+ "github.com/docker/docker/opts"
+ "github.com/docker/docker/pkg/homedir"
+ "github.com/docker/docker/pkg/jsonmessage"
+ "github.com/docker/docker/pkg/stdcopy"
+)
+
+const (
+ userAgent = "go-dockerclient"
+
+ unixProtocol = "unix"
+ namedPipeProtocol = "npipe"
+)
+
+var (
+ // ErrInvalidEndpoint is returned when the endpoint is not a valid HTTP URL.
+ ErrInvalidEndpoint = errors.New("invalid endpoint")
+
+ // ErrConnectionRefused is returned when the client cannot connect to the given endpoint.
+ ErrConnectionRefused = errors.New("cannot connect to Docker endpoint")
+
+ // ErrInactivityTimeout is returned when a streamable call has been inactive for some time.
+ ErrInactivityTimeout = errors.New("inactivity time exceeded timeout")
+
+ apiVersion112, _ = NewAPIVersion("1.12")
+ apiVersion119, _ = NewAPIVersion("1.19")
+ apiVersion124, _ = NewAPIVersion("1.24")
+ apiVersion125, _ = NewAPIVersion("1.25")
+)
+
+// APIVersion is an internal representation of a version of the Remote API.
+type APIVersion []int
+
+// NewAPIVersion returns an instance of APIVersion for the given string.
+//
+// The given string must be in the form <major>.<minor>.<patch>, where <major>,
+// <minor> and <patch> are integer numbers.
+func NewAPIVersion(input string) (APIVersion, error) {
+ if !strings.Contains(input, ".") {
+ return nil, fmt.Errorf("Unable to parse version %q", input)
+ }
+ raw := strings.Split(input, "-")
+ arr := strings.Split(raw[0], ".")
+ ret := make(APIVersion, len(arr))
+ var err error
+ for i, val := range arr {
+ ret[i], err = strconv.Atoi(val)
+ if err != nil {
+ return nil, fmt.Errorf("Unable to parse version %q: %q is not an integer", input, val)
+ }
+ }
+ return ret, nil
+}
+
+func (version APIVersion) String() string {
+ var str string
+ for i, val := range version {
+ str += strconv.Itoa(val)
+ if i < len(version)-1 {
+ str += "."
+ }
+ }
+ return str
+}
+
+// LessThan is a function for comparing APIVersion structs
+func (version APIVersion) LessThan(other APIVersion) bool {
+ return version.compare(other) < 0
+}
+
+// LessThanOrEqualTo is a function for comparing APIVersion structs
+func (version APIVersion) LessThanOrEqualTo(other APIVersion) bool {
+ return version.compare(other) <= 0
+}
+
+// GreaterThan is a function for comparing APIVersion structs
+func (version APIVersion) GreaterThan(other APIVersion) bool {
+ return version.compare(other) > 0
+}
+
+// GreaterThanOrEqualTo is a function for comparing APIVersion structs
+func (version APIVersion) GreaterThanOrEqualTo(other APIVersion) bool {
+ return version.compare(other) >= 0
+}
+
+func (version APIVersion) compare(other APIVersion) int {
+ for i, v := range version {
+ if i <= len(other)-1 {
+ otherVersion := other[i]
+
+ if v < otherVersion {
+ return -1
+ } else if v > otherVersion {
+ return 1
+ }
+ }
+ }
+ if len(version) > len(other) {
+ return 1
+ }
+ if len(version) < len(other) {
+ return -1
+ }
+ return 0
+}
+
+// Client is the basic type of this package. It provides methods for
+// interaction with the API.
+type Client struct {
+ SkipServerVersionCheck bool
+ HTTPClient *http.Client
+ TLSConfig *tls.Config
+ Dialer Dialer
+
+ endpoint string
+ endpointURL *url.URL
+ eventMonitor *eventMonitoringState
+ requestedAPIVersion APIVersion
+ serverAPIVersion APIVersion
+ expectedAPIVersion APIVersion
+}
+
+// Dialer is an interface that allows network connections to be dialed
+// (net.Dialer fulfills this interface) and named pipes (a shim using
+// winio.DialPipe)
+type Dialer interface {
+ Dial(network, address string) (net.Conn, error)
+}
+
+// NewClient returns a Client instance ready for communication with the given
+// server endpoint. It will use the latest remote API version available in the
+// server.
+func NewClient(endpoint string) (*Client, error) {
+ client, err := NewVersionedClient(endpoint, "")
+ if err != nil {
+ return nil, err
+ }
+ client.SkipServerVersionCheck = true
+ return client, nil
+}
+
+// NewTLSClient returns a Client instance ready for TLS communications with the givens
+// server endpoint, key and certificates . It will use the latest remote API version
+// available in the server.
+func NewTLSClient(endpoint string, cert, key, ca string) (*Client, error) {
+ client, err := NewVersionedTLSClient(endpoint, cert, key, ca, "")
+ if err != nil {
+ return nil, err
+ }
+ client.SkipServerVersionCheck = true
+ return client, nil
+}
+
+// NewTLSClientFromBytes returns a Client instance ready for TLS communications with the givens
+// server endpoint, key and certificates (passed inline to the function as opposed to being
+// read from a local file). It will use the latest remote API version available in the server.
+func NewTLSClientFromBytes(endpoint string, certPEMBlock, keyPEMBlock, caPEMCert []byte) (*Client, error) {
+ client, err := NewVersionedTLSClientFromBytes(endpoint, certPEMBlock, keyPEMBlock, caPEMCert, "")
+ if err != nil {
+ return nil, err
+ }
+ client.SkipServerVersionCheck = true
+ return client, nil
+}
+
+// NewVersionedClient returns a Client instance ready for communication with
+// the given server endpoint, using a specific remote API version.
+func NewVersionedClient(endpoint string, apiVersionString string) (*Client, error) {
+ u, err := parseEndpoint(endpoint, false)
+ if err != nil {
+ return nil, err
+ }
+ var requestedAPIVersion APIVersion
+ if strings.Contains(apiVersionString, ".") {
+ requestedAPIVersion, err = NewAPIVersion(apiVersionString)
+ if err != nil {
+ return nil, err
+ }
+ }
+ c := &Client{
+ HTTPClient: defaultClient(),
+ Dialer: &net.Dialer{},
+ endpoint: endpoint,
+ endpointURL: u,
+ eventMonitor: new(eventMonitoringState),
+ requestedAPIVersion: requestedAPIVersion,
+ }
+ c.initializeNativeClient(defaultTransport)
+ return c, nil
+}
+
+// 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)
+}
+
+// NewVersionedTLSClient returns a Client instance ready for TLS communications with the givens
+// server endpoint, key and certificates, using a specific remote API version.
+func NewVersionedTLSClient(endpoint string, cert, key, ca, apiVersionString string) (*Client, error) {
+ var certPEMBlock []byte
+ var keyPEMBlock []byte
+ var caPEMCert []byte
+ if _, err := os.Stat(cert); !os.IsNotExist(err) {
+ certPEMBlock, err = ioutil.ReadFile(cert)
+ if err != nil {
+ return nil, err
+ }
+ }
+ if _, err := os.Stat(key); !os.IsNotExist(err) {
+ keyPEMBlock, err = ioutil.ReadFile(key)
+ if err != nil {
+ return nil, err
+ }
+ }
+ if _, err := os.Stat(ca); !os.IsNotExist(err) {
+ caPEMCert, err = ioutil.ReadFile(ca)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return NewVersionedTLSClientFromBytes(endpoint, certPEMBlock, keyPEMBlock, caPEMCert, apiVersionString)
+}
+
+// NewClientFromEnv returns a Client instance ready for communication created from
+// Docker's default logic for the environment variables DOCKER_HOST, DOCKER_TLS_VERIFY, and DOCKER_CERT_PATH.
+//
+// See https://github.com/docker/docker/blob/1f963af697e8df3a78217f6fdbf67b8123a7db94/docker/docker.go#L68.
+// See https://github.com/docker/compose/blob/81707ef1ad94403789166d2fe042c8a718a4c748/compose/cli/docker_client.py#L7.
+func NewClientFromEnv() (*Client, error) {
+ client, err := NewVersionedClientFromEnv("")
+ if err != nil {
+ return nil, err
+ }
+ client.SkipServerVersionCheck = true
+ return client, nil
+}
+
+// NewVersionedClientFromEnv returns a Client instance ready for TLS communications created from
+// Docker's default logic for the environment variables DOCKER_HOST, DOCKER_TLS_VERIFY, and DOCKER_CERT_PATH,
+// and using a specific remote API version.
+//
+// See https://github.com/docker/docker/blob/1f963af697e8df3a78217f6fdbf67b8123a7db94/docker/docker.go#L68.
+// See https://github.com/docker/compose/blob/81707ef1ad94403789166d2fe042c8a718a4c748/compose/cli/docker_client.py#L7.
+func NewVersionedClientFromEnv(apiVersionString string) (*Client, error) {
+ dockerEnv, err := getDockerEnv()
+ if err != nil {
+ return nil, err
+ }
+ dockerHost := dockerEnv.dockerHost
+ if dockerEnv.dockerTLSVerify {
+ parts := strings.SplitN(dockerEnv.dockerHost, "://", 2)
+ if len(parts) != 2 {
+ return nil, fmt.Errorf("could not split %s into two parts by ://", dockerHost)
+ }
+ cert := filepath.Join(dockerEnv.dockerCertPath, "cert.pem")
+ key := filepath.Join(dockerEnv.dockerCertPath, "key.pem")
+ ca := filepath.Join(dockerEnv.dockerCertPath, "ca.pem")
+ return NewVersionedTLSClient(dockerEnv.dockerHost, cert, key, ca, apiVersionString)
+ }
+ return NewVersionedClient(dockerEnv.dockerHost, apiVersionString)
+}
+
+// NewVersionedTLSClientFromBytes returns a Client instance ready for TLS communications with the givens
+// server endpoint, key and certificates (passed inline to the function as opposed to being
+// read from a local file), using a specific remote API version.
+func NewVersionedTLSClientFromBytes(endpoint string, certPEMBlock, keyPEMBlock, caPEMCert []byte, apiVersionString string) (*Client, error) {
+ u, err := parseEndpoint(endpoint, true)
+ if err != nil {
+ return nil, err
+ }
+ var requestedAPIVersion APIVersion
+ if strings.Contains(apiVersionString, ".") {
+ requestedAPIVersion, err = NewAPIVersion(apiVersionString)
+ if err != nil {
+ return nil, err
+ }
+ }
+ tlsConfig := &tls.Config{}
+ if certPEMBlock != nil && keyPEMBlock != nil {
+ tlsCert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock)
+ if err != nil {
+ return nil, err
+ }
+ tlsConfig.Certificates = []tls.Certificate{tlsCert}
+ }
+ if caPEMCert == nil {
+ tlsConfig.InsecureSkipVerify = true
+ } else {
+ caPool := x509.NewCertPool()
+ if !caPool.AppendCertsFromPEM(caPEMCert) {
+ return nil, errors.New("Could not add RootCA pem")
+ }
+ tlsConfig.RootCAs = caPool
+ }
+ tr := defaultTransport()
+ tr.TLSClientConfig = tlsConfig
+ if err != nil {
+ return nil, err
+ }
+ c := &Client{
+ HTTPClient: &http.Client{Transport: tr},
+ TLSConfig: tlsConfig,
+ Dialer: &net.Dialer{},
+ endpoint: endpoint,
+ endpointURL: u,
+ eventMonitor: new(eventMonitoringState),
+ requestedAPIVersion: requestedAPIVersion,
+ }
+ c.initializeNativeClient(defaultTransport)
+ return c, nil
+}
+
+// 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
+ }
+}
+
+func (c *Client) checkAPIVersion() error {
+ serverAPIVersionString, err := c.getServerAPIVersionString()
+ if err != nil {
+ return err
+ }
+ c.serverAPIVersion, err = NewAPIVersion(serverAPIVersionString)
+ if err != nil {
+ return err
+ }
+ if c.requestedAPIVersion == nil {
+ c.expectedAPIVersion = c.serverAPIVersion
+ } else {
+ c.expectedAPIVersion = c.requestedAPIVersion
+ }
+ return nil
+}
+
+// Endpoint returns the current endpoint. It's useful for getting the endpoint
+// when using functions that get this data from the environment (like
+// NewClientFromEnv.
+func (c *Client) Endpoint() string {
+ return c.endpoint
+}
+
+// Ping pings the docker server
+//
+// See https://goo.gl/wYfgY1 for more details.
+func (c *Client) Ping() error {
+ return c.PingWithContext(nil)
+}
+
+// PingWithContext pings the docker server
+// The context object can be used to cancel the ping request.
+//
+// See https://goo.gl/wYfgY1 for more details.
+func (c *Client) PingWithContext(ctx context.Context) error {
+ path := "/_ping"
+ resp, err := c.do("GET", path, doOptions{context: ctx})
+ if err != nil {
+ return err
+ }
+ if resp.StatusCode != http.StatusOK {
+ return newError(resp)
+ }
+ resp.Body.Close()
+ return nil
+}
+
+func (c *Client) getServerAPIVersionString() (version string, err error) {
+ resp, err := c.do("GET", "/version", doOptions{})
+ if err != nil {
+ return "", err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != http.StatusOK {
+ return "", fmt.Errorf("Received unexpected status %d while trying to retrieve the server version", resp.StatusCode)
+ }
+ var versionResponse map[string]interface{}
+ if err := json.NewDecoder(resp.Body).Decode(&versionResponse); err != nil {
+ return "", err
+ }
+ if version, ok := (versionResponse["ApiVersion"]).(string); ok {
+ return version, nil
+ }
+ return "", nil
+}
+
+type doOptions struct {
+ data interface{}
+ forceJSON bool
+ headers map[string]string
+ context context.Context
+}
+
+func (c *Client) do(method, path string, doOptions doOptions) (*http.Response, error) {
+ var params io.Reader
+ if doOptions.data != nil || doOptions.forceJSON {
+ buf, err := json.Marshal(doOptions.data)
+ if err != nil {
+ return nil, err
+ }
+ params = bytes.NewBuffer(buf)
+ }
+ if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil {
+ err := c.checkAPIVersion()
+ if err != nil {
+ return nil, err
+ }
+ }
+ protocol := c.endpointURL.Scheme
+ var u string
+ switch protocol {
+ case unixProtocol, namedPipeProtocol:
+ u = c.getFakeNativeURL(path)
+ default:
+ u = c.getURL(path)
+ }
+
+ req, err := http.NewRequest(method, u, params)
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("User-Agent", userAgent)
+ if doOptions.data != nil {
+ req.Header.Set("Content-Type", "application/json")
+ } else if method == "POST" {
+ req.Header.Set("Content-Type", "plain/text")
+ }
+
+ for k, v := range doOptions.headers {
+ req.Header.Set(k, v)
+ }
+
+ ctx := doOptions.context
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ resp, err := c.HTTPClient.Do(req.WithContext(ctx))
+ if err != nil {
+ if strings.Contains(err.Error(), "connection refused") {
+ return nil, ErrConnectionRefused
+ }
+
+ return nil, chooseError(ctx, err)
+ }
+ if resp.StatusCode < 200 || resp.StatusCode >= 400 {
+ return nil, newError(resp)
+ }
+ return resp, nil
+}
+
+type streamOptions struct {
+ setRawTerminal bool
+ rawJSONStream bool
+ useJSONDecoder bool
+ headers map[string]string
+ in io.Reader
+ stdout io.Writer
+ stderr io.Writer
+ reqSent chan struct{}
+ // timeout is the initial connection timeout
+ timeout time.Duration
+ // Timeout with no data is received, it's reset every time new data
+ // arrives
+ inactivityTimeout time.Duration
+ context context.Context
+}
+
+// if error in context, return that instead of generic http error
+func chooseError(ctx context.Context, err error) error {
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ default:
+ return err
+ }
+}
+
+func (c *Client) stream(method, path string, streamOptions streamOptions) error {
+ if (method == "POST" || method == "PUT") && streamOptions.in == nil {
+ streamOptions.in = bytes.NewReader(nil)
+ }
+ if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil {
+ err := c.checkAPIVersion()
+ if err != nil {
+ return err
+ }
+ }
+ req, err := http.NewRequest(method, c.getURL(path), streamOptions.in)
+ if err != nil {
+ return err
+ }
+ req.Header.Set("User-Agent", userAgent)
+ if method == "POST" {
+ req.Header.Set("Content-Type", "plain/text")
+ }
+ for key, val := range streamOptions.headers {
+ req.Header.Set(key, val)
+ }
+ var resp *http.Response
+ protocol := c.endpointURL.Scheme
+ address := c.endpointURL.Path
+ if streamOptions.stdout == nil {
+ streamOptions.stdout = ioutil.Discard
+ }
+ if streamOptions.stderr == nil {
+ streamOptions.stderr = ioutil.Discard
+ }
+
+ // make a sub-context so that our active cancellation does not affect parent
+ ctx := streamOptions.context
+ if ctx == nil {
+ ctx = context.Background()
+ }
+ subCtx, cancelRequest := context.WithCancel(ctx)
+ defer cancelRequest()
+
+ if protocol == unixProtocol || protocol == namedPipeProtocol {
+ var dial net.Conn
+ dial, err = c.Dialer.Dial(protocol, address)
+ if err != nil {
+ return err
+ }
+ go func() {
+ <-subCtx.Done()
+ dial.Close()
+ }()
+ breader := bufio.NewReader(dial)
+ err = req.Write(dial)
+ if err != nil {
+ return chooseError(subCtx, err)
+ }
+
+ // ReadResponse may hang if server does not replay
+ if streamOptions.timeout > 0 {
+ dial.SetDeadline(time.Now().Add(streamOptions.timeout))
+ }
+
+ if streamOptions.reqSent != nil {
+ close(streamOptions.reqSent)
+ }
+ if resp, err = http.ReadResponse(breader, req); err != nil {
+ // Cancel timeout for future I/O operations
+ if streamOptions.timeout > 0 {
+ dial.SetDeadline(time.Time{})
+ }
+ if strings.Contains(err.Error(), "connection refused") {
+ return ErrConnectionRefused
+ }
+
+ return chooseError(subCtx, err)
+ }
+ } else {
+ if resp, err = c.HTTPClient.Do(req.WithContext(subCtx)); err != nil {
+ if strings.Contains(err.Error(), "connection refused") {
+ return ErrConnectionRefused
+ }
+ return chooseError(subCtx, err)
+ }
+ if streamOptions.reqSent != nil {
+ close(streamOptions.reqSent)
+ }
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode < 200 || resp.StatusCode >= 400 {
+ return newError(resp)
+ }
+ var canceled uint32
+ if streamOptions.inactivityTimeout > 0 {
+ var ch chan<- struct{}
+ resp.Body, ch = handleInactivityTimeout(resp.Body, streamOptions.inactivityTimeout, cancelRequest, &canceled)
+ defer close(ch)
+ }
+ err = handleStreamResponse(resp, &streamOptions)
+ if err != nil {
+ if atomic.LoadUint32(&canceled) != 0 {
+ return ErrInactivityTimeout
+ }
+ return chooseError(subCtx, err)
+ }
+ return nil
+}
+
+func handleStreamResponse(resp *http.Response, streamOptions *streamOptions) error {
+ var err error
+ if !streamOptions.useJSONDecoder && resp.Header.Get("Content-Type") != "application/json" {
+ if streamOptions.setRawTerminal {
+ _, err = io.Copy(streamOptions.stdout, resp.Body)
+ } else {
+ _, err = stdcopy.StdCopy(streamOptions.stdout, streamOptions.stderr, resp.Body)
+ }
+ return err
+ }
+ // if we want to get raw json stream, just copy it back to output
+ // without decoding it
+ if streamOptions.rawJSONStream {
+ _, err = io.Copy(streamOptions.stdout, resp.Body)
+ return err
+ }
+ if st, ok := streamOptions.stdout.(interface {
+ io.Writer
+ FD() uintptr
+ IsTerminal() bool
+ }); ok {
+ err = jsonmessage.DisplayJSONMessagesToStream(resp.Body, st, nil)
+ } else {
+ err = jsonmessage.DisplayJSONMessagesStream(resp.Body, streamOptions.stdout, 0, false, nil)
+ }
+ return err
+}
+
+type proxyReader struct {
+ io.ReadCloser
+ calls uint64
+}
+
+func (p *proxyReader) callCount() uint64 {
+ return atomic.LoadUint64(&p.calls)
+}
+
+func (p *proxyReader) Read(data []byte) (int, error) {
+ atomic.AddUint64(&p.calls, 1)
+ return p.ReadCloser.Read(data)
+}
+
+func handleInactivityTimeout(reader io.ReadCloser, timeout time.Duration, cancelRequest func(), canceled *uint32) (io.ReadCloser, chan<- struct{}) {
+ done := make(chan struct{})
+ proxyReader := &proxyReader{ReadCloser: reader}
+ go func() {
+ var lastCallCount uint64
+ for {
+ select {
+ case <-time.After(timeout):
+ case <-done:
+ return
+ }
+ curCallCount := proxyReader.callCount()
+ if curCallCount == lastCallCount {
+ atomic.AddUint32(canceled, 1)
+ cancelRequest()
+ return
+ }
+ lastCallCount = curCallCount
+ }
+ }()
+ return proxyReader, done
+}
+
+type hijackOptions struct {
+ success chan struct{}
+ setRawTerminal bool
+ in io.Reader
+ stdout io.Writer
+ stderr io.Writer
+ data interface{}
+}
+
+// CloseWaiter is an interface with methods for closing the underlying resource
+// and then waiting for it to finish processing.
+type CloseWaiter interface {
+ io.Closer
+ Wait() error
+}
+
+type waiterFunc func() error
+
+func (w waiterFunc) Wait() error { return w() }
+
+type closerFunc func() error
+
+func (c closerFunc) Close() error { return c() }
+
+func (c *Client) hijack(method, path string, hijackOptions hijackOptions) (CloseWaiter, error) {
+ if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil {
+ err := c.checkAPIVersion()
+ if err != nil {
+ return nil, err
+ }
+ }
+ var params io.Reader
+ if hijackOptions.data != nil {
+ buf, err := json.Marshal(hijackOptions.data)
+ if err != nil {
+ return nil, err
+ }
+ params = bytes.NewBuffer(buf)
+ }
+ req, err := http.NewRequest(method, c.getURL(path), params)
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Connection", "Upgrade")
+ req.Header.Set("Upgrade", "tcp")
+ protocol := c.endpointURL.Scheme
+ address := c.endpointURL.Path
+ if protocol != unixProtocol && protocol != namedPipeProtocol {
+ protocol = "tcp"
+ address = c.endpointURL.Host
+ }
+ var dial net.Conn
+ if c.TLSConfig != nil && protocol != unixProtocol && protocol != namedPipeProtocol {
+ netDialer, ok := c.Dialer.(*net.Dialer)
+ if !ok {
+ return nil, ErrTLSNotSupported
+ }
+ dial, err = tlsDialWithDialer(netDialer, protocol, address, c.TLSConfig)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ dial, err = c.Dialer.Dial(protocol, address)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ errs := make(chan error, 1)
+ quit := make(chan struct{})
+ go func() {
+ clientconn := httputil.NewClientConn(dial, nil)
+ defer clientconn.Close()
+ clientconn.Do(req)
+ if hijackOptions.success != nil {
+ hijackOptions.success <- struct{}{}
+ <-hijackOptions.success
+ }
+ rwc, br := clientconn.Hijack()
+ defer rwc.Close()
+
+ errChanOut := make(chan error, 1)
+ errChanIn := make(chan error, 2)
+ if hijackOptions.stdout == nil && hijackOptions.stderr == nil {
+ close(errChanOut)
+ } else {
+ // Only copy if hijackOptions.stdout and/or hijackOptions.stderr is actually set.
+ // Otherwise, if the only stream you care about is stdin, your attach session
+ // will "hang" until the container terminates, even though you're not reading
+ // stdout/stderr
+ if hijackOptions.stdout == nil {
+ hijackOptions.stdout = ioutil.Discard
+ }
+ if hijackOptions.stderr == nil {
+ hijackOptions.stderr = ioutil.Discard
+ }
+
+ go func() {
+ defer func() {
+ if hijackOptions.in != nil {
+ if closer, ok := hijackOptions.in.(io.Closer); ok {
+ closer.Close()
+ }
+ errChanIn <- nil
+ }
+ }()
+
+ var err error
+ if hijackOptions.setRawTerminal {
+ _, err = io.Copy(hijackOptions.stdout, br)
+ } else {
+ _, err = stdcopy.StdCopy(hijackOptions.stdout, hijackOptions.stderr, br)
+ }
+ errChanOut <- err
+ }()
+ }
+
+ go func() {
+ var err error
+ if hijackOptions.in != nil {
+ _, err = io.Copy(rwc, hijackOptions.in)
+ }
+ errChanIn <- err
+ rwc.(interface {
+ CloseWrite() error
+ }).CloseWrite()
+ }()
+
+ var errIn error
+ select {
+ case errIn = <-errChanIn:
+ case <-quit:
+ }
+
+ var errOut error
+ select {
+ case errOut = <-errChanOut:
+ case <-quit:
+ }
+
+ if errIn != nil {
+ errs <- errIn
+ } else {
+ errs <- errOut
+ }
+ }()
+
+ return struct {
+ closerFunc
+ waiterFunc
+ }{
+ closerFunc(func() error { close(quit); return nil }),
+ waiterFunc(func() error { return <-errs }),
+ }, nil
+}
+
+func (c *Client) getURL(path string) string {
+ urlStr := strings.TrimRight(c.endpointURL.String(), "/")
+ if c.endpointURL.Scheme == unixProtocol || c.endpointURL.Scheme == namedPipeProtocol {
+ urlStr = ""
+ }
+ if c.requestedAPIVersion != nil {
+ return fmt.Sprintf("%s/v%s%s", urlStr, c.requestedAPIVersion, path)
+ }
+ return fmt.Sprintf("%s%s", urlStr, path)
+}
+
+// getFakeNativeURL returns the URL needed to make an HTTP request over a UNIX
+// domain socket to the given path.
+func (c *Client) getFakeNativeURL(path string) string {
+ u := *c.endpointURL // Copy.
+
+ // Override URL so that net/http will not complain.
+ u.Scheme = "http"
+ u.Host = "unix.sock" // Doesn't matter what this is - it's not used.
+ u.Path = ""
+ urlStr := strings.TrimRight(u.String(), "/")
+ if c.requestedAPIVersion != nil {
+ return fmt.Sprintf("%s/v%s%s", urlStr, c.requestedAPIVersion, path)
+ }
+ return fmt.Sprintf("%s%s", urlStr, path)
+}
+
+type jsonMessage struct {
+ Status string `json:"status,omitempty"`
+ Progress string `json:"progress,omitempty"`
+ Error string `json:"error,omitempty"`
+ Stream string `json:"stream,omitempty"`
+}
+
+func queryString(opts interface{}) string {
+ if opts == nil {
+ return ""
+ }
+ value := reflect.ValueOf(opts)
+ if value.Kind() == reflect.Ptr {
+ value = value.Elem()
+ }
+ if value.Kind() != reflect.Struct {
+ return ""
+ }
+ items := url.Values(map[string][]string{})
+ for i := 0; i < value.NumField(); i++ {
+ field := value.Type().Field(i)
+ if field.PkgPath != "" {
+ continue
+ }
+ key := field.Tag.Get("qs")
+ if key == "" {
+ key = strings.ToLower(field.Name)
+ } else if key == "-" {
+ continue
+ }
+ addQueryStringValue(items, key, value.Field(i))
+ }
+ return items.Encode()
+}
+
+func addQueryStringValue(items url.Values, key string, v reflect.Value) {
+ switch v.Kind() {
+ case reflect.Bool:
+ if v.Bool() {
+ items.Add(key, "1")
+ }
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ if v.Int() > 0 {
+ items.Add(key, strconv.FormatInt(v.Int(), 10))
+ }
+ case reflect.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))
+ }
+ case reflect.String:
+ if v.String() != "" {
+ items.Add(key, v.String())
+ }
+ case reflect.Ptr:
+ if !v.IsNil() {
+ if b, err := json.Marshal(v.Interface()); err == nil {
+ items.Add(key, string(b))
+ }
+ }
+ case reflect.Map:
+ if len(v.MapKeys()) > 0 {
+ if b, err := json.Marshal(v.Interface()); err == nil {
+ items.Add(key, string(b))
+ }
+ }
+ case reflect.Array, reflect.Slice:
+ vLen := v.Len()
+ if vLen > 0 {
+ for i := 0; i < vLen; i++ {
+ addQueryStringValue(items, key, v.Index(i))
+ }
+ }
+ }
+}
+
+// Error represents failures in the API. It represents a failure from the API.
+type Error struct {
+ Status int
+ Message string
+}
+
+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)}
+ }
+ 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 {
+ return fmt.Sprintf("API error (%d): %s", e.Status, e.Message)
+}
+
+func parseEndpoint(endpoint string, tls bool) (*url.URL, error) {
+ if endpoint != "" && !strings.Contains(endpoint, "://") {
+ endpoint = "tcp://" + endpoint
+ }
+ u, err := url.Parse(endpoint)
+ if err != nil {
+ return nil, ErrInvalidEndpoint
+ }
+ if tls && u.Scheme != "unix" {
+ u.Scheme = "https"
+ }
+ switch u.Scheme {
+ case unixProtocol, namedPipeProtocol:
+ return u, nil
+ case "http", "https", "tcp":
+ _, port, err := net.SplitHostPort(u.Host)
+ if err != nil {
+ if e, ok := err.(*net.AddrError); ok {
+ if e.Err == "missing port in address" {
+ return u, nil
+ }
+ }
+ return nil, ErrInvalidEndpoint
+ }
+ number, err := strconv.ParseInt(port, 10, 64)
+ if err == nil && number > 0 && number < 65536 {
+ if u.Scheme == "tcp" {
+ if tls {
+ u.Scheme = "https"
+ } else {
+ u.Scheme = "http"
+ }
+ }
+ return u, nil
+ }
+ return nil, ErrInvalidEndpoint
+ default:
+ return nil, ErrInvalidEndpoint
+ }
+}
+
+type dockerEnv struct {
+ dockerHost string
+ dockerTLSVerify bool
+ dockerCertPath string
+}
+
+func getDockerEnv() (*dockerEnv, error) {
+ dockerHost := os.Getenv("DOCKER_HOST")
+ var err error
+ if dockerHost == "" {
+ dockerHost = opts.DefaultHost
+ }
+ dockerTLSVerify := os.Getenv("DOCKER_TLS_VERIFY") != ""
+ var dockerCertPath string
+ if dockerTLSVerify {
+ dockerCertPath = os.Getenv("DOCKER_CERT_PATH")
+ if dockerCertPath == "" {
+ home := homedir.Get()
+ if home == "" {
+ return nil, errors.New("environment variable HOME must be set if DOCKER_CERT_PATH is not set")
+ }
+ dockerCertPath = filepath.Join(home, ".docker")
+ dockerCertPath, err = filepath.Abs(dockerCertPath)
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+ return &dockerEnv{
+ dockerHost: dockerHost,
+ dockerTLSVerify: dockerTLSVerify,
+ dockerCertPath: dockerCertPath,
+ }, nil
+}
+
+// defaultTransport returns a new http.Transport with similar default values to
+// http.DefaultTransport, but with idle connections and keepalives disabled.
+func defaultTransport() *http.Transport {
+ transport := defaultPooledTransport()
+ transport.DisableKeepAlives = true
+ transport.MaxIdleConnsPerHost = -1
+ return transport
+}
+
+// defaultPooledTransport returns a new http.Transport with similar default
+// values to http.DefaultTransport. Do not use this for transient transports as
+// it can leak file descriptors over time. Only use this for transports that
+// will be re-used for the same host(s).
+func defaultPooledTransport() *http.Transport {
+ transport := &http.Transport{
+ Proxy: http.ProxyFromEnvironment,
+ DialContext: (&net.Dialer{
+ Timeout: 30 * time.Second,
+ KeepAlive: 30 * time.Second,
+ }).DialContext,
+ MaxIdleConns: 100,
+ IdleConnTimeout: 90 * time.Second,
+ TLSHandshakeTimeout: 10 * time.Second,
+ ExpectContinueTimeout: 1 * time.Second,
+ MaxIdleConnsPerHost: runtime.GOMAXPROCS(0) + 1,
+ }
+ return transport
+}
+
+// defaultClient returns a new http.Client with similar default values to
+// http.Client, but with a non-shared Transport, idle connections disabled, and
+// keepalives disabled.
+func defaultClient() *http.Client {
+ return &http.Client{
+ Transport: defaultTransport(),
+ }
+}
diff --git a/vendor/github.com/fsouza/go-dockerclient/client_unix.go b/vendor/github.com/fsouza/go-dockerclient/client_unix.go
new file mode 100644
index 000000000..57d7904ea
--- /dev/null
+++ b/vendor/github.com/fsouza/go-dockerclient/client_unix.go
@@ -0,0 +1,32 @@
+// 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 (
+ "context"
+ "net"
+ "net/http"
+)
+
+// initializeNativeClient initializes the native Unix domain socket client on
+// Unix-style operating systems
+func (c *Client) initializeNativeClient(trFunc func() *http.Transport) {
+ if c.endpointURL.Scheme != unixProtocol {
+ return
+ }
+ sockPath := c.endpointURL.Path
+
+ tr := trFunc()
+
+ tr.Dial = func(network, addr string) (net.Conn, error) {
+ return c.Dialer.Dial(unixProtocol, sockPath)
+ }
+ tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
+ return c.Dialer.Dial(unixProtocol, sockPath)
+ }
+ 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
new file mode 100644
index 000000000..8e7b457d7
--- /dev/null
+++ b/vendor/github.com/fsouza/go-dockerclient/client_windows.go
@@ -0,0 +1,45 @@
+// 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 (
+ "context"
+ "net"
+ "net/http"
+ "time"
+
+ "github.com/Microsoft/go-winio"
+)
+
+const namedPipeConnectTimeout = 2 * time.Second
+
+type pipeDialer struct {
+ dialFunc func(network, addr string) (net.Conn, error)
+}
+
+func (p pipeDialer) Dial(network, address string) (net.Conn, error) {
+ return p.dialFunc(network, address)
+}
+
+// initializeNativeClient initializes the native Named Pipe client for Windows
+func (c *Client) initializeNativeClient(trFunc func() *http.Transport) {
+ if c.endpointURL.Scheme != namedPipeProtocol {
+ return
+ }
+ namedPipePath := c.endpointURL.Path
+ dialFunc := func(network, addr string) (net.Conn, error) {
+ timeout := namedPipeConnectTimeout
+ return winio.DialPipe(namedPipePath, &timeout)
+ }
+ 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.HTTPClient.Transport = tr
+}
diff --git a/vendor/github.com/fsouza/go-dockerclient/container.go b/vendor/github.com/fsouza/go-dockerclient/container.go
new file mode 100644
index 000000000..e24c9fb2e
--- /dev/null
+++ b/vendor/github.com/fsouza/go-dockerclient/container.go
@@ -0,0 +1,1623 @@
+// Copyright 2013 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"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/docker/go-units"
+)
+
+// ErrContainerAlreadyExists is the error returned by CreateContainer when the
+// container already exists.
+var ErrContainerAlreadyExists = errors.New("container already exists")
+
+// ListContainersOptions specify parameters to the ListContainers function.
+//
+// See https://goo.gl/kaOHGw for more details.
+type ListContainersOptions struct {
+ All bool
+ Size bool
+ Limit int
+ Since string
+ Before string
+ Filters map[string][]string
+ Context context.Context
+}
+
+// APIPort is a type that represents a port mapping returned by the Docker API
+type APIPort struct {
+ PrivatePort int64 `json:"PrivatePort,omitempty" yaml:"PrivatePort,omitempty" toml:"PrivatePort,omitempty"`
+ PublicPort int64 `json:"PublicPort,omitempty" yaml:"PublicPort,omitempty" toml:"PublicPort,omitempty"`
+ Type string `json:"Type,omitempty" yaml:"Type,omitempty" toml:"Type,omitempty"`
+ IP string `json:"IP,omitempty" yaml:"IP,omitempty" toml:"IP,omitempty"`
+}
+
+// APIMount represents a mount point for a container.
+type APIMount struct {
+ Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"`
+ Source string `json:"Source,omitempty" yaml:"Source,omitempty" toml:"Source,omitempty"`
+ Destination string `json:"Destination,omitempty" yaml:"Destination,omitempty" toml:"Destination,omitempty"`
+ Driver string `json:"Driver,omitempty" yaml:"Driver,omitempty" toml:"Driver,omitempty"`
+ Mode string `json:"Mode,omitempty" yaml:"Mode,omitempty" toml:"Mode,omitempty"`
+ RW bool `json:"RW,omitempty" yaml:"RW,omitempty" toml:"RW,omitempty"`
+ Propogation string `json:"Propogation,omitempty" yaml:"Propogation,omitempty" toml:"Propogation,omitempty"`
+}
+
+// APIContainers represents each container in the list returned by
+// ListContainers.
+type APIContainers struct {
+ ID string `json:"Id" yaml:"Id" toml:"Id"`
+ Image string `json:"Image,omitempty" yaml:"Image,omitempty" toml:"Image,omitempty"`
+ Command string `json:"Command,omitempty" yaml:"Command,omitempty" toml:"Command,omitempty"`
+ Created int64 `json:"Created,omitempty" yaml:"Created,omitempty" toml:"Created,omitempty"`
+ State string `json:"State,omitempty" yaml:"State,omitempty" toml:"State,omitempty"`
+ Status string `json:"Status,omitempty" yaml:"Status,omitempty" toml:"Status,omitempty"`
+ Ports []APIPort `json:"Ports,omitempty" yaml:"Ports,omitempty" toml:"Ports,omitempty"`
+ SizeRw int64 `json:"SizeRw,omitempty" yaml:"SizeRw,omitempty" toml:"SizeRw,omitempty"`
+ SizeRootFs int64 `json:"SizeRootFs,omitempty" yaml:"SizeRootFs,omitempty" toml:"SizeRootFs,omitempty"`
+ Names []string `json:"Names,omitempty" yaml:"Names,omitempty" toml:"Names,omitempty"`
+ Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty" toml:"Labels,omitempty"`
+ Networks NetworkList `json:"NetworkSettings,omitempty" yaml:"NetworkSettings,omitempty" toml:"NetworkSettings,omitempty"`
+ Mounts []APIMount `json:"Mounts,omitempty" yaml:"Mounts,omitempty" toml:"Mounts,omitempty"`
+}
+
+// NetworkList encapsulates a map of networks, as returned by the Docker API in
+// ListContainers.
+type NetworkList struct {
+ Networks map[string]ContainerNetwork `json:"Networks" yaml:"Networks,omitempty" toml:"Networks,omitempty"`
+}
+
+// ListContainers returns a slice of containers matching the given criteria.
+//
+// See https://goo.gl/kaOHGw for more details.
+func (c *Client) ListContainers(opts ListContainersOptions) ([]APIContainers, error) {
+ path := "/containers/json?" + queryString(opts)
+ resp, err := c.do("GET", path, doOptions{context: opts.Context})
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ var containers []APIContainers
+ if err := json.NewDecoder(resp.Body).Decode(&containers); err != nil {
+ return nil, err
+ }
+ return containers, nil
+}
+
+// Port represents the port number and the protocol, in the form
+// <number>/<protocol>. For example: 80/tcp.
+type Port string
+
+// Port returns the number of the port.
+func (p Port) Port() string {
+ return strings.Split(string(p), "/")[0]
+}
+
+// Proto returns the name of the protocol.
+func (p Port) Proto() string {
+ parts := strings.Split(string(p), "/")
+ if len(parts) == 1 {
+ return "tcp"
+ }
+ return parts[1]
+}
+
+// HealthCheck represents one check of health.
+type HealthCheck struct {
+ Start time.Time `json:"Start,omitempty" yaml:"Start,omitempty" toml:"Start,omitempty"`
+ End time.Time `json:"End,omitempty" yaml:"End,omitempty" toml:"End,omitempty"`
+ ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty" toml:"ExitCode,omitempty"`
+ Output string `json:"Output,omitempty" yaml:"Output,omitempty" toml:"Output,omitempty"`
+}
+
+// Health represents the health of a container.
+type Health struct {
+ Status string `json:"Status,omitempty" yaml:"Status,omitempty" toml:"Status,omitempty"`
+ FailingStreak int `json:"FailingStreak,omitempty" yaml:"FailingStreak,omitempty" toml:"FailingStreak,omitempty"`
+ Log []HealthCheck `json:"Log,omitempty" yaml:"Log,omitempty" toml:"Log,omitempty"`
+}
+
+// State represents the state of a container.
+type State struct {
+ Status string `json:"Status,omitempty" yaml:"Status,omitempty" toml:"Status,omitempty"`
+ Running bool `json:"Running,omitempty" yaml:"Running,omitempty" toml:"Running,omitempty"`
+ Paused bool `json:"Paused,omitempty" yaml:"Paused,omitempty" toml:"Paused,omitempty"`
+ Restarting bool `json:"Restarting,omitempty" yaml:"Restarting,omitempty" toml:"Restarting,omitempty"`
+ OOMKilled bool `json:"OOMKilled,omitempty" yaml:"OOMKilled,omitempty" toml:"OOMKilled,omitempty"`
+ RemovalInProgress bool `json:"RemovalInProgress,omitempty" yaml:"RemovalInProgress,omitempty" toml:"RemovalInProgress,omitempty"`
+ Dead bool `json:"Dead,omitempty" yaml:"Dead,omitempty" toml:"Dead,omitempty"`
+ Pid int `json:"Pid,omitempty" yaml:"Pid,omitempty" toml:"Pid,omitempty"`
+ ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty" toml:"ExitCode,omitempty"`
+ Error string `json:"Error,omitempty" yaml:"Error,omitempty" toml:"Error,omitempty"`
+ StartedAt time.Time `json:"StartedAt,omitempty" yaml:"StartedAt,omitempty" toml:"StartedAt,omitempty"`
+ FinishedAt time.Time `json:"FinishedAt,omitempty" yaml:"FinishedAt,omitempty" toml:"FinishedAt,omitempty"`
+ Health Health `json:"Health,omitempty" yaml:"Health,omitempty" toml:"Health,omitempty"`
+}
+
+// String returns a human-readable description of the state
+func (s *State) String() string {
+ if s.Running {
+ if s.Paused {
+ return fmt.Sprintf("Up %s (Paused)", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)))
+ }
+ if s.Restarting {
+ return fmt.Sprintf("Restarting (%d) %s ago", s.ExitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt)))
+ }
+
+ return fmt.Sprintf("Up %s", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)))
+ }
+
+ if s.RemovalInProgress {
+ return "Removal In Progress"
+ }
+
+ if s.Dead {
+ return "Dead"
+ }
+
+ if s.StartedAt.IsZero() {
+ return "Created"
+ }
+
+ if s.FinishedAt.IsZero() {
+ return ""
+ }
+
+ return fmt.Sprintf("Exited (%d) %s ago", s.ExitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt)))
+}
+
+// StateString returns a single string to describe state
+func (s *State) StateString() string {
+ if s.Running {
+ if s.Paused {
+ return "paused"
+ }
+ if s.Restarting {
+ return "restarting"
+ }
+ return "running"
+ }
+
+ if s.Dead {
+ return "dead"
+ }
+
+ if s.StartedAt.IsZero() {
+ return "created"
+ }
+
+ return "exited"
+}
+
+// PortBinding represents the host/container port mapping as returned in the
+// `docker inspect` json
+type PortBinding struct {
+ HostIP string `json:"HostIp,omitempty" yaml:"HostIp,omitempty" toml:"HostIp,omitempty"`
+ HostPort string `json:"HostPort,omitempty" yaml:"HostPort,omitempty" toml:"HostPort,omitempty"`
+}
+
+// PortMapping represents a deprecated field in the `docker inspect` output,
+// and its value as found in NetworkSettings should always be nil
+type PortMapping map[string]string
+
+// ContainerNetwork represents the networking settings of a container per network.
+type ContainerNetwork struct {
+ Aliases []string `json:"Aliases,omitempty" yaml:"Aliases,omitempty" toml:"Aliases,omitempty"`
+ MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty" toml:"MacAddress,omitempty"`
+ GlobalIPv6PrefixLen int `json:"GlobalIPv6PrefixLen,omitempty" yaml:"GlobalIPv6PrefixLen,omitempty" toml:"GlobalIPv6PrefixLen,omitempty"`
+ GlobalIPv6Address string `json:"GlobalIPv6Address,omitempty" yaml:"GlobalIPv6Address,omitempty" toml:"GlobalIPv6Address,omitempty"`
+ IPv6Gateway string `json:"IPv6Gateway,omitempty" yaml:"IPv6Gateway,omitempty" toml:"IPv6Gateway,omitempty"`
+ IPPrefixLen int `json:"IPPrefixLen,omitempty" yaml:"IPPrefixLen,omitempty" toml:"IPPrefixLen,omitempty"`
+ IPAddress string `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty" toml:"IPAddress,omitempty"`
+ Gateway string `json:"Gateway,omitempty" yaml:"Gateway,omitempty" toml:"Gateway,omitempty"`
+ EndpointID string `json:"EndpointID,omitempty" yaml:"EndpointID,omitempty" toml:"EndpointID,omitempty"`
+ NetworkID string `json:"NetworkID,omitempty" yaml:"NetworkID,omitempty" toml:"NetworkID,omitempty"`
+}
+
+// NetworkSettings contains network-related information about a container
+type NetworkSettings struct {
+ Networks map[string]ContainerNetwork `json:"Networks,omitempty" yaml:"Networks,omitempty" toml:"Networks,omitempty"`
+ IPAddress string `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty" toml:"IPAddress,omitempty"`
+ IPPrefixLen int `json:"IPPrefixLen,omitempty" yaml:"IPPrefixLen,omitempty" toml:"IPPrefixLen,omitempty"`
+ MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty" toml:"MacAddress,omitempty"`
+ Gateway string `json:"Gateway,omitempty" yaml:"Gateway,omitempty" toml:"Gateway,omitempty"`
+ Bridge string `json:"Bridge,omitempty" yaml:"Bridge,omitempty" toml:"Bridge,omitempty"`
+ PortMapping map[string]PortMapping `json:"PortMapping,omitempty" yaml:"PortMapping,omitempty" toml:"PortMapping,omitempty"`
+ Ports map[Port][]PortBinding `json:"Ports,omitempty" yaml:"Ports,omitempty" toml:"Ports,omitempty"`
+ NetworkID string `json:"NetworkID,omitempty" yaml:"NetworkID,omitempty" toml:"NetworkID,omitempty"`
+ EndpointID string `json:"EndpointID,omitempty" yaml:"EndpointID,omitempty" toml:"EndpointID,omitempty"`
+ SandboxKey string `json:"SandboxKey,omitempty" yaml:"SandboxKey,omitempty" toml:"SandboxKey,omitempty"`
+ GlobalIPv6Address string `json:"GlobalIPv6Address,omitempty" yaml:"GlobalIPv6Address,omitempty" toml:"GlobalIPv6Address,omitempty"`
+ GlobalIPv6PrefixLen int `json:"GlobalIPv6PrefixLen,omitempty" yaml:"GlobalIPv6PrefixLen,omitempty" toml:"GlobalIPv6PrefixLen,omitempty"`
+ IPv6Gateway string `json:"IPv6Gateway,omitempty" yaml:"IPv6Gateway,omitempty" toml:"IPv6Gateway,omitempty"`
+ LinkLocalIPv6Address string `json:"LinkLocalIPv6Address,omitempty" yaml:"LinkLocalIPv6Address,omitempty" toml:"LinkLocalIPv6Address,omitempty"`
+ LinkLocalIPv6PrefixLen int `json:"LinkLocalIPv6PrefixLen,omitempty" yaml:"LinkLocalIPv6PrefixLen,omitempty" toml:"LinkLocalIPv6PrefixLen,omitempty"`
+ SecondaryIPAddresses []string `json:"SecondaryIPAddresses,omitempty" yaml:"SecondaryIPAddresses,omitempty" toml:"SecondaryIPAddresses,omitempty"`
+ SecondaryIPv6Addresses []string `json:"SecondaryIPv6Addresses,omitempty" yaml:"SecondaryIPv6Addresses,omitempty" toml:"SecondaryIPv6Addresses,omitempty"`
+}
+
+// PortMappingAPI translates the port mappings as contained in NetworkSettings
+// into the format in which they would appear when returned by the API
+func (settings *NetworkSettings) PortMappingAPI() []APIPort {
+ var mapping []APIPort
+ for port, bindings := range settings.Ports {
+ p, _ := parsePort(port.Port())
+ if len(bindings) == 0 {
+ mapping = append(mapping, APIPort{
+ PrivatePort: int64(p),
+ Type: port.Proto(),
+ })
+ continue
+ }
+ for _, binding := range bindings {
+ p, _ := parsePort(port.Port())
+ h, _ := parsePort(binding.HostPort)
+ mapping = append(mapping, APIPort{
+ PrivatePort: int64(p),
+ PublicPort: int64(h),
+ Type: port.Proto(),
+ IP: binding.HostIP,
+ })
+ }
+ }
+ return mapping
+}
+
+func parsePort(rawPort string) (int, error) {
+ port, err := strconv.ParseUint(rawPort, 10, 16)
+ if err != nil {
+ return 0, err
+ }
+ return int(port), nil
+}
+
+// Config is the list of configuration options used when creating a container.
+// Config does not contain the options that are specific to starting a container on a
+// given host. Those are contained in HostConfig
+type Config struct {
+ Hostname string `json:"Hostname,omitempty" yaml:"Hostname,omitempty" toml:"Hostname,omitempty"`
+ Domainname string `json:"Domainname,omitempty" yaml:"Domainname,omitempty" toml:"Domainname,omitempty"`
+ User string `json:"User,omitempty" yaml:"User,omitempty" toml:"User,omitempty"`
+ Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty" toml:"Memory,omitempty"`
+ MemorySwap int64 `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty" toml:"MemorySwap,omitempty"`
+ MemoryReservation int64 `json:"MemoryReservation,omitempty" yaml:"MemoryReservation,omitempty" toml:"MemoryReservation,omitempty"`
+ KernelMemory int64 `json:"KernelMemory,omitempty" yaml:"KernelMemory,omitempty" toml:"KernelMemory,omitempty"`
+ CPUShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty" toml:"CpuShares,omitempty"`
+ CPUSet string `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty" toml:"Cpuset,omitempty"`
+ PortSpecs []string `json:"PortSpecs,omitempty" yaml:"PortSpecs,omitempty" toml:"PortSpecs,omitempty"`
+ 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"`
+ Volumes map[string]struct{} `json:"Volumes,omitempty" yaml:"Volumes,omitempty" toml:"Volumes,omitempty"`
+ VolumeDriver string `json:"VolumeDriver,omitempty" yaml:"VolumeDriver,omitempty" toml:"VolumeDriver,omitempty"`
+ WorkingDir string `json:"WorkingDir,omitempty" yaml:"WorkingDir,omitempty" toml:"WorkingDir,omitempty"`
+ MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty" toml:"MacAddress,omitempty"`
+ Entrypoint []string `json:"Entrypoint" yaml:"Entrypoint" toml:"Entrypoint"`
+ SecurityOpts []string `json:"SecurityOpts,omitempty" yaml:"SecurityOpts,omitempty" toml:"SecurityOpts,omitempty"`
+ OnBuild []string `json:"OnBuild,omitempty" yaml:"OnBuild,omitempty" toml:"OnBuild,omitempty"`
+ Mounts []Mount `json:"Mounts,omitempty" yaml:"Mounts,omitempty" toml:"Mounts,omitempty"`
+ Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty" toml:"Labels,omitempty"`
+ AttachStdin bool `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty" toml:"AttachStdin,omitempty"`
+ AttachStdout bool `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty" toml:"AttachStdout,omitempty"`
+ AttachStderr bool `json:"AttachStderr,omitempty" yaml:"AttachStderr,omitempty" toml:"AttachStderr,omitempty"`
+ ArgsEscaped bool `json:"ArgsEscaped,omitempty" yaml:"ArgsEscaped,omitempty" toml:"ArgsEscaped,omitempty"`
+ Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty" toml:"Tty,omitempty"`
+ OpenStdin bool `json:"OpenStdin,omitempty" yaml:"OpenStdin,omitempty" toml:"OpenStdin,omitempty"`
+ StdinOnce bool `json:"StdinOnce,omitempty" yaml:"StdinOnce,omitempty" toml:"StdinOnce,omitempty"`
+ NetworkDisabled bool `json:"NetworkDisabled,omitempty" yaml:"NetworkDisabled,omitempty" toml:"NetworkDisabled,omitempty"`
+
+ // This is no longer used and has been kept here for backward
+ // compatibility, please use HostConfig.VolumesFrom.
+ VolumesFrom string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty" toml:"VolumesFrom,omitempty"`
+}
+
+// HostMount represents a mount point in the container in HostConfig.
+//
+// It has been added in the version 1.25 of the Docker API
+type HostMount struct {
+ Target string `json:"Target,omitempty" yaml:"Target,omitempty" toml:"Target,omitempty"`
+ Source string `json:"Source,omitempty" yaml:"Source,omitempty" toml:"Source,omitempty"`
+ Type string `json:"Type,omitempty" yaml:"Type,omitempty" toml:"Type,omitempty"`
+ ReadOnly bool `json:"ReadOnly,omitempty" yaml:"ReadOnly,omitempty" toml:"ReadOnly,omitempty"`
+ BindOptions *BindOptions `json:"BindOptions,omitempty" yaml:"BindOptions,omitempty" toml:"BindOptions,omitempty"`
+ VolumeOptions *VolumeOptions `json:"VolumeOptions,omitempty" yaml:"VolumeOptions,omitempty" toml:"VolumeOptions,omitempty"`
+ TempfsOptions *TempfsOptions `json:"TempfsOptions,omitempty" yaml:"TempfsOptions,omitempty" toml:"TempfsOptions,omitempty"`
+}
+
+// BindOptions contains optional configuration for the bind type
+type BindOptions struct {
+ Propagation string `json:"Propagation,omitempty" yaml:"Propagation,omitempty" toml:"Propagation,omitempty"`
+}
+
+// VolumeOptions contains optional configuration for the volume type
+type VolumeOptions struct {
+ NoCopy bool `json:"NoCopy,omitempty" yaml:"NoCopy,omitempty" toml:"NoCopy,omitempty"`
+ Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty" toml:"Labels,omitempty"`
+ DriverConfig VolumeDriverConfig `json:"DriverConfig,omitempty" yaml:"DriverConfig,omitempty" toml:"DriverConfig,omitempty"`
+}
+
+// TempfsOptions contains optional configuration for the tempfs type
+type TempfsOptions struct {
+ SizeBytes int64 `json:"SizeBytes,omitempty" yaml:"SizeBytes,omitempty" toml:"SizeBytes,omitempty"`
+ Mode int `json:"Mode,omitempty" yaml:"Mode,omitempty" toml:"Mode,omitempty"`
+}
+
+// VolumeDriverConfig holds a map of volume driver specific options
+type VolumeDriverConfig struct {
+ Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"`
+ Options map[string]string `json:"Options,omitempty" yaml:"Options,omitempty" toml:"Options,omitempty"`
+}
+
+// Mount represents a mount point in the container.
+//
+// It has been added in the version 1.20 of the Docker API, available since
+// Docker 1.8.
+type Mount struct {
+ Name string
+ Source string
+ Destination string
+ Driver string
+ Mode string
+ RW bool
+}
+
+// LogConfig defines the log driver type and the configuration for it.
+type LogConfig struct {
+ Type string `json:"Type,omitempty" yaml:"Type,omitempty" toml:"Type,omitempty"`
+ Config map[string]string `json:"Config,omitempty" yaml:"Config,omitempty" toml:"Config,omitempty"`
+}
+
+// ULimit defines system-wide resource limitations This can help a lot in
+// system administration, e.g. when a user starts too many processes and
+// therefore makes the system unresponsive for other users.
+type ULimit struct {
+ Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"`
+ Soft int64 `json:"Soft,omitempty" yaml:"Soft,omitempty" toml:"Soft,omitempty"`
+ Hard int64 `json:"Hard,omitempty" yaml:"Hard,omitempty" toml:"Hard,omitempty"`
+}
+
+// SwarmNode containers information about which Swarm node the container is on.
+type SwarmNode struct {
+ ID string `json:"ID,omitempty" yaml:"ID,omitempty" toml:"ID,omitempty"`
+ IP string `json:"IP,omitempty" yaml:"IP,omitempty" toml:"IP,omitempty"`
+ Addr string `json:"Addr,omitempty" yaml:"Addr,omitempty" toml:"Addr,omitempty"`
+ Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"`
+ CPUs int64 `json:"CPUs,omitempty" yaml:"CPUs,omitempty" toml:"CPUs,omitempty"`
+ Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty" toml:"Memory,omitempty"`
+ Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty" toml:"Labels,omitempty"`
+}
+
+// GraphDriver contains information about the GraphDriver used by the
+// container.
+type GraphDriver struct {
+ Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"`
+ Data map[string]string `json:"Data,omitempty" yaml:"Data,omitempty" toml:"Data,omitempty"`
+}
+
+// HealthConfig holds configuration settings for the HEALTHCHECK feature
+//
+// It has been added in the version 1.24 of the Docker API, available since
+// Docker 1.12.
+type HealthConfig struct {
+ // Test is the test to perform to check that the container is healthy.
+ // An empty slice means to inherit the default.
+ // The options are:
+ // {} : inherit healthcheck
+ // {"NONE"} : disable healthcheck
+ // {"CMD", args...} : exec arguments directly
+ // {"CMD-SHELL", command} : run command with system's default shell
+ 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.
+ 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.
+ Retries int `json:"Retries,omitempty" yaml:"Retries,omitempty" toml:"Retries,omitempty"`
+}
+
+// Container is the type encompasing everything about a container - its config,
+// hostconfig, etc.
+type Container struct {
+ ID string `json:"Id" yaml:"Id" toml:"Id"`
+
+ Created time.Time `json:"Created,omitempty" yaml:"Created,omitempty" toml:"Created,omitempty"`
+
+ Path string `json:"Path,omitempty" yaml:"Path,omitempty" toml:"Path,omitempty"`
+ Args []string `json:"Args,omitempty" yaml:"Args,omitempty" toml:"Args,omitempty"`
+
+ Config *Config `json:"Config,omitempty" yaml:"Config,omitempty" toml:"Config,omitempty"`
+ State State `json:"State,omitempty" yaml:"State,omitempty" toml:"State,omitempty"`
+ Image string `json:"Image,omitempty" yaml:"Image,omitempty" toml:"Image,omitempty"`
+
+ Node *SwarmNode `json:"Node,omitempty" yaml:"Node,omitempty" toml:"Node,omitempty"`
+
+ NetworkSettings *NetworkSettings `json:"NetworkSettings,omitempty" yaml:"NetworkSettings,omitempty" toml:"NetworkSettings,omitempty"`
+
+ SysInitPath string `json:"SysInitPath,omitempty" yaml:"SysInitPath,omitempty" toml:"SysInitPath,omitempty"`
+ ResolvConfPath string `json:"ResolvConfPath,omitempty" yaml:"ResolvConfPath,omitempty" toml:"ResolvConfPath,omitempty"`
+ HostnamePath string `json:"HostnamePath,omitempty" yaml:"HostnamePath,omitempty" toml:"HostnamePath,omitempty"`
+ HostsPath string `json:"HostsPath,omitempty" yaml:"HostsPath,omitempty" toml:"HostsPath,omitempty"`
+ LogPath string `json:"LogPath,omitempty" yaml:"LogPath,omitempty" toml:"LogPath,omitempty"`
+ Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"`
+ Driver string `json:"Driver,omitempty" yaml:"Driver,omitempty" toml:"Driver,omitempty"`
+ Mounts []Mount `json:"Mounts,omitempty" yaml:"Mounts,omitempty" toml:"Mounts,omitempty"`
+
+ Volumes map[string]string `json:"Volumes,omitempty" yaml:"Volumes,omitempty" toml:"Volumes,omitempty"`
+ VolumesRW map[string]bool `json:"VolumesRW,omitempty" yaml:"VolumesRW,omitempty" toml:"VolumesRW,omitempty"`
+ HostConfig *HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty" toml:"HostConfig,omitempty"`
+ ExecIDs []string `json:"ExecIDs,omitempty" yaml:"ExecIDs,omitempty" toml:"ExecIDs,omitempty"`
+ GraphDriver *GraphDriver `json:"GraphDriver,omitempty" yaml:"GraphDriver,omitempty" toml:"GraphDriver,omitempty"`
+
+ RestartCount int `json:"RestartCount,omitempty" yaml:"RestartCount,omitempty" toml:"RestartCount,omitempty"`
+
+ AppArmorProfile string `json:"AppArmorProfile,omitempty" yaml:"AppArmorProfile,omitempty" toml:"AppArmorProfile,omitempty"`
+}
+
+// UpdateContainerOptions specify parameters to the UpdateContainer function.
+//
+// See https://goo.gl/Y6fXUy for more details.
+type UpdateContainerOptions struct {
+ BlkioWeight int `json:"BlkioWeight"`
+ CPUShares int `json:"CpuShares"`
+ CPUPeriod int `json:"CpuPeriod"`
+ CPURealtimePeriod int64 `json:"CpuRealtimePeriod"`
+ CPURealtimeRuntime int64 `json:"CpuRealtimeRuntime"`
+ CPUQuota int `json:"CpuQuota"`
+ CpusetCpus string `json:"CpusetCpus"`
+ CpusetMems string `json:"CpusetMems"`
+ Memory int `json:"Memory"`
+ MemorySwap int `json:"MemorySwap"`
+ MemoryReservation int `json:"MemoryReservation"`
+ KernelMemory int `json:"KernelMemory"`
+ RestartPolicy RestartPolicy `json:"RestartPolicy,omitempty"`
+ Context context.Context
+}
+
+// UpdateContainer updates the container at ID with the options
+//
+// See https://goo.gl/Y6fXUy for more details.
+func (c *Client) UpdateContainer(id string, opts UpdateContainerOptions) error {
+ resp, err := c.do("POST", fmt.Sprintf("/containers/"+id+"/update"), doOptions{
+ data: opts,
+ forceJSON: true,
+ context: opts.Context,
+ })
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+ return nil
+}
+
+// RenameContainerOptions specify parameters to the RenameContainer function.
+//
+// See https://goo.gl/46inai for more details.
+type RenameContainerOptions struct {
+ // ID of container to rename
+ ID string `qs:"-"`
+
+ // New name
+ Name string `json:"name,omitempty" yaml:"name,omitempty"`
+ Context context.Context
+}
+
+// RenameContainer updates and existing containers name
+//
+// See https://goo.gl/46inai for more details.
+func (c *Client) RenameContainer(opts RenameContainerOptions) error {
+ resp, err := c.do("POST", fmt.Sprintf("/containers/"+opts.ID+"/rename?%s", queryString(opts)), doOptions{
+ context: opts.Context,
+ })
+ if err != nil {
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// InspectContainer returns information about a container by its ID.
+//
+// See https://goo.gl/FaI5JT for more details.
+func (c *Client) InspectContainer(id string) (*Container, error) {
+ return c.inspectContainer(id, doOptions{})
+}
+
+// InspectContainerWithContext returns information about a container by its ID.
+// The context object can be used to cancel the inspect request.
+//
+// See https://goo.gl/FaI5JT for more details.
+func (c *Client) InspectContainerWithContext(id string, ctx context.Context) (*Container, error) {
+ return c.inspectContainer(id, doOptions{context: ctx})
+}
+
+func (c *Client) inspectContainer(id string, opts doOptions) (*Container, error) {
+ path := "/containers/" + id + "/json"
+ resp, err := c.do("GET", path, opts)
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return nil, &NoSuchContainer{ID: id}
+ }
+ return nil, err
+ }
+ defer resp.Body.Close()
+ var container Container
+ if err := json.NewDecoder(resp.Body).Decode(&container); err != nil {
+ return nil, err
+ }
+ return &container, nil
+}
+
+// ContainerChanges returns changes in the filesystem of the given container.
+//
+// See https://goo.gl/15KKzh for more details.
+func (c *Client) ContainerChanges(id string) ([]Change, error) {
+ path := "/containers/" + id + "/changes"
+ resp, err := c.do("GET", path, doOptions{})
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return nil, &NoSuchContainer{ID: id}
+ }
+ return nil, err
+ }
+ defer resp.Body.Close()
+ var changes []Change
+ if err := json.NewDecoder(resp.Body).Decode(&changes); err != nil {
+ return nil, err
+ }
+ return changes, nil
+}
+
+// CreateContainerOptions specify parameters to the CreateContainer function.
+//
+// See https://goo.gl/tyzwVM for more details.
+type CreateContainerOptions struct {
+ Name string
+ Config *Config `qs:"-"`
+ HostConfig *HostConfig `qs:"-"`
+ NetworkingConfig *NetworkingConfig `qs:"-"`
+ Context context.Context
+}
+
+// CreateContainer creates a new container, returning the container instance,
+// or an error in case of failure.
+//
+// The returned container instance contains only the container ID. To get more
+// details about the container after creating it, use InspectContainer.
+//
+// See https://goo.gl/tyzwVM for more details.
+func (c *Client) CreateContainer(opts CreateContainerOptions) (*Container, error) {
+ path := "/containers/create?" + queryString(opts)
+ resp, err := c.do(
+ "POST",
+ path,
+ doOptions{
+ data: struct {
+ *Config
+ HostConfig *HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty" toml:"HostConfig,omitempty"`
+ NetworkingConfig *NetworkingConfig `json:"NetworkingConfig,omitempty" yaml:"NetworkingConfig,omitempty" toml:"NetworkingConfig,omitempty"`
+ }{
+ opts.Config,
+ opts.HostConfig,
+ opts.NetworkingConfig,
+ },
+ context: opts.Context,
+ },
+ )
+
+ if e, ok := err.(*Error); ok {
+ if e.Status == http.StatusNotFound {
+ return nil, ErrNoSuchImage
+ }
+ 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 {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ var container Container
+ if err := json.NewDecoder(resp.Body).Decode(&container); err != nil {
+ return nil, err
+ }
+
+ container.Name = opts.Name
+
+ return &container, nil
+}
+
+// KeyValuePair is a type for generic key/value pairs as used in the Lxc
+// configuration
+type KeyValuePair struct {
+ Key string `json:"Key,omitempty" yaml:"Key,omitempty" toml:"Key,omitempty"`
+ Value string `json:"Value,omitempty" yaml:"Value,omitempty" toml:"Value,omitempty"`
+}
+
+// RestartPolicy represents the policy for automatically restarting a container.
+//
+// Possible values are:
+//
+// - always: the docker daemon will always restart the container
+// - on-failure: the docker daemon will restart the container on failures, at
+// most MaximumRetryCount times
+// - unless-stopped: the docker daemon will always restart the container except
+// when user has manually stopped the container
+// - no: the docker daemon will not restart the container automatically
+type RestartPolicy struct {
+ Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"`
+ MaximumRetryCount int `json:"MaximumRetryCount,omitempty" yaml:"MaximumRetryCount,omitempty" toml:"MaximumRetryCount,omitempty"`
+}
+
+// AlwaysRestart returns a restart policy that tells the Docker daemon to
+// always restart the container.
+func AlwaysRestart() RestartPolicy {
+ return RestartPolicy{Name: "always"}
+}
+
+// RestartOnFailure returns a restart policy that tells the Docker daemon to
+// restart the container on failures, trying at most maxRetry times.
+func RestartOnFailure(maxRetry int) RestartPolicy {
+ return RestartPolicy{Name: "on-failure", MaximumRetryCount: maxRetry}
+}
+
+// RestartUnlessStopped returns a restart policy that tells the Docker daemon to
+// always restart the container except when user has manually stopped the container.
+func RestartUnlessStopped() RestartPolicy {
+ return RestartPolicy{Name: "unless-stopped"}
+}
+
+// NeverRestart returns a restart policy that tells the Docker daemon to never
+// restart the container on failures.
+func NeverRestart() RestartPolicy {
+ return RestartPolicy{Name: "no"}
+}
+
+// Device represents a device mapping between the Docker host and the
+// container.
+type Device struct {
+ PathOnHost string `json:"PathOnHost,omitempty" yaml:"PathOnHost,omitempty" toml:"PathOnHost,omitempty"`
+ PathInContainer string `json:"PathInContainer,omitempty" yaml:"PathInContainer,omitempty" toml:"PathInContainer,omitempty"`
+ CgroupPermissions string `json:"CgroupPermissions,omitempty" yaml:"CgroupPermissions,omitempty" toml:"CgroupPermissions,omitempty"`
+}
+
+// BlockWeight represents a relative device weight for an individual device inside
+// of a container
+type BlockWeight struct {
+ Path string `json:"Path,omitempty"`
+ Weight string `json:"Weight,omitempty"`
+}
+
+// BlockLimit represents a read/write limit in IOPS or Bandwidth for a device
+// inside of a container
+type BlockLimit struct {
+ Path string `json:"Path,omitempty"`
+ Rate int64 `json:"Rate,omitempty"`
+}
+
+// HostConfig contains the container options related to starting a container on
+// a given host
+type HostConfig struct {
+ Binds []string `json:"Binds,omitempty" yaml:"Binds,omitempty" toml:"Binds,omitempty"`
+ CapAdd []string `json:"CapAdd,omitempty" yaml:"CapAdd,omitempty" toml:"CapAdd,omitempty"`
+ CapDrop []string `json:"CapDrop,omitempty" yaml:"CapDrop,omitempty" toml:"CapDrop,omitempty"`
+ GroupAdd []string `json:"GroupAdd,omitempty" yaml:"GroupAdd,omitempty" toml:"GroupAdd,omitempty"`
+ ContainerIDFile string `json:"ContainerIDFile,omitempty" yaml:"ContainerIDFile,omitempty" toml:"ContainerIDFile,omitempty"`
+ LxcConf []KeyValuePair `json:"LxcConf,omitempty" yaml:"LxcConf,omitempty" toml:"LxcConf,omitempty"`
+ PortBindings map[Port][]PortBinding `json:"PortBindings,omitempty" yaml:"PortBindings,omitempty" toml:"PortBindings,omitempty"`
+ Links []string `json:"Links,omitempty" yaml:"Links,omitempty" toml:"Links,omitempty"`
+ DNS []string `json:"Dns,omitempty" yaml:"Dns,omitempty" toml:"Dns,omitempty"` // For Docker API v1.10 and above only
+ DNSOptions []string `json:"DnsOptions,omitempty" yaml:"DnsOptions,omitempty" toml:"DnsOptions,omitempty"`
+ DNSSearch []string `json:"DnsSearch,omitempty" yaml:"DnsSearch,omitempty" toml:"DnsSearch,omitempty"`
+ ExtraHosts []string `json:"ExtraHosts,omitempty" yaml:"ExtraHosts,omitempty" toml:"ExtraHosts,omitempty"`
+ VolumesFrom []string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty" toml:"VolumesFrom,omitempty"`
+ UsernsMode string `json:"UsernsMode,omitempty" yaml:"UsernsMode,omitempty" toml:"UsernsMode,omitempty"`
+ NetworkMode string `json:"NetworkMode,omitempty" yaml:"NetworkMode,omitempty" toml:"NetworkMode,omitempty"`
+ IpcMode string `json:"IpcMode,omitempty" yaml:"IpcMode,omitempty" toml:"IpcMode,omitempty"`
+ PidMode string `json:"PidMode,omitempty" yaml:"PidMode,omitempty" toml:"PidMode,omitempty"`
+ 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"`
+ CgroupParent string `json:"CgroupParent,omitempty" yaml:"CgroupParent,omitempty" toml:"CgroupParent,omitempty"`
+ Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty" toml:"Memory,omitempty"`
+ 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,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"`
+ CPUSetMEMs string `json:"CpusetMems,omitempty" yaml:"CpusetMems,omitempty" toml:"CpusetMems,omitempty"`
+ CPUQuota int64 `json:"CpuQuota,omitempty" yaml:"CpuQuota,omitempty" toml:"CpuQuota,omitempty"`
+ CPUPeriod int64 `json:"CpuPeriod,omitempty" yaml:"CpuPeriod,omitempty" toml:"CpuPeriod,omitempty"`
+ CPURealtimePeriod int64 `json:"CpuRealtimePeriod,omitempty" yaml:"CpuRealtimePeriod,omitempty" toml:"CpuRealtimePeriod,omitempty"`
+ CPURealtimeRuntime int64 `json:"CpuRealtimeRuntime,omitempty" yaml:"CpuRealtimeRuntime,omitempty" toml:"CpuRealtimeRuntime,omitempty"`
+ BlkioWeight int64 `json:"BlkioWeight,omitempty" yaml:"BlkioWeight,omitempty" toml:"BlkioWeight,omitempty"`
+ BlkioWeightDevice []BlockWeight `json:"BlkioWeightDevice,omitempty" yaml:"BlkioWeightDevice,omitempty" toml:"BlkioWeightDevice,omitempty"`
+ BlkioDeviceReadBps []BlockLimit `json:"BlkioDeviceReadBps,omitempty" yaml:"BlkioDeviceReadBps,omitempty" toml:"BlkioDeviceReadBps,omitempty"`
+ BlkioDeviceReadIOps []BlockLimit `json:"BlkioDeviceReadIOps,omitempty" yaml:"BlkioDeviceReadIOps,omitempty" toml:"BlkioDeviceReadIOps,omitempty"`
+ BlkioDeviceWriteBps []BlockLimit `json:"BlkioDeviceWriteBps,omitempty" yaml:"BlkioDeviceWriteBps,omitempty" toml:"BlkioDeviceWriteBps,omitempty"`
+ BlkioDeviceWriteIOps []BlockLimit `json:"BlkioDeviceWriteIOps,omitempty" yaml:"BlkioDeviceWriteIOps,omitempty" toml:"BlkioDeviceWriteIOps,omitempty"`
+ Ulimits []ULimit `json:"Ulimits,omitempty" yaml:"Ulimits,omitempty" toml:"Ulimits,omitempty"`
+ VolumeDriver string `json:"VolumeDriver,omitempty" yaml:"VolumeDriver,omitempty" toml:"VolumeDriver,omitempty"`
+ OomScoreAdj int `json:"OomScoreAdj,omitempty" yaml:"OomScoreAdj,omitempty" toml:"OomScoreAdj,omitempty"`
+ PidsLimit int64 `json:"PidsLimit,omitempty" yaml:"PidsLimit,omitempty" toml:"PidsLimit,omitempty"`
+ ShmSize int64 `json:"ShmSize,omitempty" yaml:"ShmSize,omitempty" toml:"ShmSize,omitempty"`
+ Tmpfs map[string]string `json:"Tmpfs,omitempty" yaml:"Tmpfs,omitempty" toml:"Tmpfs,omitempty"`
+ Privileged bool `json:"Privileged,omitempty" yaml:"Privileged,omitempty" toml:"Privileged,omitempty"`
+ PublishAllPorts bool `json:"PublishAllPorts,omitempty" yaml:"PublishAllPorts,omitempty" toml:"PublishAllPorts,omitempty"`
+ ReadonlyRootfs bool `json:"ReadonlyRootfs,omitempty" yaml:"ReadonlyRootfs,omitempty" toml:"ReadonlyRootfs,omitempty"`
+ OOMKillDisable bool `json:"OomKillDisable,omitempty" yaml:"OomKillDisable,omitempty" toml:"OomKillDisable,omitempty"`
+ AutoRemove bool `json:"AutoRemove,omitempty" yaml:"AutoRemove,omitempty" toml:"AutoRemove,omitempty"`
+ StorageOpt map[string]string `json:"StorageOpt,omitempty" yaml:"StorageOpt,omitempty" toml:"StorageOpt,omitempty"`
+ Sysctls map[string]string `json:"Sysctls,omitempty" yaml:"Sysctls,omitempty" toml:"Sysctls,omitempty"`
+ CPUCount int64 `json:"CpuCount,omitempty" yaml:"CpuCount,omitempty"`
+ CPUPercent int64 `json:"CpuPercent,omitempty" yaml:"CpuPercent,omitempty"`
+ IOMaximumBandwidth int64 `json:"IOMaximumBandwidth,omitempty" yaml:"IOMaximumBandwidth,omitempty"`
+ IOMaximumIOps int64 `json:"IOMaximumIOps,omitempty" yaml:"IOMaximumIOps,omitempty"`
+ Mounts []HostMount `json:"Mounts,omitempty" yaml:"Mounts,omitempty" toml:"Mounts,omitempty"`
+ Init bool `json:",omitempty" yaml:",omitempty"`
+}
+
+// NetworkingConfig represents the container's networking configuration for each of its interfaces
+// Carries the networking configs specified in the `docker run` and `docker network connect` commands
+type NetworkingConfig struct {
+ EndpointsConfig map[string]*EndpointConfig `json:"EndpointsConfig" yaml:"EndpointsConfig" toml:"EndpointsConfig"` // Endpoint configs for each connecting network
+}
+
+// StartContainer starts a container, returning an error in case of failure.
+//
+// Passing the HostConfig to this method has been deprecated in Docker API 1.22
+// (Docker Engine 1.10.x) and totally removed in Docker API 1.24 (Docker Engine
+// 1.12.x). The client will ignore the parameter when communicating with Docker
+// API 1.24 or greater.
+//
+// See https://goo.gl/fbOSZy for more details.
+func (c *Client) StartContainer(id string, hostConfig *HostConfig) error {
+ return c.startContainer(id, hostConfig, doOptions{})
+}
+
+// StartContainerWithContext starts a container, returning an error in case of
+// failure. The context can be used to cancel the outstanding start container
+// request.
+//
+// Passing the HostConfig to this method has been deprecated in Docker API 1.22
+// (Docker Engine 1.10.x) and totally removed in Docker API 1.24 (Docker Engine
+// 1.12.x). The client will ignore the parameter when communicating with Docker
+// API 1.24 or greater.
+//
+// See https://goo.gl/fbOSZy for more details.
+func (c *Client) StartContainerWithContext(id string, hostConfig *HostConfig, ctx context.Context) error {
+ return c.startContainer(id, hostConfig, doOptions{context: ctx})
+}
+
+func (c *Client) startContainer(id string, hostConfig *HostConfig, opts doOptions) error {
+ path := "/containers/" + id + "/start"
+ if c.serverAPIVersion == nil {
+ c.checkAPIVersion()
+ }
+ if c.serverAPIVersion != nil && c.serverAPIVersion.LessThan(apiVersion124) {
+ opts.data = hostConfig
+ opts.forceJSON = true
+ }
+ resp, err := c.do("POST", path, opts)
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return &NoSuchContainer{ID: id, Err: err}
+ }
+ return err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode == http.StatusNotModified {
+ return &ContainerAlreadyRunning{ID: id}
+ }
+ return nil
+}
+
+// StopContainer stops a container, killing it after the given timeout (in
+// seconds).
+//
+// See https://goo.gl/R9dZcV for more details.
+func (c *Client) StopContainer(id string, timeout uint) error {
+ return c.stopContainer(id, timeout, doOptions{})
+}
+
+// StopContainerWithContext stops a container, killing it after the given
+// timeout (in seconds). The context can be used to cancel the stop
+// container request.
+//
+// See https://goo.gl/R9dZcV for more details.
+func (c *Client) StopContainerWithContext(id string, timeout uint, ctx context.Context) error {
+ return c.stopContainer(id, timeout, doOptions{context: ctx})
+}
+
+func (c *Client) stopContainer(id string, timeout uint, opts doOptions) error {
+ path := fmt.Sprintf("/containers/%s/stop?t=%d", id, timeout)
+ resp, err := c.do("POST", path, opts)
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return &NoSuchContainer{ID: id}
+ }
+ return err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode == http.StatusNotModified {
+ return &ContainerNotRunning{ID: id}
+ }
+ return nil
+}
+
+// RestartContainer stops a container, killing it after the given timeout (in
+// seconds), during the stop process.
+//
+// See https://goo.gl/MrAKQ5 for more details.
+func (c *Client) RestartContainer(id string, timeout uint) error {
+ path := fmt.Sprintf("/containers/%s/restart?t=%d", id, timeout)
+ resp, err := c.do("POST", path, doOptions{})
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return &NoSuchContainer{ID: id}
+ }
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// PauseContainer pauses the given container.
+//
+// See https://goo.gl/D1Yaii for more details.
+func (c *Client) PauseContainer(id string) error {
+ path := fmt.Sprintf("/containers/%s/pause", id)
+ resp, err := c.do("POST", path, doOptions{})
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return &NoSuchContainer{ID: id}
+ }
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// UnpauseContainer unpauses the given container.
+//
+// See https://goo.gl/sZ2faO for more details.
+func (c *Client) UnpauseContainer(id string) error {
+ path := fmt.Sprintf("/containers/%s/unpause", id)
+ resp, err := c.do("POST", path, doOptions{})
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return &NoSuchContainer{ID: id}
+ }
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// TopResult represents the list of processes running in a container, as
+// returned by /containers/<id>/top.
+//
+// See https://goo.gl/FLwpPl for more details.
+type TopResult struct {
+ Titles []string
+ Processes [][]string
+}
+
+// TopContainer returns processes running inside a container
+//
+// See https://goo.gl/FLwpPl for more details.
+func (c *Client) TopContainer(id string, psArgs string) (TopResult, error) {
+ var args string
+ var result TopResult
+ if psArgs != "" {
+ args = fmt.Sprintf("?ps_args=%s", psArgs)
+ }
+ path := fmt.Sprintf("/containers/%s/top%s", id, args)
+ resp, err := c.do("GET", path, doOptions{})
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return result, &NoSuchContainer{ID: id}
+ }
+ return result, err
+ }
+ defer resp.Body.Close()
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ return result, err
+}
+
+// Stats represents container statistics, returned by /containers/<id>/stats.
+//
+// See https://goo.gl/Dk3Xio for more details.
+type Stats struct {
+ Read time.Time `json:"read,omitempty" yaml:"read,omitempty" toml:"read,omitempty"`
+ PreRead time.Time `json:"preread,omitempty" yaml:"preread,omitempty" toml:"preread,omitempty"`
+ NumProcs uint32 `json:"num_procs" yaml:"num_procs" toml:"num_procs"`
+ PidsStats struct {
+ Current uint64 `json:"current,omitempty" yaml:"current,omitempty"`
+ } `json:"pids_stats,omitempty" yaml:"pids_stats,omitempty" toml:"pids_stats,omitempty"`
+ Network NetworkStats `json:"network,omitempty" yaml:"network,omitempty" toml:"network,omitempty"`
+ Networks map[string]NetworkStats `json:"networks,omitempty" yaml:"networks,omitempty" toml:"networks,omitempty"`
+ MemoryStats struct {
+ Stats struct {
+ TotalPgmafault uint64 `json:"total_pgmafault,omitempty" yaml:"total_pgmafault,omitempty" toml:"total_pgmafault,omitempty"`
+ Cache uint64 `json:"cache,omitempty" yaml:"cache,omitempty" toml:"cache,omitempty"`
+ MappedFile uint64 `json:"mapped_file,omitempty" yaml:"mapped_file,omitempty" toml:"mapped_file,omitempty"`
+ TotalInactiveFile uint64 `json:"total_inactive_file,omitempty" yaml:"total_inactive_file,omitempty" toml:"total_inactive_file,omitempty"`
+ Pgpgout uint64 `json:"pgpgout,omitempty" yaml:"pgpgout,omitempty" toml:"pgpgout,omitempty"`
+ Rss uint64 `json:"rss,omitempty" yaml:"rss,omitempty" toml:"rss,omitempty"`
+ TotalMappedFile uint64 `json:"total_mapped_file,omitempty" yaml:"total_mapped_file,omitempty" toml:"total_mapped_file,omitempty"`
+ Writeback uint64 `json:"writeback,omitempty" yaml:"writeback,omitempty" toml:"writeback,omitempty"`
+ Unevictable uint64 `json:"unevictable,omitempty" yaml:"unevictable,omitempty" toml:"unevictable,omitempty"`
+ Pgpgin uint64 `json:"pgpgin,omitempty" yaml:"pgpgin,omitempty" toml:"pgpgin,omitempty"`
+ TotalUnevictable uint64 `json:"total_unevictable,omitempty" yaml:"total_unevictable,omitempty" toml:"total_unevictable,omitempty"`
+ Pgmajfault uint64 `json:"pgmajfault,omitempty" yaml:"pgmajfault,omitempty" toml:"pgmajfault,omitempty"`
+ TotalRss uint64 `json:"total_rss,omitempty" yaml:"total_rss,omitempty" toml:"total_rss,omitempty"`
+ TotalRssHuge uint64 `json:"total_rss_huge,omitempty" yaml:"total_rss_huge,omitempty" toml:"total_rss_huge,omitempty"`
+ TotalWriteback uint64 `json:"total_writeback,omitempty" yaml:"total_writeback,omitempty" toml:"total_writeback,omitempty"`
+ TotalInactiveAnon uint64 `json:"total_inactive_anon,omitempty" yaml:"total_inactive_anon,omitempty" toml:"total_inactive_anon,omitempty"`
+ RssHuge uint64 `json:"rss_huge,omitempty" yaml:"rss_huge,omitempty" toml:"rss_huge,omitempty"`
+ HierarchicalMemoryLimit uint64 `json:"hierarchical_memory_limit,omitempty" yaml:"hierarchical_memory_limit,omitempty" toml:"hierarchical_memory_limit,omitempty"`
+ TotalPgfault uint64 `json:"total_pgfault,omitempty" yaml:"total_pgfault,omitempty" toml:"total_pgfault,omitempty"`
+ TotalActiveFile uint64 `json:"total_active_file,omitempty" yaml:"total_active_file,omitempty" toml:"total_active_file,omitempty"`
+ ActiveAnon uint64 `json:"active_anon,omitempty" yaml:"active_anon,omitempty" toml:"active_anon,omitempty"`
+ TotalActiveAnon uint64 `json:"total_active_anon,omitempty" yaml:"total_active_anon,omitempty" toml:"total_active_anon,omitempty"`
+ TotalPgpgout uint64 `json:"total_pgpgout,omitempty" yaml:"total_pgpgout,omitempty" toml:"total_pgpgout,omitempty"`
+ TotalCache uint64 `json:"total_cache,omitempty" yaml:"total_cache,omitempty" toml:"total_cache,omitempty"`
+ InactiveAnon uint64 `json:"inactive_anon,omitempty" yaml:"inactive_anon,omitempty" toml:"inactive_anon,omitempty"`
+ ActiveFile uint64 `json:"active_file,omitempty" yaml:"active_file,omitempty" toml:"active_file,omitempty"`
+ Pgfault uint64 `json:"pgfault,omitempty" yaml:"pgfault,omitempty" toml:"pgfault,omitempty"`
+ InactiveFile uint64 `json:"inactive_file,omitempty" yaml:"inactive_file,omitempty" toml:"inactive_file,omitempty"`
+ TotalPgpgin uint64 `json:"total_pgpgin,omitempty" yaml:"total_pgpgin,omitempty" toml:"total_pgpgin,omitempty"`
+ HierarchicalMemswLimit uint64 `json:"hierarchical_memsw_limit,omitempty" yaml:"hierarchical_memsw_limit,omitempty" toml:"hierarchical_memsw_limit,omitempty"`
+ Swap uint64 `json:"swap,omitempty" yaml:"swap,omitempty" toml:"swap,omitempty"`
+ } `json:"stats,omitempty" yaml:"stats,omitempty" toml:"stats,omitempty"`
+ MaxUsage uint64 `json:"max_usage,omitempty" yaml:"max_usage,omitempty" toml:"max_usage,omitempty"`
+ Usage uint64 `json:"usage,omitempty" yaml:"usage,omitempty" toml:"usage,omitempty"`
+ Failcnt uint64 `json:"failcnt,omitempty" yaml:"failcnt,omitempty" toml:"failcnt,omitempty"`
+ Limit uint64 `json:"limit,omitempty" yaml:"limit,omitempty" toml:"limit,omitempty"`
+ Commit uint64 `json:"commitbytes,omitempty" yaml:"commitbytes,omitempty" toml:"privateworkingset,omitempty"`
+ CommitPeak uint64 `json:"commitpeakbytes,omitempty" yaml:"commitpeakbytes,omitempty" toml:"commitpeakbytes,omitempty"`
+ PrivateWorkingSet uint64 `json:"privateworkingset,omitempty" yaml:"privateworkingset,omitempty" toml:"privateworkingset,omitempty"`
+ } `json:"memory_stats,omitempty" yaml:"memory_stats,omitempty" toml:"memory_stats,omitempty"`
+ BlkioStats struct {
+ IOServiceBytesRecursive []BlkioStatsEntry `json:"io_service_bytes_recursive,omitempty" yaml:"io_service_bytes_recursive,omitempty" toml:"io_service_bytes_recursive,omitempty"`
+ IOServicedRecursive []BlkioStatsEntry `json:"io_serviced_recursive,omitempty" yaml:"io_serviced_recursive,omitempty" toml:"io_serviced_recursive,omitempty"`
+ IOQueueRecursive []BlkioStatsEntry `json:"io_queue_recursive,omitempty" yaml:"io_queue_recursive,omitempty" toml:"io_queue_recursive,omitempty"`
+ IOServiceTimeRecursive []BlkioStatsEntry `json:"io_service_time_recursive,omitempty" yaml:"io_service_time_recursive,omitempty" toml:"io_service_time_recursive,omitempty"`
+ IOWaitTimeRecursive []BlkioStatsEntry `json:"io_wait_time_recursive,omitempty" yaml:"io_wait_time_recursive,omitempty" toml:"io_wait_time_recursive,omitempty"`
+ IOMergedRecursive []BlkioStatsEntry `json:"io_merged_recursive,omitempty" yaml:"io_merged_recursive,omitempty" toml:"io_merged_recursive,omitempty"`
+ IOTimeRecursive []BlkioStatsEntry `json:"io_time_recursive,omitempty" yaml:"io_time_recursive,omitempty" toml:"io_time_recursive,omitempty"`
+ SectorsRecursive []BlkioStatsEntry `json:"sectors_recursive,omitempty" yaml:"sectors_recursive,omitempty" toml:"sectors_recursive,omitempty"`
+ } `json:"blkio_stats,omitempty" yaml:"blkio_stats,omitempty" toml:"blkio_stats,omitempty"`
+ CPUStats CPUStats `json:"cpu_stats,omitempty" yaml:"cpu_stats,omitempty" toml:"cpu_stats,omitempty"`
+ PreCPUStats CPUStats `json:"precpu_stats,omitempty"`
+ StorageStats struct {
+ ReadCountNormalized uint64 `json:"read_count_normalized,omitempty" yaml:"read_count_normalized,omitempty" toml:"read_count_normalized,omitempty"`
+ ReadSizeBytes uint64 `json:"read_size_bytes,omitempty" yaml:"read_size_bytes,omitempty" toml:"read_size_bytes,omitempty"`
+ WriteCountNormalized uint64 `json:"write_count_normalized,omitempty" yaml:"write_count_normalized,omitempty" toml:"write_count_normalized,omitempty"`
+ WriteSizeBytes uint64 `json:"write_size_bytes,omitempty" yaml:"write_size_bytes,omitempty" toml:"write_size_bytes,omitempty"`
+ } `json:"storage_stats,omitempty" yaml:"storage_stats,omitempty" toml:"storage_stats,omitempty"`
+}
+
+// NetworkStats is a stats entry for network stats
+type NetworkStats struct {
+ RxDropped uint64 `json:"rx_dropped,omitempty" yaml:"rx_dropped,omitempty" toml:"rx_dropped,omitempty"`
+ RxBytes uint64 `json:"rx_bytes,omitempty" yaml:"rx_bytes,omitempty" toml:"rx_bytes,omitempty"`
+ RxErrors uint64 `json:"rx_errors,omitempty" yaml:"rx_errors,omitempty" toml:"rx_errors,omitempty"`
+ TxPackets uint64 `json:"tx_packets,omitempty" yaml:"tx_packets,omitempty" toml:"tx_packets,omitempty"`
+ TxDropped uint64 `json:"tx_dropped,omitempty" yaml:"tx_dropped,omitempty" toml:"tx_dropped,omitempty"`
+ RxPackets uint64 `json:"rx_packets,omitempty" yaml:"rx_packets,omitempty" toml:"rx_packets,omitempty"`
+ TxErrors uint64 `json:"tx_errors,omitempty" yaml:"tx_errors,omitempty" toml:"tx_errors,omitempty"`
+ TxBytes uint64 `json:"tx_bytes,omitempty" yaml:"tx_bytes,omitempty" toml:"tx_bytes,omitempty"`
+}
+
+// CPUStats is a stats entry for cpu stats
+type CPUStats struct {
+ CPUUsage struct {
+ PercpuUsage []uint64 `json:"percpu_usage,omitempty" yaml:"percpu_usage,omitempty" toml:"percpu_usage,omitempty"`
+ UsageInUsermode uint64 `json:"usage_in_usermode,omitempty" yaml:"usage_in_usermode,omitempty" toml:"usage_in_usermode,omitempty"`
+ TotalUsage uint64 `json:"total_usage,omitempty" yaml:"total_usage,omitempty" toml:"total_usage,omitempty"`
+ 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"`
+ ThrottledTime uint64 `json:"throttled_time,omitempty"`
+ } `json:"throttling_data,omitempty" yaml:"throttling_data,omitempty" toml:"throttling_data,omitempty"`
+}
+
+// BlkioStatsEntry is a stats entry for blkio_stats
+type BlkioStatsEntry struct {
+ Major uint64 `json:"major,omitempty" yaml:"major,omitempty" toml:"major,omitempty"`
+ Minor uint64 `json:"minor,omitempty" yaml:"minor,omitempty" toml:"minor,omitempty"`
+ Op string `json:"op,omitempty" yaml:"op,omitempty" toml:"op,omitempty"`
+ Value uint64 `json:"value,omitempty" yaml:"value,omitempty" toml:"value,omitempty"`
+}
+
+// StatsOptions specify parameters to the Stats function.
+//
+// See https://goo.gl/Dk3Xio for more details.
+type StatsOptions struct {
+ ID string
+ Stats chan<- *Stats
+ Stream bool
+ // A flag that enables stopping the stats operation
+ Done <-chan bool
+ // Initial connection timeout
+ Timeout time.Duration
+ // Timeout with no data is received, it's reset every time new data
+ // arrives
+ InactivityTimeout time.Duration `qs:"-"`
+ Context context.Context
+}
+
+// Stats sends container statistics for the given container to the given channel.
+//
+// This function is blocking, similar to a streaming call for logs, and should be run
+// on a separate goroutine from the caller. Note that this function will block until
+// the given container is removed, not just exited. When finished, this function
+// will close the given channel. Alternatively, function can be stopped by
+// signaling on the Done channel.
+//
+// See https://goo.gl/Dk3Xio for more details.
+func (c *Client) Stats(opts StatsOptions) (retErr error) {
+ errC := make(chan error, 1)
+ readCloser, writeCloser := io.Pipe()
+
+ defer func() {
+ close(opts.Stats)
+
+ select {
+ case err := <-errC:
+ if err != nil && retErr == nil {
+ retErr = err
+ }
+ default:
+ // No errors
+ }
+
+ if err := readCloser.Close(); err != nil && retErr == nil {
+ retErr = err
+ }
+ }()
+
+ reqSent := make(chan struct{})
+ go func() {
+ err := c.stream("GET", fmt.Sprintf("/containers/%s/stats?stream=%v", opts.ID, opts.Stream), streamOptions{
+ rawJSONStream: true,
+ useJSONDecoder: true,
+ stdout: writeCloser,
+ timeout: opts.Timeout,
+ inactivityTimeout: opts.InactivityTimeout,
+ context: opts.Context,
+ reqSent: reqSent,
+ })
+ if err != nil {
+ dockerError, ok := err.(*Error)
+ if ok {
+ if dockerError.Status == http.StatusNotFound {
+ err = &NoSuchContainer{ID: opts.ID}
+ }
+ }
+ }
+ if closeErr := writeCloser.Close(); closeErr != nil && err == nil {
+ err = closeErr
+ }
+ errC <- err
+ close(errC)
+ }()
+
+ quit := make(chan struct{})
+ defer close(quit)
+ go func() {
+ // block here waiting for the signal to stop function
+ select {
+ case <-opts.Done:
+ readCloser.Close()
+ case <-quit:
+ return
+ }
+ }()
+
+ decoder := json.NewDecoder(readCloser)
+ stats := new(Stats)
+ <-reqSent
+ for err := decoder.Decode(stats); err != io.EOF; err = decoder.Decode(stats) {
+ if err != nil {
+ return err
+ }
+ opts.Stats <- stats
+ stats = new(Stats)
+ }
+ return nil
+}
+
+// KillContainerOptions represents the set of options that can be used in a
+// call to KillContainer.
+//
+// See https://goo.gl/JnTxXZ for more details.
+type KillContainerOptions struct {
+ // The ID of the container.
+ ID string `qs:"-"`
+
+ // The signal to send to the container. When omitted, Docker server
+ // will assume SIGKILL.
+ Signal Signal
+ Context context.Context
+}
+
+// KillContainer sends a signal to a container, returning an error in case of
+// failure.
+//
+// See https://goo.gl/JnTxXZ for more details.
+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 {
+ 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
+ }
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// RemoveContainerOptions encapsulates options to remove a container.
+//
+// See https://goo.gl/hL5IPC for more details.
+type RemoveContainerOptions struct {
+ // The ID of the container.
+ ID string `qs:"-"`
+
+ // A flag that indicates whether Docker should remove the volumes
+ // associated to the container.
+ RemoveVolumes bool `qs:"v"`
+
+ // A flag that indicates whether Docker should remove the container
+ // even if it is currently running.
+ Force bool
+ Context context.Context
+}
+
+// RemoveContainer removes a container, returning an error in case of failure.
+//
+// See https://goo.gl/hL5IPC for more details.
+func (c *Client) RemoveContainer(opts RemoveContainerOptions) error {
+ path := "/containers/" + opts.ID + "?" + queryString(opts)
+ resp, err := c.do("DELETE", path, doOptions{context: opts.Context})
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return &NoSuchContainer{ID: opts.ID}
+ }
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// UploadToContainerOptions is the set of options that can be used when
+// uploading an archive into a container.
+//
+// See https://goo.gl/g25o7u for more details.
+type UploadToContainerOptions struct {
+ InputStream io.Reader `json:"-" qs:"-"`
+ Path string `qs:"path"`
+ NoOverwriteDirNonDir bool `qs:"noOverwriteDirNonDir"`
+ Context context.Context
+}
+
+// UploadToContainer uploads a tar archive to be extracted to a path in the
+// filesystem of the container.
+//
+// See https://goo.gl/g25o7u for more details.
+func (c *Client) UploadToContainer(id string, opts UploadToContainerOptions) error {
+ url := fmt.Sprintf("/containers/%s/archive?", id) + queryString(opts)
+
+ return c.stream("PUT", url, streamOptions{
+ in: opts.InputStream,
+ context: opts.Context,
+ })
+}
+
+// DownloadFromContainerOptions is the set of options that can be used when
+// downloading resources from a container.
+//
+// See https://goo.gl/W49jxK for more details.
+type DownloadFromContainerOptions struct {
+ OutputStream io.Writer `json:"-" qs:"-"`
+ Path string `qs:"path"`
+ InactivityTimeout time.Duration `qs:"-"`
+ Context context.Context
+}
+
+// DownloadFromContainer downloads a tar archive of files or folders in a container.
+//
+// See https://goo.gl/W49jxK for more details.
+func (c *Client) DownloadFromContainer(id string, opts DownloadFromContainerOptions) error {
+ url := fmt.Sprintf("/containers/%s/archive?", id) + queryString(opts)
+
+ return c.stream("GET", url, streamOptions{
+ setRawTerminal: true,
+ stdout: opts.OutputStream,
+ inactivityTimeout: opts.InactivityTimeout,
+ context: opts.Context,
+ })
+}
+
+// CopyFromContainerOptions contains the set of options used for copying
+// files from a container.
+//
+// Deprecated: Use DownloadFromContainerOptions and DownloadFromContainer instead.
+type CopyFromContainerOptions struct {
+ OutputStream io.Writer `json:"-"`
+ Container string `json:"-"`
+ Resource string
+ Context context.Context `json:"-"`
+}
+
+// CopyFromContainer copies files from a container.
+//
+// Deprecated: Use DownloadFromContainer and DownloadFromContainer instead.
+func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error {
+ if opts.Container == "" {
+ return &NoSuchContainer{ID: opts.Container}
+ }
+ if c.serverAPIVersion == nil {
+ c.checkAPIVersion()
+ }
+ if c.serverAPIVersion != nil && c.serverAPIVersion.GreaterThanOrEqualTo(apiVersion124) {
+ return errors.New("go-dockerclient: CopyFromContainer is no longer available in Docker >= 1.12, use DownloadFromContainer instead")
+ }
+ url := fmt.Sprintf("/containers/%s/copy", opts.Container)
+ resp, err := c.do("POST", url, doOptions{
+ data: opts,
+ context: opts.Context,
+ })
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return &NoSuchContainer{ID: opts.Container}
+ }
+ return err
+ }
+ defer resp.Body.Close()
+ _, err = io.Copy(opts.OutputStream, resp.Body)
+ return err
+}
+
+// WaitContainer blocks until the given container stops, return the exit code
+// of the container status.
+//
+// See https://goo.gl/4AGweZ for more details.
+func (c *Client) WaitContainer(id string) (int, error) {
+ return c.waitContainer(id, doOptions{})
+}
+
+// WaitContainerWithContext blocks until the given container stops, return the exit code
+// of the container status. The context object can be used to cancel the
+// inspect request.
+//
+// See https://goo.gl/4AGweZ for more details.
+func (c *Client) WaitContainerWithContext(id string, ctx context.Context) (int, error) {
+ return c.waitContainer(id, doOptions{context: ctx})
+}
+
+func (c *Client) waitContainer(id string, opts doOptions) (int, error) {
+ resp, err := c.do("POST", "/containers/"+id+"/wait", opts)
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return 0, &NoSuchContainer{ID: id}
+ }
+ return 0, err
+ }
+ defer resp.Body.Close()
+ var r struct{ StatusCode int }
+ if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
+ return 0, err
+ }
+ return r.StatusCode, nil
+}
+
+// CommitContainerOptions aggregates parameters to the CommitContainer method.
+//
+// See https://goo.gl/CzIguf for more details.
+type CommitContainerOptions struct {
+ Container string
+ Repository string `qs:"repo"`
+ Tag string
+ Message string `qs:"comment"`
+ Author string
+ Changes []string `qs:"changes"`
+ Run *Config `qs:"-"`
+ Context context.Context
+}
+
+// CommitContainer creates a new image from a container's changes.
+//
+// See https://goo.gl/CzIguf for more details.
+func (c *Client) CommitContainer(opts CommitContainerOptions) (*Image, error) {
+ path := "/commit?" + queryString(opts)
+ resp, err := c.do("POST", path, doOptions{
+ data: opts.Run,
+ context: opts.Context,
+ })
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return nil, &NoSuchContainer{ID: opts.Container}
+ }
+ return nil, err
+ }
+ defer resp.Body.Close()
+ var image Image
+ if err := json.NewDecoder(resp.Body).Decode(&image); err != nil {
+ return nil, err
+ }
+ return &image, nil
+}
+
+// AttachToContainerOptions is the set of options that can be used when
+// attaching to a container.
+//
+// See https://goo.gl/JF10Zk for more details.
+type AttachToContainerOptions struct {
+ Container string `qs:"-"`
+ InputStream io.Reader `qs:"-"`
+ OutputStream io.Writer `qs:"-"`
+ ErrorStream io.Writer `qs:"-"`
+
+ // If set, after a successful connect, a sentinel will be sent and then the
+ // client will block on receive before continuing.
+ //
+ // It must be an unbuffered channel. Using a buffered channel can lead
+ // to unexpected behavior.
+ Success chan struct{}
+
+ // Use raw terminal? Usually true when the container contains a TTY.
+ RawTerminal bool `qs:"-"`
+
+ // Get container logs, sending it to OutputStream.
+ Logs bool
+
+ // Stream the response?
+ Stream bool
+
+ // Attach to stdin, and use InputStream.
+ Stdin bool
+
+ // Attach to stdout, and use OutputStream.
+ Stdout bool
+
+ // Attach to stderr, and use ErrorStream.
+ Stderr bool
+}
+
+// AttachToContainer attaches to a container, using the given options.
+//
+// See https://goo.gl/JF10Zk for more details.
+func (c *Client) AttachToContainer(opts AttachToContainerOptions) error {
+ cw, err := c.AttachToContainerNonBlocking(opts)
+ if err != nil {
+ return err
+ }
+ return cw.Wait()
+}
+
+// AttachToContainerNonBlocking attaches to a container, using the given options.
+// This function does not block.
+//
+// See https://goo.gl/NKpkFk for more details.
+func (c *Client) AttachToContainerNonBlocking(opts AttachToContainerOptions) (CloseWaiter, error) {
+ if opts.Container == "" {
+ return nil, &NoSuchContainer{ID: opts.Container}
+ }
+ path := "/containers/" + opts.Container + "/attach?" + queryString(opts)
+ return c.hijack("POST", path, hijackOptions{
+ success: opts.Success,
+ setRawTerminal: opts.RawTerminal,
+ in: opts.InputStream,
+ stdout: opts.OutputStream,
+ stderr: opts.ErrorStream,
+ })
+}
+
+// LogsOptions represents the set of options used when getting logs from a
+// container.
+//
+// See https://goo.gl/krK0ZH for more details.
+type LogsOptions struct {
+ Context context.Context
+ Container string `qs:"-"`
+ OutputStream io.Writer `qs:"-"`
+ ErrorStream io.Writer `qs:"-"`
+ InactivityTimeout time.Duration `qs:"-"`
+ Tail string
+
+ Since int64
+ Follow bool
+ Stdout bool
+ Stderr bool
+ Timestamps bool
+
+ // Use raw terminal? Usually true when the container contains a TTY.
+ RawTerminal bool `qs:"-"`
+}
+
+// Logs gets stdout and stderr logs from the specified container.
+//
+// When LogsOptions.RawTerminal is set to false, go-dockerclient will multiplex
+// the streams and send the containers stdout to LogsOptions.OutputStream, and
+// stderr to LogsOptions.ErrorStream.
+//
+// When LogsOptions.RawTerminal is true, callers will get the raw stream on
+// LogsOptions.OutputStream. The caller can use libraries such as dlog
+// (github.com/ahmetalpbalkan/dlog).
+//
+// See https://goo.gl/krK0ZH for more details.
+func (c *Client) Logs(opts LogsOptions) error {
+ if opts.Container == "" {
+ return &NoSuchContainer{ID: opts.Container}
+ }
+ if opts.Tail == "" {
+ opts.Tail = "all"
+ }
+ path := "/containers/" + opts.Container + "/logs?" + queryString(opts)
+ return c.stream("GET", path, streamOptions{
+ setRawTerminal: opts.RawTerminal,
+ stdout: opts.OutputStream,
+ stderr: opts.ErrorStream,
+ inactivityTimeout: opts.InactivityTimeout,
+ context: opts.Context,
+ })
+}
+
+// ResizeContainerTTY resizes the terminal to the given height and width.
+//
+// See https://goo.gl/FImjeq for more details.
+func (c *Client) ResizeContainerTTY(id string, height, width int) error {
+ params := make(url.Values)
+ params.Set("h", strconv.Itoa(height))
+ params.Set("w", strconv.Itoa(width))
+ resp, err := c.do("POST", "/containers/"+id+"/resize?"+params.Encode(), doOptions{})
+ if err != nil {
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// ExportContainerOptions is the set of parameters to the ExportContainer
+// method.
+//
+// See https://goo.gl/yGJCIh for more details.
+type ExportContainerOptions struct {
+ ID string
+ OutputStream io.Writer
+ InactivityTimeout time.Duration `qs:"-"`
+ Context context.Context
+}
+
+// ExportContainer export the contents of container id as tar archive
+// and prints the exported contents to stdout.
+//
+// See https://goo.gl/yGJCIh for more details.
+func (c *Client) ExportContainer(opts ExportContainerOptions) error {
+ if opts.ID == "" {
+ return &NoSuchContainer{ID: opts.ID}
+ }
+ url := fmt.Sprintf("/containers/%s/export", opts.ID)
+ return c.stream("GET", url, streamOptions{
+ setRawTerminal: true,
+ stdout: opts.OutputStream,
+ inactivityTimeout: opts.InactivityTimeout,
+ context: opts.Context,
+ })
+}
+
+// PruneContainersOptions specify parameters to the PruneContainers function.
+//
+// See https://goo.gl/wnkgDT for more details.
+type PruneContainersOptions struct {
+ Filters map[string][]string
+ Context context.Context
+}
+
+// PruneContainersResults specify results from the PruneContainers function.
+//
+// See https://goo.gl/wnkgDT for more details.
+type PruneContainersResults struct {
+ ContainersDeleted []string
+ SpaceReclaimed int64
+}
+
+// PruneContainers deletes containers which are stopped.
+//
+// See https://goo.gl/wnkgDT for more details.
+func (c *Client) PruneContainers(opts PruneContainersOptions) (*PruneContainersResults, error) {
+ path := "/containers/prune?" + queryString(opts)
+ resp, err := c.do("POST", path, doOptions{context: opts.Context})
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ var results PruneContainersResults
+ if err := json.NewDecoder(resp.Body).Decode(&results); err != nil {
+ return nil, err
+ }
+ return &results, nil
+}
+
+// NoSuchContainer is the error returned when a given container does not exist.
+type NoSuchContainer struct {
+ ID string
+ Err error
+}
+
+func (err *NoSuchContainer) Error() string {
+ if err.Err != nil {
+ return err.Err.Error()
+ }
+ return "No such container: " + err.ID
+}
+
+// ContainerAlreadyRunning is the error returned when a given container is
+// already running.
+type ContainerAlreadyRunning struct {
+ ID string
+}
+
+func (err *ContainerAlreadyRunning) Error() string {
+ return "Container already running: " + err.ID
+}
+
+// ContainerNotRunning is the error returned when a given container is not
+// running.
+type ContainerNotRunning struct {
+ ID string
+}
+
+func (err *ContainerNotRunning) Error() string {
+ return "Container not running: " + err.ID
+}
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/env.go b/vendor/github.com/fsouza/go-dockerclient/env.go
new file mode 100644
index 000000000..13fedfb17
--- /dev/null
+++ b/vendor/github.com/fsouza/go-dockerclient/env.go
@@ -0,0 +1,172 @@
+// 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 docker
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "strconv"
+ "strings"
+)
+
+// Env represents a list of key-pair represented in the form KEY=VALUE.
+type Env []string
+
+// Get returns the string value of the given key.
+func (env *Env) Get(key string) (value string) {
+ return env.Map()[key]
+}
+
+// Exists checks whether the given key is defined in the internal Env
+// representation.
+func (env *Env) Exists(key string) bool {
+ _, exists := env.Map()[key]
+ return exists
+}
+
+// GetBool returns a boolean representation of the given key. The key is false
+// whenever its value if 0, no, false, none or an empty string. Any other value
+// will be interpreted as true.
+func (env *Env) GetBool(key string) (value bool) {
+ s := strings.ToLower(strings.Trim(env.Get(key), " \t"))
+ if s == "" || s == "0" || s == "no" || s == "false" || s == "none" {
+ return false
+ }
+ return true
+}
+
+// SetBool defines a boolean value to the given key.
+func (env *Env) SetBool(key string, value bool) {
+ if value {
+ env.Set(key, "1")
+ } else {
+ env.Set(key, "0")
+ }
+}
+
+// GetInt returns the value of the provided key, converted to int.
+//
+// It the value cannot be represented as an integer, it returns -1.
+func (env *Env) GetInt(key string) int {
+ return int(env.GetInt64(key))
+}
+
+// SetInt defines an integer value to the given key.
+func (env *Env) SetInt(key string, value int) {
+ env.Set(key, strconv.Itoa(value))
+}
+
+// GetInt64 returns the value of the provided key, converted to int64.
+//
+// It the value cannot be represented as an integer, it returns -1.
+func (env *Env) GetInt64(key string) int64 {
+ s := strings.Trim(env.Get(key), " \t")
+ val, err := strconv.ParseInt(s, 10, 64)
+ if err != nil {
+ return -1
+ }
+ return val
+}
+
+// SetInt64 defines an integer (64-bit wide) value to the given key.
+func (env *Env) SetInt64(key string, value int64) {
+ env.Set(key, strconv.FormatInt(value, 10))
+}
+
+// GetJSON unmarshals the value of the provided key in the provided iface.
+//
+// iface is a value that can be provided to the json.Unmarshal function.
+func (env *Env) GetJSON(key string, iface interface{}) error {
+ sval := env.Get(key)
+ if sval == "" {
+ return nil
+ }
+ return json.Unmarshal([]byte(sval), iface)
+}
+
+// SetJSON marshals the given value to JSON format and stores it using the
+// provided key.
+func (env *Env) SetJSON(key string, value interface{}) error {
+ sval, err := json.Marshal(value)
+ if err != nil {
+ return err
+ }
+ env.Set(key, string(sval))
+ return nil
+}
+
+// GetList returns a list of strings matching the provided key. It handles the
+// list as a JSON representation of a list of strings.
+//
+// If the given key matches to a single string, it will return a list
+// containing only the value that matches the key.
+func (env *Env) GetList(key string) []string {
+ sval := env.Get(key)
+ if sval == "" {
+ return nil
+ }
+ var l []string
+ if err := json.Unmarshal([]byte(sval), &l); err != nil {
+ l = append(l, sval)
+ }
+ return l
+}
+
+// SetList stores the given list in the provided key, after serializing it to
+// JSON format.
+func (env *Env) SetList(key string, value []string) error {
+ return env.SetJSON(key, value)
+}
+
+// Set defines the value of a key to the given string.
+func (env *Env) Set(key, value string) {
+ *env = append(*env, key+"="+value)
+}
+
+// Decode decodes `src` as a json dictionary, and adds each decoded key-value
+// pair to the environment.
+//
+// If `src` cannot be decoded as a json dictionary, an error is returned.
+func (env *Env) Decode(src io.Reader) error {
+ m := make(map[string]interface{})
+ if err := json.NewDecoder(src).Decode(&m); err != nil {
+ return err
+ }
+ for k, v := range m {
+ env.SetAuto(k, v)
+ }
+ return nil
+}
+
+// SetAuto will try to define the Set* method to call based on the given value.
+func (env *Env) SetAuto(key string, value interface{}) {
+ if fval, ok := value.(float64); ok {
+ env.SetInt64(key, int64(fval))
+ } else if sval, ok := value.(string); ok {
+ env.Set(key, sval)
+ } else if val, err := json.Marshal(value); err == nil {
+ env.Set(key, string(val))
+ } else {
+ env.Set(key, fmt.Sprintf("%v", value))
+ }
+}
+
+// Map returns the map representation of the env.
+func (env *Env) Map() map[string]string {
+ if len(*env) == 0 {
+ return nil
+ }
+ m := make(map[string]string)
+ for _, kv := range *env {
+ parts := strings.SplitN(kv, "=", 2)
+ if len(parts) == 1 {
+ m[parts[0]] = ""
+ } else {
+ m[parts[0]] = parts[1]
+ }
+ }
+ return m
+}
diff --git a/vendor/github.com/fsouza/go-dockerclient/event.go b/vendor/github.com/fsouza/go-dockerclient/event.go
new file mode 100644
index 000000000..18ae5d5a6
--- /dev/null
+++ b/vendor/github.com/fsouza/go-dockerclient/event.go
@@ -0,0 +1,410 @@
+// Copyright 2014 go-dockerclient authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package docker
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "math"
+ "net"
+ "net/http"
+ "net/http/httputil"
+ "sync"
+ "sync/atomic"
+ "time"
+)
+
+// APIEvents represents events coming from the Docker API
+// The fields in the Docker API changed in API version 1.22, and
+// events for more than images and containers are now fired off.
+// To maintain forward and backward compatibility, go-dockerclient
+// replicates the event in both the new and old format as faithfully as possible.
+//
+// For events that only exist in 1.22 in later, `Status` is filled in as
+// `"Type:Action"` instead of just `Action` to allow for older clients to
+// differentiate and not break if they rely on the pre-1.22 Status types.
+//
+// The transformEvent method can be consulted for more information about how
+// events are translated from new/old API formats
+type APIEvents struct {
+ // New API Fields in 1.22
+ Action string `json:"action,omitempty"`
+ Type string `json:"type,omitempty"`
+ Actor APIActor `json:"actor,omitempty"`
+
+ // Old API fields for < 1.22
+ Status string `json:"status,omitempty"`
+ ID string `json:"id,omitempty"`
+ From string `json:"from,omitempty"`
+
+ // Fields in both
+ Time int64 `json:"time,omitempty"`
+ TimeNano int64 `json:"timeNano,omitempty"`
+}
+
+// APIActor represents an actor that accomplishes something for an event
+type APIActor struct {
+ ID string `json:"id,omitempty"`
+ Attributes map[string]string `json:"attributes,omitempty"`
+}
+
+type eventMonitoringState struct {
+ // `sync/atomic` expects the first word in an allocated struct to be 64-bit
+ // aligned on both ARM and x86-32. See https://goo.gl/zW7dgq for more details.
+ lastSeen int64
+ sync.RWMutex
+ sync.WaitGroup
+ enabled bool
+ C chan *APIEvents
+ errC chan error
+ listeners []chan<- *APIEvents
+}
+
+const (
+ maxMonitorConnRetries = 5
+ retryInitialWaitTime = 10.
+)
+
+var (
+ // ErrNoListeners is the error returned when no listeners are available
+ // to receive an event.
+ ErrNoListeners = errors.New("no listeners present to receive event")
+
+ // ErrListenerAlreadyExists is the error returned when the listerner already
+ // exists.
+ ErrListenerAlreadyExists = errors.New("listener already exists for docker events")
+
+ // ErrTLSNotSupported is the error returned when the client does not support
+ // TLS (this applies to the Windows named pipe client).
+ ErrTLSNotSupported = errors.New("tls not supported by this client")
+
+ // EOFEvent is sent when the event listener receives an EOF error.
+ EOFEvent = &APIEvents{
+ Type: "EOF",
+ Status: "EOF",
+ }
+)
+
+// AddEventListener adds a new listener to container events in the Docker API.
+//
+// The parameter is a channel through which events will be sent.
+func (c *Client) AddEventListener(listener chan<- *APIEvents) error {
+ var err error
+ if !c.eventMonitor.isEnabled() {
+ err = c.eventMonitor.enableEventMonitoring(c)
+ if err != nil {
+ return err
+ }
+ }
+ return c.eventMonitor.addListener(listener)
+}
+
+// RemoveEventListener removes a listener from the monitor.
+func (c *Client) RemoveEventListener(listener chan *APIEvents) error {
+ err := c.eventMonitor.removeListener(listener)
+ if err != nil {
+ return err
+ }
+ if c.eventMonitor.listernersCount() == 0 {
+ c.eventMonitor.disableEventMonitoring()
+ }
+ return nil
+}
+
+func (eventState *eventMonitoringState) addListener(listener chan<- *APIEvents) error {
+ eventState.Lock()
+ defer eventState.Unlock()
+ if listenerExists(listener, &eventState.listeners) {
+ return ErrListenerAlreadyExists
+ }
+ eventState.Add(1)
+ eventState.listeners = append(eventState.listeners, listener)
+ return nil
+}
+
+func (eventState *eventMonitoringState) removeListener(listener chan<- *APIEvents) error {
+ eventState.Lock()
+ defer eventState.Unlock()
+ if listenerExists(listener, &eventState.listeners) {
+ var newListeners []chan<- *APIEvents
+ for _, l := range eventState.listeners {
+ if l != listener {
+ newListeners = append(newListeners, l)
+ }
+ }
+ eventState.listeners = newListeners
+ eventState.Add(-1)
+ }
+ return nil
+}
+
+func (eventState *eventMonitoringState) closeListeners() {
+ for _, l := range eventState.listeners {
+ close(l)
+ eventState.Add(-1)
+ }
+ eventState.listeners = nil
+}
+
+func (eventState *eventMonitoringState) listernersCount() int {
+ eventState.RLock()
+ defer eventState.RUnlock()
+ return len(eventState.listeners)
+}
+
+func listenerExists(a chan<- *APIEvents, list *[]chan<- *APIEvents) bool {
+ for _, b := range *list {
+ if b == a {
+ return true
+ }
+ }
+ return false
+}
+
+func (eventState *eventMonitoringState) enableEventMonitoring(c *Client) error {
+ eventState.Lock()
+ defer eventState.Unlock()
+ if !eventState.enabled {
+ eventState.enabled = true
+ atomic.StoreInt64(&eventState.lastSeen, 0)
+ eventState.C = make(chan *APIEvents, 100)
+ eventState.errC = make(chan error, 1)
+ go eventState.monitorEvents(c)
+ }
+ return nil
+}
+
+func (eventState *eventMonitoringState) disableEventMonitoring() error {
+ eventState.Lock()
+ defer eventState.Unlock()
+
+ eventState.closeListeners()
+
+ eventState.Wait()
+
+ if eventState.enabled {
+ eventState.enabled = false
+ close(eventState.C)
+ close(eventState.errC)
+ }
+ return nil
+}
+
+func (eventState *eventMonitoringState) monitorEvents(c *Client) {
+ const (
+ noListenersTimeout = 5 * time.Second
+ noListenersInterval = 10 * time.Millisecond
+ noListenersMaxTries = noListenersTimeout / noListenersInterval
+ )
+
+ var err error
+ 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()
+ return
+ }
+ for eventState.isEnabled() {
+ timeout := time.After(100 * time.Millisecond)
+ select {
+ case ev, ok := <-eventState.C:
+ if !ok {
+ return
+ }
+ if ev == EOFEvent {
+ eventState.disableEventMonitoring()
+ return
+ }
+ eventState.updateLastSeen(ev)
+ eventState.sendEvent(ev)
+ case err = <-eventState.errC:
+ if err == ErrNoListeners {
+ eventState.disableEventMonitoring()
+ return
+ } else if err != nil {
+ defer func() { go eventState.monitorEvents(c) }()
+ return
+ }
+ case <-timeout:
+ continue
+ }
+ }
+}
+
+func (eventState *eventMonitoringState) connectWithRetry(c *Client) error {
+ var retries int
+ eventState.RLock()
+ eventChan := eventState.C
+ errChan := eventState.errC
+ eventState.RUnlock()
+ err := c.eventHijack(atomic.LoadInt64(&eventState.lastSeen), eventChan, errChan)
+ for ; err != nil && retries < maxMonitorConnRetries; retries++ {
+ waitTime := int64(retryInitialWaitTime * math.Pow(2, float64(retries)))
+ time.Sleep(time.Duration(waitTime) * time.Millisecond)
+ eventState.RLock()
+ eventChan = eventState.C
+ errChan = eventState.errC
+ eventState.RUnlock()
+ err = c.eventHijack(atomic.LoadInt64(&eventState.lastSeen), eventChan, errChan)
+ }
+ return err
+}
+
+func (eventState *eventMonitoringState) noListeners() bool {
+ eventState.RLock()
+ defer eventState.RUnlock()
+ return len(eventState.listeners) == 0
+}
+
+func (eventState *eventMonitoringState) isEnabled() bool {
+ eventState.RLock()
+ defer eventState.RUnlock()
+ return eventState.enabled
+}
+
+func (eventState *eventMonitoringState) sendEvent(event *APIEvents) {
+ eventState.RLock()
+ defer eventState.RUnlock()
+ eventState.Add(1)
+ defer eventState.Done()
+ if eventState.enabled {
+ if len(eventState.listeners) == 0 {
+ eventState.errC <- ErrNoListeners
+ return
+ }
+
+ for _, listener := range eventState.listeners {
+ select {
+ case listener <- event:
+ default:
+ }
+ }
+ }
+}
+
+func (eventState *eventMonitoringState) updateLastSeen(e *APIEvents) {
+ eventState.Lock()
+ defer eventState.Unlock()
+ if atomic.LoadInt64(&eventState.lastSeen) < e.Time {
+ atomic.StoreInt64(&eventState.lastSeen, e.Time)
+ }
+}
+
+func (c *Client) eventHijack(startTime int64, eventChan chan *APIEvents, errChan chan error) error {
+ uri := "/events"
+ if startTime != 0 {
+ uri += fmt.Sprintf("?since=%d", startTime)
+ }
+ protocol := c.endpointURL.Scheme
+ address := c.endpointURL.Path
+ if protocol != "unix" && protocol != "npipe" {
+ protocol = "tcp"
+ address = c.endpointURL.Host
+ }
+ var dial net.Conn
+ var err error
+ if c.TLSConfig == nil {
+ dial, err = c.Dialer.Dial(protocol, address)
+ } else {
+ netDialer, ok := c.Dialer.(*net.Dialer)
+ if !ok {
+ return ErrTLSNotSupported
+ }
+ dial, err = tlsDialWithDialer(netDialer, protocol, address, c.TLSConfig)
+ }
+ if err != nil {
+ return err
+ }
+ conn := httputil.NewClientConn(dial, nil)
+ req, err := http.NewRequest("GET", uri, nil)
+ if err != nil {
+ return err
+ }
+ res, err := conn.Do(req)
+ if err != nil {
+ return err
+ }
+ go func(res *http.Response, conn *httputil.ClientConn) {
+ defer conn.Close()
+ defer res.Body.Close()
+ decoder := json.NewDecoder(res.Body)
+ for {
+ var event APIEvents
+ if err = decoder.Decode(&event); err != nil {
+ if err == io.EOF || err == io.ErrUnexpectedEOF {
+ c.eventMonitor.RLock()
+ if c.eventMonitor.enabled && c.eventMonitor.C == eventChan {
+ // Signal that we're exiting.
+ eventChan <- EOFEvent
+ }
+ c.eventMonitor.RUnlock()
+ break
+ }
+ errChan <- err
+ }
+ if event.Time == 0 {
+ continue
+ }
+ transformEvent(&event)
+ c.eventMonitor.RLock()
+ if c.eventMonitor.enabled && c.eventMonitor.C == eventChan {
+ eventChan <- &event
+ }
+ c.eventMonitor.RUnlock()
+ }
+ }(res, conn)
+ return nil
+}
+
+// transformEvent takes an event and determines what version it is from
+// then populates both versions of the event
+func transformEvent(event *APIEvents) {
+ // if event version is <= 1.21 there will be no Action and no Type
+ if event.Action == "" && event.Type == "" {
+ event.Action = event.Status
+ event.Actor.ID = event.ID
+ event.Actor.Attributes = map[string]string{}
+ switch event.Status {
+ case "delete", "import", "pull", "push", "tag", "untag":
+ event.Type = "image"
+ default:
+ event.Type = "container"
+ if event.From != "" {
+ event.Actor.Attributes["image"] = event.From
+ }
+ }
+ } else {
+ if event.Status == "" {
+ if event.Type == "image" || event.Type == "container" {
+ event.Status = event.Action
+ } else {
+ // Because just the Status has been overloaded with different Types
+ // if an event is not for an image or a container, we prepend the type
+ // to avoid problems for people relying on actions being only for
+ // images and containers
+ event.Status = event.Type + ":" + event.Action
+ }
+ }
+ if event.ID == "" {
+ event.ID = event.Actor.ID
+ }
+ if event.From == "" {
+ event.From = event.Actor.Attributes["image"]
+ }
+ }
+}
diff --git a/vendor/github.com/fsouza/go-dockerclient/exec.go b/vendor/github.com/fsouza/go-dockerclient/exec.go
new file mode 100644
index 000000000..3b875fa3c
--- /dev/null
+++ b/vendor/github.com/fsouza/go-dockerclient/exec.go
@@ -0,0 +1,213 @@
+// Copyright 2014 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"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "strconv"
+)
+
+// Exec is the type representing a `docker exec` instance and containing the
+// instance ID
+type Exec struct {
+ ID string `json:"Id,omitempty" yaml:"Id,omitempty"`
+}
+
+// CreateExecOptions specify parameters to the CreateExecContainer function.
+//
+// See https://goo.gl/60TeBP for more details
+type CreateExecOptions struct {
+ AttachStdin bool `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty" toml:"AttachStdin,omitempty"`
+ AttachStdout bool `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty" toml:"AttachStdout,omitempty"`
+ AttachStderr bool `json:"AttachStderr,omitempty" yaml:"AttachStderr,omitempty" toml:"AttachStderr,omitempty"`
+ Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty" toml:"Tty,omitempty"`
+ Env []string `json:"Env,omitempty" yaml:"Env,omitempty" toml:"Env,omitempty"`
+ 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"`
+ Context context.Context `json:"-"`
+ Privileged bool `json:"Privileged,omitempty" yaml:"Privileged,omitempty" toml:"Privileged,omitempty"`
+}
+
+// CreateExec sets up an exec instance in a running container `id`, returning the exec
+// instance, or an error in case of failure.
+//
+// See https://goo.gl/60TeBP for more details
+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")
+ }
+ path := fmt.Sprintf("/containers/%s/exec", opts.Container)
+ resp, err := c.do("POST", path, doOptions{data: opts, context: opts.Context})
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return nil, &NoSuchContainer{ID: opts.Container}
+ }
+ return nil, err
+ }
+ defer resp.Body.Close()
+ var exec Exec
+ if err := json.NewDecoder(resp.Body).Decode(&exec); err != nil {
+ return nil, err
+ }
+
+ return &exec, nil
+}
+
+// StartExecOptions specify parameters to the StartExecContainer function.
+//
+// See https://goo.gl/1EeDWi for more details
+type StartExecOptions struct {
+ InputStream io.Reader `qs:"-"`
+ OutputStream io.Writer `qs:"-"`
+ ErrorStream io.Writer `qs:"-"`
+
+ Detach bool `json:"Detach,omitempty" yaml:"Detach,omitempty" toml:"Detach,omitempty"`
+ Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty" toml:"Tty,omitempty"`
+
+ // Use raw terminal? Usually true when the container contains a TTY.
+ RawTerminal bool `qs:"-"`
+
+ // If set, after a successful connect, a sentinel will be sent and then the
+ // client will block on receive before continuing.
+ //
+ // It must be an unbuffered channel. Using a buffered channel can lead
+ // to unexpected behavior.
+ Success chan struct{} `json:"-"`
+
+ Context context.Context `json:"-"`
+}
+
+// StartExec starts a previously set up exec instance id. If opts.Detach is
+// true, it returns after starting the exec command. Otherwise, it sets up an
+// interactive session with the exec command.
+//
+// See https://goo.gl/1EeDWi for more details
+func (c *Client) StartExec(id string, opts StartExecOptions) error {
+ cw, err := c.StartExecNonBlocking(id, opts)
+ if err != nil {
+ return err
+ }
+ if cw != nil {
+ return cw.Wait()
+ }
+ return nil
+}
+
+// StartExecNonBlocking starts a previously set up exec instance id. If opts.Detach is
+// true, it returns after starting the exec command. Otherwise, it sets up an
+// interactive session with the exec command.
+//
+// See https://goo.gl/1EeDWi for more details
+func (c *Client) StartExecNonBlocking(id string, opts StartExecOptions) (CloseWaiter, error) {
+ if id == "" {
+ return nil, &NoSuchExec{ID: id}
+ }
+
+ path := fmt.Sprintf("/exec/%s/start", id)
+
+ if opts.Detach {
+ resp, err := c.do("POST", path, doOptions{data: opts, context: opts.Context})
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return nil, &NoSuchExec{ID: id}
+ }
+ return nil, err
+ }
+ defer resp.Body.Close()
+ return nil, nil
+ }
+
+ return c.hijack("POST", path, hijackOptions{
+ success: opts.Success,
+ setRawTerminal: opts.RawTerminal,
+ in: opts.InputStream,
+ stdout: opts.OutputStream,
+ stderr: opts.ErrorStream,
+ data: opts,
+ })
+}
+
+// ResizeExecTTY resizes the tty session used by the exec command id. This API
+// is valid only if Tty was specified as part of creating and starting the exec
+// command.
+//
+// See https://goo.gl/Mo5bxx for more details
+func (c *Client) ResizeExecTTY(id string, height, width int) error {
+ params := make(url.Values)
+ params.Set("h", strconv.Itoa(height))
+ params.Set("w", strconv.Itoa(width))
+
+ path := fmt.Sprintf("/exec/%s/resize?%s", id, params.Encode())
+ resp, err := c.do("POST", path, doOptions{})
+ if err != nil {
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// ExecProcessConfig is a type describing the command associated to a Exec
+// instance. It's used in the ExecInspect type.
+type ExecProcessConfig struct {
+ User string `json:"user,omitempty" yaml:"user,omitempty" toml:"user,omitempty"`
+ Privileged bool `json:"privileged,omitempty" yaml:"privileged,omitempty" toml:"privileged,omitempty"`
+ Tty bool `json:"tty,omitempty" yaml:"tty,omitempty" toml:"tty,omitempty"`
+ EntryPoint string `json:"entrypoint,omitempty" yaml:"entrypoint,omitempty" toml:"entrypoint,omitempty"`
+ Arguments []string `json:"arguments,omitempty" yaml:"arguments,omitempty" toml:"arguments,omitempty"`
+}
+
+// ExecInspect is a type with details about a exec instance, including the
+// exit code if the command has finished running. It's returned by a api
+// call to /exec/(id)/json
+//
+// See https://goo.gl/ctMUiW for more details
+type ExecInspect struct {
+ ID string `json:"ID,omitempty" yaml:"ID,omitempty" toml:"ID,omitempty"`
+ ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty" toml:"ExitCode,omitempty"`
+ Running bool `json:"Running,omitempty" yaml:"Running,omitempty" toml:"Running,omitempty"`
+ OpenStdin bool `json:"OpenStdin,omitempty" yaml:"OpenStdin,omitempty" toml:"OpenStdin,omitempty"`
+ OpenStderr bool `json:"OpenStderr,omitempty" yaml:"OpenStderr,omitempty" toml:"OpenStderr,omitempty"`
+ OpenStdout bool `json:"OpenStdout,omitempty" yaml:"OpenStdout,omitempty" toml:"OpenStdout,omitempty"`
+ ProcessConfig ExecProcessConfig `json:"ProcessConfig,omitempty" yaml:"ProcessConfig,omitempty" toml:"ProcessConfig,omitempty"`
+ ContainerID string `json:"ContainerID,omitempty" yaml:"ContainerID,omitempty" toml:"ContainerID,omitempty"`
+ DetachKeys string `json:"DetachKeys,omitempty" yaml:"DetachKeys,omitempty" toml:"DetachKeys,omitempty"`
+ CanRemove bool `json:"CanRemove,omitempty" yaml:"CanRemove,omitempty" toml:"CanRemove,omitempty"`
+}
+
+// InspectExec returns low-level information about the exec command id.
+//
+// See https://goo.gl/ctMUiW for more details
+func (c *Client) InspectExec(id string) (*ExecInspect, error) {
+ path := fmt.Sprintf("/exec/%s/json", id)
+ resp, err := c.do("GET", path, doOptions{})
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return nil, &NoSuchExec{ID: id}
+ }
+ return nil, err
+ }
+ defer resp.Body.Close()
+ var exec ExecInspect
+ if err := json.NewDecoder(resp.Body).Decode(&exec); err != nil {
+ return nil, err
+ }
+ return &exec, nil
+}
+
+// NoSuchExec is the error returned when a given exec instance does not exist.
+type NoSuchExec struct {
+ ID string
+}
+
+func (err *NoSuchExec) Error() string {
+ return "No such exec instance: " + err.ID
+}
diff --git a/vendor/github.com/fsouza/go-dockerclient/image.go b/vendor/github.com/fsouza/go-dockerclient/image.go
new file mode 100644
index 000000000..124e78da3
--- /dev/null
+++ b/vendor/github.com/fsouza/go-dockerclient/image.go
@@ -0,0 +1,720 @@
+// Copyright 2013 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 (
+ "bytes"
+ "context"
+ "encoding/base64"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "os"
+ "strings"
+ "time"
+)
+
+// APIImages represent an image returned in the ListImages call.
+type APIImages struct {
+ ID string `json:"Id" yaml:"Id" toml:"Id"`
+ RepoTags []string `json:"RepoTags,omitempty" yaml:"RepoTags,omitempty" toml:"RepoTags,omitempty"`
+ Created int64 `json:"Created,omitempty" yaml:"Created,omitempty" toml:"Created,omitempty"`
+ Size int64 `json:"Size,omitempty" yaml:"Size,omitempty" toml:"Size,omitempty"`
+ VirtualSize int64 `json:"VirtualSize,omitempty" yaml:"VirtualSize,omitempty" toml:"VirtualSize,omitempty"`
+ ParentID string `json:"ParentId,omitempty" yaml:"ParentId,omitempty" toml:"ParentId,omitempty"`
+ RepoDigests []string `json:"RepoDigests,omitempty" yaml:"RepoDigests,omitempty" toml:"RepoDigests,omitempty"`
+ Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty" toml:"Labels,omitempty"`
+}
+
+// RootFS represents the underlying layers used by an image
+type RootFS struct {
+ Type string `json:"Type,omitempty" yaml:"Type,omitempty" toml:"Type,omitempty"`
+ Layers []string `json:"Layers,omitempty" yaml:"Layers,omitempty" toml:"Layers,omitempty"`
+}
+
+// Image is the type representing a docker image and its various properties
+type Image struct {
+ ID string `json:"Id" yaml:"Id" toml:"Id"`
+ RepoTags []string `json:"RepoTags,omitempty" yaml:"RepoTags,omitempty" toml:"RepoTags,omitempty"`
+ Parent string `json:"Parent,omitempty" yaml:"Parent,omitempty" toml:"Parent,omitempty"`
+ Comment string `json:"Comment,omitempty" yaml:"Comment,omitempty" toml:"Comment,omitempty"`
+ Created time.Time `json:"Created,omitempty" yaml:"Created,omitempty" toml:"Created,omitempty"`
+ Container string `json:"Container,omitempty" yaml:"Container,omitempty" toml:"Container,omitempty"`
+ ContainerConfig Config `json:"ContainerConfig,omitempty" yaml:"ContainerConfig,omitempty" toml:"ContainerConfig,omitempty"`
+ DockerVersion string `json:"DockerVersion,omitempty" yaml:"DockerVersion,omitempty" toml:"DockerVersion,omitempty"`
+ Author string `json:"Author,omitempty" yaml:"Author,omitempty" toml:"Author,omitempty"`
+ Config *Config `json:"Config,omitempty" yaml:"Config,omitempty" toml:"Config,omitempty"`
+ Architecture string `json:"Architecture,omitempty" yaml:"Architecture,omitempty"`
+ Size int64 `json:"Size,omitempty" yaml:"Size,omitempty" toml:"Size,omitempty"`
+ VirtualSize int64 `json:"VirtualSize,omitempty" yaml:"VirtualSize,omitempty" toml:"VirtualSize,omitempty"`
+ RepoDigests []string `json:"RepoDigests,omitempty" yaml:"RepoDigests,omitempty" toml:"RepoDigests,omitempty"`
+ RootFS *RootFS `json:"RootFS,omitempty" yaml:"RootFS,omitempty" toml:"RootFS,omitempty"`
+ OS string `json:"Os,omitempty" yaml:"Os,omitempty" toml:"Os,omitempty"`
+}
+
+// ImagePre012 serves the same purpose as the Image type except that it is for
+// earlier versions of the Docker API (pre-012 to be specific)
+type ImagePre012 struct {
+ ID string `json:"id"`
+ Parent string `json:"parent,omitempty"`
+ Comment string `json:"comment,omitempty"`
+ Created time.Time `json:"created"`
+ Container string `json:"container,omitempty"`
+ ContainerConfig Config `json:"container_config,omitempty"`
+ DockerVersion string `json:"docker_version,omitempty"`
+ Author string `json:"author,omitempty"`
+ Config *Config `json:"config,omitempty"`
+ Architecture string `json:"architecture,omitempty"`
+ Size int64 `json:"size,omitempty"`
+}
+
+var (
+ // ErrNoSuchImage is the error returned when the image does not exist.
+ ErrNoSuchImage = errors.New("no such image")
+
+ // ErrMissingRepo is the error returned when the remote repository is
+ // missing.
+ ErrMissingRepo = errors.New("missing remote repository e.g. 'github.com/user/repo'")
+
+ // ErrMissingOutputStream is the error returned when no output stream
+ // is provided to some calls, like BuildImage.
+ ErrMissingOutputStream = errors.New("missing output stream")
+
+ // ErrMultipleContexts is the error returned when both a ContextDir and
+ // InputStream are provided in BuildImageOptions
+ ErrMultipleContexts = errors.New("image build may not be provided BOTH context dir and input stream")
+
+ // ErrMustSpecifyNames is the error rreturned when the Names field on
+ // ExportImagesOptions is nil or empty
+ ErrMustSpecifyNames = errors.New("must specify at least one name to export")
+)
+
+// ListImagesOptions specify parameters to the ListImages function.
+//
+// See https://goo.gl/BVzauZ for more details.
+type ListImagesOptions struct {
+ Filters map[string][]string
+ All bool
+ Digests bool
+ Filter string
+ Context context.Context
+}
+
+// ListImages returns the list of available images in the server.
+//
+// See https://goo.gl/BVzauZ for more details.
+func (c *Client) ListImages(opts ListImagesOptions) ([]APIImages, error) {
+ path := "/images/json?" + queryString(opts)
+ resp, err := c.do("GET", path, doOptions{context: opts.Context})
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ var images []APIImages
+ if err := json.NewDecoder(resp.Body).Decode(&images); err != nil {
+ return nil, err
+ }
+ return images, nil
+}
+
+// ImageHistory represent a layer in an image's history returned by the
+// ImageHistory call.
+type ImageHistory struct {
+ ID string `json:"Id" yaml:"Id" toml:"Id"`
+ Tags []string `json:"Tags,omitempty" yaml:"Tags,omitempty" toml:"Tags,omitempty"`
+ Created int64 `json:"Created,omitempty" yaml:"Created,omitempty" toml:"Tags,omitempty"`
+ CreatedBy string `json:"CreatedBy,omitempty" yaml:"CreatedBy,omitempty" toml:"CreatedBy,omitempty"`
+ Size int64 `json:"Size,omitempty" yaml:"Size,omitempty" toml:"Size,omitempty"`
+}
+
+// ImageHistory returns the history of the image by its name or ID.
+//
+// See https://goo.gl/fYtxQa for more details.
+func (c *Client) ImageHistory(name string) ([]ImageHistory, error) {
+ resp, err := c.do("GET", "/images/"+name+"/history", doOptions{})
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return nil, ErrNoSuchImage
+ }
+ return nil, err
+ }
+ defer resp.Body.Close()
+ var history []ImageHistory
+ if err := json.NewDecoder(resp.Body).Decode(&history); err != nil {
+ return nil, err
+ }
+ return history, nil
+}
+
+// RemoveImage removes an image by its name or ID.
+//
+// See https://goo.gl/Vd2Pck for more details.
+func (c *Client) RemoveImage(name string) error {
+ resp, err := c.do("DELETE", "/images/"+name, doOptions{})
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return ErrNoSuchImage
+ }
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// RemoveImageOptions present the set of options available for removing an image
+// from a registry.
+//
+// See https://goo.gl/Vd2Pck for more details.
+type RemoveImageOptions struct {
+ Force bool `qs:"force"`
+ NoPrune bool `qs:"noprune"`
+ Context context.Context
+}
+
+// RemoveImageExtended removes an image by its name or ID.
+// Extra params can be passed, see RemoveImageOptions
+//
+// See https://goo.gl/Vd2Pck for more details.
+func (c *Client) RemoveImageExtended(name string, opts RemoveImageOptions) error {
+ uri := fmt.Sprintf("/images/%s?%s", name, queryString(&opts))
+ resp, err := c.do("DELETE", uri, doOptions{context: opts.Context})
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return ErrNoSuchImage
+ }
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// InspectImage returns an image by its name or ID.
+//
+// See https://goo.gl/ncLTG8 for more details.
+func (c *Client) InspectImage(name string) (*Image, error) {
+ resp, err := c.do("GET", "/images/"+name+"/json", doOptions{})
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return nil, ErrNoSuchImage
+ }
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ var image Image
+
+ // if the caller elected to skip checking the server's version, assume it's the latest
+ if c.SkipServerVersionCheck || c.expectedAPIVersion.GreaterThanOrEqualTo(apiVersion112) {
+ if err := json.NewDecoder(resp.Body).Decode(&image); err != nil {
+ return nil, err
+ }
+ } else {
+ var imagePre012 ImagePre012
+ if err := json.NewDecoder(resp.Body).Decode(&imagePre012); err != nil {
+ return nil, err
+ }
+
+ image.ID = imagePre012.ID
+ image.Parent = imagePre012.Parent
+ image.Comment = imagePre012.Comment
+ image.Created = imagePre012.Created
+ image.Container = imagePre012.Container
+ image.ContainerConfig = imagePre012.ContainerConfig
+ image.DockerVersion = imagePre012.DockerVersion
+ image.Author = imagePre012.Author
+ image.Config = imagePre012.Config
+ image.Architecture = imagePre012.Architecture
+ image.Size = imagePre012.Size
+ }
+
+ return &image, nil
+}
+
+// PushImageOptions represents options to use in the PushImage method.
+//
+// See https://goo.gl/BZemGg for more details.
+type PushImageOptions struct {
+ // Name of the image
+ Name string
+
+ // Tag of the image
+ Tag string
+
+ // Registry server to push the image
+ Registry string
+
+ OutputStream io.Writer `qs:"-"`
+ RawJSONStream bool `qs:"-"`
+ InactivityTimeout time.Duration `qs:"-"`
+
+ Context context.Context
+}
+
+// PushImage pushes an image to a remote registry, logging progress to w.
+//
+// An empty instance of AuthConfiguration may be used for unauthenticated
+// pushes.
+//
+// See https://goo.gl/BZemGg for more details.
+func (c *Client) PushImage(opts PushImageOptions, auth AuthConfiguration) error {
+ if opts.Name == "" {
+ return ErrNoSuchImage
+ }
+ headers, err := headersWithAuth(auth)
+ if err != nil {
+ return err
+ }
+ name := opts.Name
+ opts.Name = ""
+ path := "/images/" + name + "/push?" + queryString(&opts)
+ return c.stream("POST", path, streamOptions{
+ setRawTerminal: true,
+ rawJSONStream: opts.RawJSONStream,
+ headers: headers,
+ stdout: opts.OutputStream,
+ inactivityTimeout: opts.InactivityTimeout,
+ context: opts.Context,
+ })
+}
+
+// PullImageOptions present the set of options available for pulling an image
+// from a registry.
+//
+// See https://goo.gl/qkoSsn for more details.
+type PullImageOptions struct {
+ Repository string `qs:"fromImage"`
+ Tag string
+
+ // Only required for Docker Engine 1.9 or 1.10 w/ Remote API < 1.21
+ // and Docker Engine < 1.9
+ // This parameter was removed in Docker Engine 1.11
+ Registry string
+
+ OutputStream io.Writer `qs:"-"`
+ RawJSONStream bool `qs:"-"`
+ InactivityTimeout time.Duration `qs:"-"`
+ Context context.Context
+}
+
+// PullImage pulls an image from a remote registry, logging progress to
+// opts.OutputStream.
+//
+// See https://goo.gl/qkoSsn for more details.
+func (c *Client) PullImage(opts PullImageOptions, auth AuthConfiguration) error {
+ if opts.Repository == "" {
+ return ErrNoSuchImage
+ }
+
+ headers, err := headersWithAuth(auth)
+ if err != nil {
+ return err
+ }
+ if opts.Tag == "" && strings.Contains(opts.Repository, "@") {
+ parts := strings.SplitN(opts.Repository, "@", 2)
+ opts.Repository = parts[0]
+ opts.Tag = parts[1]
+ }
+ return c.createImage(queryString(&opts), headers, nil, opts.OutputStream, opts.RawJSONStream, opts.InactivityTimeout, opts.Context)
+}
+
+func (c *Client) createImage(qs string, headers map[string]string, in io.Reader, w io.Writer, rawJSONStream bool, timeout time.Duration, context context.Context) error {
+ path := "/images/create?" + qs
+ return c.stream("POST", path, streamOptions{
+ setRawTerminal: true,
+ headers: headers,
+ in: in,
+ stdout: w,
+ rawJSONStream: rawJSONStream,
+ inactivityTimeout: timeout,
+ context: context,
+ })
+}
+
+// LoadImageOptions represents the options for LoadImage Docker API Call
+//
+// See https://goo.gl/rEsBV3 for more details.
+type LoadImageOptions struct {
+ InputStream io.Reader
+ OutputStream io.Writer
+ Context context.Context
+}
+
+// LoadImage imports a tarball docker image
+//
+// See https://goo.gl/rEsBV3 for more details.
+func (c *Client) LoadImage(opts LoadImageOptions) error {
+ return c.stream("POST", "/images/load", streamOptions{
+ setRawTerminal: true,
+ in: opts.InputStream,
+ stdout: opts.OutputStream,
+ context: opts.Context,
+ })
+}
+
+// ExportImageOptions represent the options for ExportImage Docker API call.
+//
+// See https://goo.gl/AuySaA for more details.
+type ExportImageOptions struct {
+ Name string
+ OutputStream io.Writer
+ InactivityTimeout time.Duration
+ Context context.Context
+}
+
+// ExportImage exports an image (as a tar file) into the stream.
+//
+// See https://goo.gl/AuySaA for more details.
+func (c *Client) ExportImage(opts ExportImageOptions) error {
+ return c.stream("GET", fmt.Sprintf("/images/%s/get", opts.Name), streamOptions{
+ setRawTerminal: true,
+ stdout: opts.OutputStream,
+ inactivityTimeout: opts.InactivityTimeout,
+ context: opts.Context,
+ })
+}
+
+// ExportImagesOptions represent the options for ExportImages Docker API call
+//
+// See https://goo.gl/N9XlDn for more details.
+type ExportImagesOptions struct {
+ Names []string
+ OutputStream io.Writer `qs:"-"`
+ InactivityTimeout time.Duration `qs:"-"`
+ Context context.Context
+}
+
+// ExportImages exports one or more images (as a tar file) into the stream
+//
+// See https://goo.gl/N9XlDn for more details.
+func (c *Client) ExportImages(opts ExportImagesOptions) error {
+ if opts.Names == nil || len(opts.Names) == 0 {
+ return ErrMustSpecifyNames
+ }
+ return c.stream("GET", "/images/get?"+queryString(&opts), streamOptions{
+ setRawTerminal: true,
+ stdout: opts.OutputStream,
+ inactivityTimeout: opts.InactivityTimeout,
+ })
+}
+
+// ImportImageOptions present the set of informations available for importing
+// an image from a source file or the stdin.
+//
+// See https://goo.gl/qkoSsn for more details.
+type ImportImageOptions struct {
+ Repository string `qs:"repo"`
+ Source string `qs:"fromSrc"`
+ Tag string `qs:"tag"`
+
+ InputStream io.Reader `qs:"-"`
+ OutputStream io.Writer `qs:"-"`
+ RawJSONStream bool `qs:"-"`
+ InactivityTimeout time.Duration `qs:"-"`
+ Context context.Context
+}
+
+// ImportImage imports an image from a url, a file or stdin
+//
+// See https://goo.gl/qkoSsn for more details.
+func (c *Client) ImportImage(opts ImportImageOptions) error {
+ if opts.Repository == "" {
+ return ErrNoSuchImage
+ }
+ if opts.Source != "-" {
+ opts.InputStream = nil
+ }
+ if opts.Source != "-" && !isURL(opts.Source) {
+ f, err := os.Open(opts.Source)
+ if err != nil {
+ return err
+ }
+ opts.InputStream = f
+ opts.Source = "-"
+ }
+ return c.createImage(queryString(&opts), nil, opts.InputStream, opts.OutputStream, opts.RawJSONStream, opts.InactivityTimeout, opts.Context)
+}
+
+// BuildImageOptions present the set of informations available for building an
+// image from a tarfile with a Dockerfile in it.
+//
+// For more details about the Docker building process, see
+// https://goo.gl/4nYHwV.
+type BuildImageOptions struct {
+ Name string `qs:"t"`
+ Dockerfile string `qs:"dockerfile"`
+ NoCache bool `qs:"nocache"`
+ CacheFrom []string `qs:"-"`
+ SuppressOutput bool `qs:"q"`
+ Pull bool `qs:"pull"`
+ RmTmpContainer bool `qs:"rm"`
+ ForceRmTmpContainer bool `qs:"forcerm"`
+ RawJSONStream bool `qs:"-"`
+ Memory int64 `qs:"memory"`
+ Memswap int64 `qs:"memswap"`
+ CPUShares int64 `qs:"cpushares"`
+ CPUQuota int64 `qs:"cpuquota"`
+ CPUPeriod int64 `qs:"cpuperiod"`
+ CPUSetCPUs string `qs:"cpusetcpus"`
+ Labels map[string]string `qs:"labels"`
+ InputStream io.Reader `qs:"-"`
+ OutputStream io.Writer `qs:"-"`
+ Remote string `qs:"remote"`
+ Auth AuthConfiguration `qs:"-"` // for older docker X-Registry-Auth header
+ AuthConfigs AuthConfigurations `qs:"-"` // for newer docker X-Registry-Config header
+ ContextDir string `qs:"-"`
+ Ulimits []ULimit `qs:"-"`
+ BuildArgs []BuildArg `qs:"-"`
+ NetworkMode string `qs:"networkmode"`
+ InactivityTimeout time.Duration `qs:"-"`
+ CgroupParent string `qs:"cgroupparent"`
+ SecurityOpt []string `qs:"securityopt"`
+ Target string `gs:"target"`
+ Context context.Context
+}
+
+// BuildArg represents arguments that can be passed to the image when building
+// it from a Dockerfile.
+//
+// For more details about the Docker building process, see
+// https://goo.gl/4nYHwV.
+type BuildArg struct {
+ Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"`
+ Value string `json:"Value,omitempty" yaml:"Value,omitempty" toml:"Value,omitempty"`
+}
+
+// BuildImage builds an image from a tarball's url or a Dockerfile in the input
+// stream.
+//
+// See https://goo.gl/4nYHwV for more details.
+func (c *Client) BuildImage(opts BuildImageOptions) error {
+ if opts.OutputStream == nil {
+ return ErrMissingOutputStream
+ }
+ headers, err := headersWithAuth(opts.Auth, c.versionedAuthConfigs(opts.AuthConfigs))
+ if err != nil {
+ return err
+ }
+
+ if opts.Remote != "" && opts.Name == "" {
+ opts.Name = opts.Remote
+ }
+ if opts.InputStream != nil || opts.ContextDir != "" {
+ headers["Content-Type"] = "application/tar"
+ } else if opts.Remote == "" {
+ return ErrMissingRepo
+ }
+ if opts.ContextDir != "" {
+ if opts.InputStream != nil {
+ return ErrMultipleContexts
+ }
+ var err error
+ if opts.InputStream, err = createTarStream(opts.ContextDir, opts.Dockerfile); err != nil {
+ return err
+ }
+ }
+ qs := queryString(&opts)
+
+ if c.serverAPIVersion.GreaterThanOrEqualTo(apiVersion125) && len(opts.CacheFrom) > 0 {
+ if b, err := json.Marshal(opts.CacheFrom); err == nil {
+ item := url.Values(map[string][]string{})
+ item.Add("cachefrom", string(b))
+ qs = fmt.Sprintf("%s&%s", qs, item.Encode())
+ }
+ }
+
+ if len(opts.Ulimits) > 0 {
+ if b, err := json.Marshal(opts.Ulimits); err == nil {
+ item := url.Values(map[string][]string{})
+ item.Add("ulimits", string(b))
+ qs = fmt.Sprintf("%s&%s", qs, item.Encode())
+ }
+ }
+
+ if len(opts.BuildArgs) > 0 {
+ v := make(map[string]string)
+ for _, arg := range opts.BuildArgs {
+ v[arg.Name] = arg.Value
+ }
+ if b, err := json.Marshal(v); err == nil {
+ item := url.Values(map[string][]string{})
+ item.Add("buildargs", string(b))
+ qs = fmt.Sprintf("%s&%s", qs, item.Encode())
+ }
+ }
+
+ return c.stream("POST", fmt.Sprintf("/build?%s", qs), streamOptions{
+ setRawTerminal: true,
+ rawJSONStream: opts.RawJSONStream,
+ headers: headers,
+ in: opts.InputStream,
+ stdout: opts.OutputStream,
+ inactivityTimeout: opts.InactivityTimeout,
+ context: opts.Context,
+ })
+}
+
+func (c *Client) versionedAuthConfigs(authConfigs AuthConfigurations) interface{} {
+ if c.serverAPIVersion == nil {
+ c.checkAPIVersion()
+ }
+ if c.serverAPIVersion != nil && c.serverAPIVersion.GreaterThanOrEqualTo(apiVersion119) {
+ return AuthConfigurations119(authConfigs.Configs)
+ }
+ return authConfigs
+}
+
+// TagImageOptions present the set of options to tag an image.
+//
+// See https://goo.gl/prHrvo for more details.
+type TagImageOptions struct {
+ Repo string
+ Tag string
+ Force bool
+ Context context.Context
+}
+
+// TagImage adds a tag to the image identified by the given name.
+//
+// See https://goo.gl/prHrvo for more details.
+func (c *Client) TagImage(name string, opts TagImageOptions) error {
+ if name == "" {
+ return ErrNoSuchImage
+ }
+ resp, err := c.do("POST", "/images/"+name+"/tag?"+queryString(&opts), doOptions{
+ context: opts.Context,
+ })
+
+ if err != nil {
+ return err
+ }
+
+ defer resp.Body.Close()
+
+ if resp.StatusCode == http.StatusNotFound {
+ return ErrNoSuchImage
+ }
+
+ return err
+}
+
+func isURL(u string) bool {
+ p, err := url.Parse(u)
+ if err != nil {
+ return false
+ }
+ return p.Scheme == "http" || p.Scheme == "https"
+}
+
+func headersWithAuth(auths ...interface{}) (map[string]string, error) {
+ var headers = make(map[string]string)
+
+ for _, auth := range auths {
+ switch auth.(type) {
+ case AuthConfiguration:
+ var buf bytes.Buffer
+ if err := json.NewEncoder(&buf).Encode(auth); err != nil {
+ return nil, err
+ }
+ headers["X-Registry-Auth"] = base64.URLEncoding.EncodeToString(buf.Bytes())
+ case AuthConfigurations, AuthConfigurations119:
+ var buf bytes.Buffer
+ if err := json.NewEncoder(&buf).Encode(auth); err != nil {
+ return nil, err
+ }
+ headers["X-Registry-Config"] = base64.URLEncoding.EncodeToString(buf.Bytes())
+ }
+ }
+
+ return headers, nil
+}
+
+// APIImageSearch reflect the result of a search on the Docker Hub.
+//
+// See https://goo.gl/KLO9IZ for more details.
+type APIImageSearch struct {
+ Description string `json:"description,omitempty" yaml:"description,omitempty" toml:"description,omitempty"`
+ IsOfficial bool `json:"is_official,omitempty" yaml:"is_official,omitempty" toml:"is_official,omitempty"`
+ IsAutomated bool `json:"is_automated,omitempty" yaml:"is_automated,omitempty" toml:"is_automated,omitempty"`
+ Name string `json:"name,omitempty" yaml:"name,omitempty" toml:"name,omitempty"`
+ StarCount int `json:"star_count,omitempty" yaml:"star_count,omitempty" toml:"star_count,omitempty"`
+}
+
+// SearchImages search the docker hub with a specific given term.
+//
+// See https://goo.gl/KLO9IZ for more details.
+func (c *Client) SearchImages(term string) ([]APIImageSearch, error) {
+ resp, err := c.do("GET", "/images/search?term="+term, doOptions{})
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ var searchResult []APIImageSearch
+ if err := json.NewDecoder(resp.Body).Decode(&searchResult); err != nil {
+ return nil, err
+ }
+ return searchResult, nil
+}
+
+// SearchImagesEx search the docker hub with a specific given term and authentication.
+//
+// See https://goo.gl/KLO9IZ for more details.
+func (c *Client) SearchImagesEx(term string, auth AuthConfiguration) ([]APIImageSearch, error) {
+ headers, err := headersWithAuth(auth)
+ if err != nil {
+ return nil, err
+ }
+
+ resp, err := c.do("GET", "/images/search?term="+term, doOptions{
+ headers: headers,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ defer resp.Body.Close()
+
+ var searchResult []APIImageSearch
+ if err := json.NewDecoder(resp.Body).Decode(&searchResult); err != nil {
+ return nil, err
+ }
+
+ return searchResult, nil
+}
+
+// PruneImagesOptions specify parameters to the PruneImages function.
+//
+// See https://goo.gl/qfZlbZ for more details.
+type PruneImagesOptions struct {
+ Filters map[string][]string
+ Context context.Context
+}
+
+// PruneImagesResults specify results from the PruneImages function.
+//
+// See https://goo.gl/qfZlbZ for more details.
+type PruneImagesResults struct {
+ ImagesDeleted []struct{ Untagged, Deleted string }
+ SpaceReclaimed int64
+}
+
+// PruneImages deletes images which are unused.
+//
+// See https://goo.gl/qfZlbZ for more details.
+func (c *Client) PruneImages(opts PruneImagesOptions) (*PruneImagesResults, error) {
+ path := "/images/prune?" + queryString(opts)
+ resp, err := c.do("POST", path, doOptions{context: opts.Context})
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ var results PruneImagesResults
+ if err := json.NewDecoder(resp.Body).Decode(&results); err != nil {
+ return nil, err
+ }
+ return &results, nil
+}
diff --git a/vendor/github.com/fsouza/go-dockerclient/misc.go b/vendor/github.com/fsouza/go-dockerclient/misc.go
new file mode 100644
index 000000000..1fc37b14e
--- /dev/null
+++ b/vendor/github.com/fsouza/go-dockerclient/misc.go
@@ -0,0 +1,188 @@
+// Copyright 2013 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"
+ "strings"
+
+ "github.com/docker/docker/api/types/swarm"
+)
+
+// Version returns version information about the docker server.
+//
+// See https://goo.gl/mU7yje for more details.
+func (c *Client) Version() (*Env, error) {
+ 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
+ }
+ defer resp.Body.Close()
+ var env Env
+ if err := env.Decode(resp.Body); err != nil {
+ return nil, err
+ }
+ return &env, nil
+}
+
+// DockerInfo contains information about the Docker server
+//
+// See https://goo.gl/bHUoz9 for more details.
+type DockerInfo struct {
+ ID string
+ Containers int
+ ContainersRunning int
+ ContainersPaused int
+ ContainersStopped int
+ Images int
+ Driver string
+ DriverStatus [][2]string
+ SystemStatus [][2]string
+ Plugins PluginsInfo
+ MemoryLimit bool
+ SwapLimit bool
+ KernelMemory bool
+ CPUCfsPeriod bool `json:"CpuCfsPeriod"`
+ CPUCfsQuota bool `json:"CpuCfsQuota"`
+ CPUShares bool
+ CPUSet bool
+ IPv4Forwarding bool
+ BridgeNfIptables bool
+ BridgeNfIP6tables bool `json:"BridgeNfIp6tables"`
+ Debug bool
+ OomKillDisable bool
+ ExperimentalBuild bool
+ NFd int
+ NGoroutines int
+ SystemTime string
+ ExecutionDriver string
+ LoggingDriver string
+ CgroupDriver string
+ NEventsListener int
+ KernelVersion string
+ OperatingSystem string
+ OSType string
+ Architecture string
+ IndexServerAddress string
+ RegistryConfig *ServiceConfig
+ SecurityOptions []string
+ NCPU int
+ MemTotal int64
+ DockerRootDir string
+ HTTPProxy string `json:"HttpProxy"`
+ HTTPSProxy string `json:"HttpsProxy"`
+ NoProxy string
+ Name string
+ Labels []string
+ ServerVersion string
+ ClusterStore string
+ ClusterAdvertise string
+ Isolation string
+ InitBinary string
+ DefaultRuntime string
+ LiveRestoreEnabled bool
+ Swarm swarm.Info
+}
+
+// PluginsInfo is a struct with the plugins registered with the docker daemon
+//
+// for more information, see: https://goo.gl/bHUoz9
+type PluginsInfo struct {
+ // List of Volume plugins registered
+ Volume []string
+ // List of Network plugins registered
+ Network []string
+ // List of Authorization plugins registered
+ Authorization []string
+}
+
+// ServiceConfig stores daemon registry services configuration.
+//
+// for more information, see: https://goo.gl/7iFFDz
+type ServiceConfig struct {
+ InsecureRegistryCIDRs []*NetIPNet
+ IndexConfigs map[string]*IndexInfo
+ Mirrors []string
+}
+
+// NetIPNet is the net.IPNet type, which can be marshalled and
+// unmarshalled to JSON.
+//
+// for more information, see: https://goo.gl/7iFFDz
+type NetIPNet net.IPNet
+
+// MarshalJSON returns the JSON representation of the IPNet.
+//
+func (ipnet *NetIPNet) MarshalJSON() ([]byte, error) {
+ return json.Marshal((*net.IPNet)(ipnet).String())
+}
+
+// UnmarshalJSON sets the IPNet from a byte array of JSON.
+//
+func (ipnet *NetIPNet) UnmarshalJSON(b []byte) (err error) {
+ var ipnetStr string
+ if err = json.Unmarshal(b, &ipnetStr); err == nil {
+ var cidr *net.IPNet
+ if _, cidr, err = net.ParseCIDR(ipnetStr); err == nil {
+ *ipnet = NetIPNet(*cidr)
+ }
+ }
+ return
+}
+
+// IndexInfo contains information about a registry.
+//
+// for more information, see: https://goo.gl/7iFFDz
+type IndexInfo struct {
+ Name string
+ Mirrors []string
+ Secure bool
+ Official bool
+}
+
+// Info returns system-wide information about the Docker server.
+//
+// See https://goo.gl/ElTHi2 for more details.
+func (c *Client) Info() (*DockerInfo, error) {
+ resp, err := c.do("GET", "/info", doOptions{})
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ var info DockerInfo
+ if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
+ return nil, err
+ }
+ return &info, nil
+}
+
+// ParseRepositoryTag gets the name of the repository and returns it splitted
+// in two parts: the repository and the tag. It ignores the digest when it is
+// present.
+//
+// Some examples:
+//
+// localhost.localdomain:5000/samalba/hipache:latest -> localhost.localdomain:5000/samalba/hipache, latest
+// localhost.localdomain:5000/samalba/hipache -> localhost.localdomain:5000/samalba/hipache, ""
+// busybox:latest@sha256:4a731fb46adc5cefe3ae374a8b6020fc1b6ad667a279647766e9a3cd89f6fa92 -> busybox, latest
+func ParseRepositoryTag(repoTag string) (repository string, tag string) {
+ parts := strings.SplitN(repoTag, "@", 2)
+ repoTag = parts[0]
+ n := strings.LastIndex(repoTag, ":")
+ if n < 0 {
+ return repoTag, ""
+ }
+ if tag := repoTag[n+1:]; !strings.Contains(tag, "/") {
+ return repoTag[:n], tag
+ }
+ return repoTag, ""
+}
diff --git a/vendor/github.com/fsouza/go-dockerclient/network.go b/vendor/github.com/fsouza/go-dockerclient/network.go
new file mode 100644
index 000000000..c6ddb22c6
--- /dev/null
+++ b/vendor/github.com/fsouza/go-dockerclient/network.go
@@ -0,0 +1,321 @@
+// Copyright 2015 go-dockerclient authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package docker
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/http"
+)
+
+// ErrNetworkAlreadyExists is the error returned by CreateNetwork when the
+// network already exists.
+var ErrNetworkAlreadyExists = errors.New("network already exists")
+
+// Network represents a network.
+//
+// See https://goo.gl/6GugX3 for more details.
+type Network struct {
+ Name string
+ ID string `json:"Id"`
+ Scope string
+ Driver string
+ IPAM IPAMOptions
+ Containers map[string]Endpoint
+ Options map[string]string
+ Internal bool
+ EnableIPv6 bool `json:"EnableIPv6"`
+ Labels map[string]string
+}
+
+// Endpoint contains network resources allocated and used for a container in a network
+//
+// See https://goo.gl/6GugX3 for more details.
+type Endpoint struct {
+ Name string
+ ID string `json:"EndpointID"`
+ MacAddress string
+ IPv4Address string
+ IPv6Address string
+}
+
+// ListNetworks returns all networks.
+//
+// See https://goo.gl/6GugX3 for more details.
+func (c *Client) ListNetworks() ([]Network, error) {
+ resp, err := c.do("GET", "/networks", doOptions{})
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ var networks []Network
+ if err := json.NewDecoder(resp.Body).Decode(&networks); err != nil {
+ return nil, err
+ }
+ return networks, nil
+}
+
+// NetworkFilterOpts is an aggregation of key=value that Docker
+// uses to filter networks
+type NetworkFilterOpts map[string]map[string]bool
+
+// FilteredListNetworks returns all networks with the filters applied
+//
+// See goo.gl/zd2mx4 for more details.
+func (c *Client) FilteredListNetworks(opts NetworkFilterOpts) ([]Network, error) {
+ params, err := json.Marshal(opts)
+ if err != nil {
+ return nil, err
+ }
+ path := "/networks?filters=" + string(params)
+ resp, err := c.do("GET", path, doOptions{})
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ var networks []Network
+ if err := json.NewDecoder(resp.Body).Decode(&networks); err != nil {
+ return nil, err
+ }
+ return networks, nil
+}
+
+// NetworkInfo returns information about a network by its ID.
+//
+// See https://goo.gl/6GugX3 for more details.
+func (c *Client) NetworkInfo(id string) (*Network, error) {
+ path := "/networks/" + id
+ resp, err := c.do("GET", path, doOptions{})
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return nil, &NoSuchNetwork{ID: id}
+ }
+ return nil, err
+ }
+ defer resp.Body.Close()
+ var network Network
+ if err := json.NewDecoder(resp.Body).Decode(&network); err != nil {
+ return nil, err
+ }
+ return &network, nil
+}
+
+// CreateNetworkOptions specify parameters to the CreateNetwork function and
+// (for now) is the expected body of the "create network" http request message
+//
+// See https://goo.gl/6GugX3 for more details.
+type CreateNetworkOptions struct {
+ Name string `json:"Name" yaml:"Name" toml:"Name"`
+ Driver string `json:"Driver" yaml:"Driver" toml:"Driver"`
+ 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"`
+ Internal bool `json:"Internal" yaml:"Internal" toml:"Internal"`
+ EnableIPv6 bool `json:"EnableIPv6" yaml:"EnableIPv6" toml:"EnableIPv6"`
+ Context context.Context `json:"-"`
+}
+
+// IPAMOptions controls IP Address Management when creating a network
+//
+// See https://goo.gl/T8kRVH for more details.
+type IPAMOptions struct {
+ Driver string `json:"Driver" yaml:"Driver" toml:"Driver"`
+ Config []IPAMConfig `json:"Config" yaml:"Config" toml:"Config"`
+ Options map[string]string `json:"Options" yaml:"Options" toml:"Options"`
+}
+
+// IPAMConfig represents IPAM configurations
+//
+// See https://goo.gl/T8kRVH for more details.
+type IPAMConfig struct {
+ Subnet string `json:",omitempty"`
+ IPRange string `json:",omitempty"`
+ Gateway string `json:",omitempty"`
+ AuxAddress map[string]string `json:"AuxiliaryAddresses,omitempty"`
+}
+
+// CreateNetwork creates a new network, returning the network instance,
+// or an error in case of failure.
+//
+// See https://goo.gl/6GugX3 for more details.
+func (c *Client) CreateNetwork(opts CreateNetworkOptions) (*Network, error) {
+ resp, err := c.do(
+ "POST",
+ "/networks/create",
+ doOptions{
+ data: opts,
+ context: opts.Context,
+ },
+ )
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ type createNetworkResponse struct {
+ ID string
+ }
+ var (
+ network Network
+ cnr createNetworkResponse
+ )
+ if err := json.NewDecoder(resp.Body).Decode(&cnr); err != nil {
+ return nil, err
+ }
+
+ network.Name = opts.Name
+ network.ID = cnr.ID
+ network.Driver = opts.Driver
+
+ return &network, nil
+}
+
+// RemoveNetwork removes a network or returns an error in case of failure.
+//
+// See https://goo.gl/6GugX3 for more details.
+func (c *Client) RemoveNetwork(id string) error {
+ resp, err := c.do("DELETE", "/networks/"+id, doOptions{})
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return &NoSuchNetwork{ID: id}
+ }
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// NetworkConnectionOptions specify parameters to the ConnectNetwork and
+// DisconnectNetwork function.
+//
+// See https://goo.gl/RV7BJU for more details.
+type NetworkConnectionOptions struct {
+ Container string
+
+ // EndpointConfig is only applicable to the ConnectNetwork call
+ EndpointConfig *EndpointConfig `json:"EndpointConfig,omitempty"`
+
+ // Force is only applicable to the DisconnectNetwork call
+ Force bool
+
+ Context context.Context `json:"-"`
+}
+
+// EndpointConfig stores network endpoint details
+//
+// See https://goo.gl/RV7BJU for more details.
+type EndpointConfig struct {
+ IPAMConfig *EndpointIPAMConfig `json:"IPAMConfig,omitempty" yaml:"IPAMConfig,omitempty" toml:"IPAMConfig,omitempty"`
+ Links []string `json:"Links,omitempty" yaml:"Links,omitempty" toml:"Links,omitempty"`
+ Aliases []string `json:"Aliases,omitempty" yaml:"Aliases,omitempty" toml:"Aliases,omitempty"`
+ NetworkID string `json:"NetworkID,omitempty" yaml:"NetworkID,omitempty" toml:"NetworkID,omitempty"`
+ EndpointID string `json:"EndpointID,omitempty" yaml:"EndpointID,omitempty" toml:"EndpointID,omitempty"`
+ Gateway string `json:"Gateway,omitempty" yaml:"Gateway,omitempty" toml:"Gateway,omitempty"`
+ IPAddress string `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty" toml:"IPAddress,omitempty"`
+ IPPrefixLen int `json:"IPPrefixLen,omitempty" yaml:"IPPrefixLen,omitempty" toml:"IPPrefixLen,omitempty"`
+ IPv6Gateway string `json:"IPv6Gateway,omitempty" yaml:"IPv6Gateway,omitempty" toml:"IPv6Gateway,omitempty"`
+ GlobalIPv6Address string `json:"GlobalIPv6Address,omitempty" yaml:"GlobalIPv6Address,omitempty" toml:"GlobalIPv6Address,omitempty"`
+ GlobalIPv6PrefixLen int `json:"GlobalIPv6PrefixLen,omitempty" yaml:"GlobalIPv6PrefixLen,omitempty" toml:"GlobalIPv6PrefixLen,omitempty"`
+ MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty" toml:"MacAddress,omitempty"`
+}
+
+// EndpointIPAMConfig represents IPAM configurations for an
+// endpoint
+//
+// See https://goo.gl/RV7BJU for more details.
+type EndpointIPAMConfig struct {
+ IPv4Address string `json:",omitempty"`
+ IPv6Address string `json:",omitempty"`
+}
+
+// ConnectNetwork adds a container to a network or returns an error in case of
+// failure.
+//
+// See https://goo.gl/6GugX3 for more details.
+func (c *Client) ConnectNetwork(id string, opts NetworkConnectionOptions) error {
+ resp, err := c.do("POST", "/networks/"+id+"/connect", doOptions{
+ data: opts,
+ context: opts.Context,
+ })
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return &NoSuchNetworkOrContainer{NetworkID: id, ContainerID: opts.Container}
+ }
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// DisconnectNetwork removes a container from a network or returns an error in
+// case of failure.
+//
+// See https://goo.gl/6GugX3 for more details.
+func (c *Client) DisconnectNetwork(id string, opts NetworkConnectionOptions) error {
+ resp, err := c.do("POST", "/networks/"+id+"/disconnect", doOptions{data: opts})
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return &NoSuchNetworkOrContainer{NetworkID: id, ContainerID: opts.Container}
+ }
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// PruneNetworksOptions specify parameters to the PruneNetworks function.
+//
+// See https://goo.gl/kX0S9h for more details.
+type PruneNetworksOptions struct {
+ Filters map[string][]string
+ Context context.Context
+}
+
+// PruneNetworksResults specify results from the PruneNetworks function.
+//
+// See https://goo.gl/kX0S9h for more details.
+type PruneNetworksResults struct {
+ NetworksDeleted []string
+}
+
+// PruneNetworks deletes networks which are unused.
+//
+// See https://goo.gl/kX0S9h for more details.
+func (c *Client) PruneNetworks(opts PruneNetworksOptions) (*PruneNetworksResults, error) {
+ path := "/networks/prune?" + queryString(opts)
+ resp, err := c.do("POST", path, doOptions{context: opts.Context})
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ var results PruneNetworksResults
+ if err := json.NewDecoder(resp.Body).Decode(&results); err != nil {
+ return nil, err
+ }
+ return &results, nil
+}
+
+// NoSuchNetwork is the error returned when a given network does not exist.
+type NoSuchNetwork struct {
+ ID string
+}
+
+func (err *NoSuchNetwork) Error() string {
+ return fmt.Sprintf("No such network: %s", err.ID)
+}
+
+// NoSuchNetworkOrContainer is the error returned when a given network or
+// container does not exist.
+type NoSuchNetworkOrContainer struct {
+ NetworkID string
+ ContainerID string
+}
+
+func (err *NoSuchNetworkOrContainer) Error() string {
+ return fmt.Sprintf("No such network (%s) or container (%s)", err.NetworkID, err.ContainerID)
+}
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/signal.go b/vendor/github.com/fsouza/go-dockerclient/signal.go
new file mode 100644
index 000000000..16aa00388
--- /dev/null
+++ b/vendor/github.com/fsouza/go-dockerclient/signal.go
@@ -0,0 +1,49 @@
+// Copyright 2014 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
+
+// Signal represents a signal that can be send to the container on
+// KillContainer call.
+type Signal int
+
+// These values represent all signals available on Linux, where containers will
+// be running.
+const (
+ SIGABRT = Signal(0x6)
+ SIGALRM = Signal(0xe)
+ SIGBUS = Signal(0x7)
+ SIGCHLD = Signal(0x11)
+ SIGCLD = Signal(0x11)
+ SIGCONT = Signal(0x12)
+ SIGFPE = Signal(0x8)
+ SIGHUP = Signal(0x1)
+ SIGILL = Signal(0x4)
+ SIGINT = Signal(0x2)
+ SIGIO = Signal(0x1d)
+ SIGIOT = Signal(0x6)
+ SIGKILL = Signal(0x9)
+ SIGPIPE = Signal(0xd)
+ SIGPOLL = Signal(0x1d)
+ SIGPROF = Signal(0x1b)
+ SIGPWR = Signal(0x1e)
+ SIGQUIT = Signal(0x3)
+ SIGSEGV = Signal(0xb)
+ SIGSTKFLT = Signal(0x10)
+ SIGSTOP = Signal(0x13)
+ SIGSYS = Signal(0x1f)
+ SIGTERM = Signal(0xf)
+ SIGTRAP = Signal(0x5)
+ SIGTSTP = Signal(0x14)
+ SIGTTIN = Signal(0x15)
+ SIGTTOU = Signal(0x16)
+ SIGUNUSED = Signal(0x1f)
+ SIGURG = Signal(0x17)
+ SIGUSR1 = Signal(0xa)
+ SIGUSR2 = Signal(0xc)
+ SIGVTALRM = Signal(0x1a)
+ SIGWINCH = Signal(0x1c)
+ SIGXCPU = Signal(0x18)
+ SIGXFSZ = Signal(0x19)
+)
diff --git a/vendor/github.com/fsouza/go-dockerclient/swarm.go b/vendor/github.com/fsouza/go-dockerclient/swarm.go
new file mode 100644
index 000000000..a257758fc
--- /dev/null
+++ b/vendor/github.com/fsouza/go-dockerclient/swarm.go
@@ -0,0 +1,156 @@
+// 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"
+ "errors"
+ "net/http"
+ "net/url"
+ "strconv"
+
+ "github.com/docker/docker/api/types/swarm"
+)
+
+var (
+ // ErrNodeAlreadyInSwarm is the error returned by InitSwarm and JoinSwarm
+ // when the node is already part of a Swarm.
+ ErrNodeAlreadyInSwarm = errors.New("node already in a Swarm")
+
+ // ErrNodeNotInSwarm is the error returned by LeaveSwarm and UpdateSwarm
+ // when the node is not part of a Swarm.
+ ErrNodeNotInSwarm = errors.New("node is not in a Swarm")
+)
+
+// InitSwarmOptions specify parameters to the InitSwarm function.
+// See https://goo.gl/hzkgWu for more details.
+type InitSwarmOptions struct {
+ swarm.InitRequest
+ Context context.Context
+}
+
+// InitSwarm initializes a new Swarm and returns the node ID.
+// See https://goo.gl/ZWyG1M for more details.
+func (c *Client) InitSwarm(opts InitSwarmOptions) (string, error) {
+ path := "/swarm/init"
+ resp, err := c.do("POST", path, doOptions{
+ data: opts.InitRequest,
+ forceJSON: true,
+ context: opts.Context,
+ })
+ if err != nil {
+ if e, ok := err.(*Error); ok && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) {
+ return "", ErrNodeAlreadyInSwarm
+ }
+ return "", err
+ }
+ defer resp.Body.Close()
+ var response string
+ if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
+ return "", err
+ }
+ return response, nil
+}
+
+// JoinSwarmOptions specify parameters to the JoinSwarm function.
+// See https://goo.gl/TdhJWU for more details.
+type JoinSwarmOptions struct {
+ swarm.JoinRequest
+ Context context.Context
+}
+
+// JoinSwarm joins an existing Swarm.
+// See https://goo.gl/N59IP1 for more details.
+func (c *Client) JoinSwarm(opts JoinSwarmOptions) error {
+ path := "/swarm/join"
+ resp, err := c.do("POST", path, doOptions{
+ data: opts.JoinRequest,
+ forceJSON: true,
+ context: opts.Context,
+ })
+ if err != nil {
+ if e, ok := err.(*Error); ok && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) {
+ return ErrNodeAlreadyInSwarm
+ }
+ }
+ resp.Body.Close()
+ return err
+}
+
+// LeaveSwarmOptions specify parameters to the LeaveSwarm function.
+// See https://goo.gl/UWDlLg for more details.
+type LeaveSwarmOptions struct {
+ Force bool
+ Context context.Context
+}
+
+// LeaveSwarm leaves a Swarm.
+// See https://goo.gl/FTX1aD for more details.
+func (c *Client) LeaveSwarm(opts LeaveSwarmOptions) error {
+ params := make(url.Values)
+ params.Set("force", strconv.FormatBool(opts.Force))
+ path := "/swarm/leave?" + params.Encode()
+ resp, err := c.do("POST", path, doOptions{
+ context: opts.Context,
+ })
+ if err != nil {
+ if e, ok := err.(*Error); ok && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) {
+ return ErrNodeNotInSwarm
+ }
+ }
+ resp.Body.Close()
+ return err
+}
+
+// UpdateSwarmOptions specify parameters to the UpdateSwarm function.
+// See https://goo.gl/vFbq36 for more details.
+type UpdateSwarmOptions struct {
+ Version int
+ RotateWorkerToken bool
+ RotateManagerToken bool
+ Swarm swarm.Spec
+ Context context.Context
+}
+
+// UpdateSwarm updates a Swarm.
+// See https://goo.gl/iJFnsw for more details.
+func (c *Client) UpdateSwarm(opts UpdateSwarmOptions) error {
+ params := make(url.Values)
+ params.Set("version", strconv.Itoa(opts.Version))
+ params.Set("rotateWorkerToken", strconv.FormatBool(opts.RotateWorkerToken))
+ params.Set("rotateManagerToken", strconv.FormatBool(opts.RotateManagerToken))
+ path := "/swarm/update?" + params.Encode()
+ resp, err := c.do("POST", path, doOptions{
+ data: opts.Swarm,
+ forceJSON: true,
+ context: opts.Context,
+ })
+ if err != nil {
+ if e, ok := err.(*Error); ok && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) {
+ return ErrNodeNotInSwarm
+ }
+ }
+ resp.Body.Close()
+ return err
+}
+
+// InspectSwarm inspects a Swarm.
+// See https://goo.gl/MFwgX9 for more details.
+func (c *Client) InspectSwarm(ctx context.Context) (swarm.Swarm, error) {
+ response := swarm.Swarm{}
+ resp, err := c.do("GET", "/swarm", doOptions{
+ context: ctx,
+ })
+ if err != nil {
+ if e, ok := err.(*Error); ok && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) {
+ return response, ErrNodeNotInSwarm
+ }
+ return response, err
+ }
+ defer resp.Body.Close()
+ err = json.NewDecoder(resp.Body).Decode(&response)
+ return response, err
+}
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_node.go b/vendor/github.com/fsouza/go-dockerclient/swarm_node.go
new file mode 100644
index 000000000..095653cd9
--- /dev/null
+++ b/vendor/github.com/fsouza/go-dockerclient/swarm_node.go
@@ -0,0 +1,130 @@
+// 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"
+)
+
+// NoSuchNode is the error returned when a given node does not exist.
+type NoSuchNode struct {
+ ID string
+ Err error
+}
+
+func (err *NoSuchNode) Error() string {
+ if err.Err != nil {
+ return err.Err.Error()
+ }
+ return "No such node: " + err.ID
+}
+
+// ListNodesOptions specify parameters to the ListNodes function.
+//
+// See http://goo.gl/3K4GwU for more details.
+type ListNodesOptions struct {
+ Filters map[string][]string
+ Context context.Context
+}
+
+// ListNodes returns a slice of nodes matching the given criteria.
+//
+// See http://goo.gl/3K4GwU for more details.
+func (c *Client) ListNodes(opts ListNodesOptions) ([]swarm.Node, error) {
+ path := "/nodes?" + queryString(opts)
+ resp, err := c.do("GET", path, doOptions{context: opts.Context})
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ var nodes []swarm.Node
+ if err := json.NewDecoder(resp.Body).Decode(&nodes); err != nil {
+ return nil, err
+ }
+ return nodes, nil
+}
+
+// InspectNode returns information about a node by its ID.
+//
+// See http://goo.gl/WjkTOk for more details.
+func (c *Client) InspectNode(id string) (*swarm.Node, error) {
+ resp, err := c.do("GET", "/nodes/"+id, doOptions{})
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return nil, &NoSuchNode{ID: id}
+ }
+ return nil, err
+ }
+ defer resp.Body.Close()
+ var node swarm.Node
+ if err := json.NewDecoder(resp.Body).Decode(&node); err != nil {
+ return nil, err
+ }
+ return &node, nil
+}
+
+// UpdateNodeOptions specify parameters to the NodeUpdate function.
+//
+// See http://goo.gl/VPBFgA for more details.
+type UpdateNodeOptions struct {
+ swarm.NodeSpec
+ Version uint64
+ Context context.Context
+}
+
+// UpdateNode updates a node.
+//
+// See http://goo.gl/VPBFgA for more details.
+func (c *Client) UpdateNode(id string, opts UpdateNodeOptions) error {
+ params := make(url.Values)
+ params.Set("version", strconv.FormatUint(opts.Version, 10))
+ path := "/nodes/" + id + "/update?" + params.Encode()
+ resp, err := c.do("POST", path, doOptions{
+ context: opts.Context,
+ forceJSON: true,
+ data: opts.NodeSpec,
+ })
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return &NoSuchNode{ID: id}
+ }
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// RemoveNodeOptions specify parameters to the RemoveNode function.
+//
+// See http://goo.gl/0SNvYg for more details.
+type RemoveNodeOptions struct {
+ ID string
+ Force bool
+ Context context.Context
+}
+
+// RemoveNode removes a node.
+//
+// See http://goo.gl/0SNvYg for more details.
+func (c *Client) RemoveNode(opts RemoveNodeOptions) error {
+ params := make(url.Values)
+ params.Set("force", strconv.FormatBool(opts.Force))
+ path := "/nodes/" + opts.ID + "?" + params.Encode()
+ resp, err := c.do("DELETE", path, doOptions{context: opts.Context})
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return &NoSuchNode{ID: opts.ID}
+ }
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
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_service.go b/vendor/github.com/fsouza/go-dockerclient/swarm_service.go
new file mode 100644
index 000000000..ca7e23725
--- /dev/null
+++ b/vendor/github.com/fsouza/go-dockerclient/swarm_service.go
@@ -0,0 +1,213 @@
+// 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"
+ "io"
+ "net/http"
+ "time"
+
+ "github.com/docker/docker/api/types/swarm"
+)
+
+// NoSuchService is the error returned when a given service does not exist.
+type NoSuchService struct {
+ ID string
+ Err error
+}
+
+func (err *NoSuchService) Error() string {
+ if err.Err != nil {
+ return err.Err.Error()
+ }
+ return "No such service: " + err.ID
+}
+
+// CreateServiceOptions specify parameters to the CreateService function.
+//
+// See https://goo.gl/KrVjHz for more details.
+type CreateServiceOptions struct {
+ Auth AuthConfiguration `qs:"-"`
+ swarm.ServiceSpec
+ Context context.Context
+}
+
+// CreateService creates a new service, returning the service instance
+// or an error in case of failure.
+//
+// See https://goo.gl/KrVjHz for more details.
+func (c *Client) CreateService(opts CreateServiceOptions) (*swarm.Service, error) {
+ headers, err := headersWithAuth(opts.Auth)
+ if err != nil {
+ return nil, err
+ }
+ path := "/services/create?" + queryString(opts)
+ resp, err := c.do("POST", path, doOptions{
+ headers: headers,
+ data: opts.ServiceSpec,
+ forceJSON: true,
+ context: opts.Context,
+ })
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ var service swarm.Service
+ if err := json.NewDecoder(resp.Body).Decode(&service); err != nil {
+ return nil, err
+ }
+ return &service, nil
+}
+
+// RemoveServiceOptions encapsulates options to remove a service.
+//
+// See https://goo.gl/Tqrtya for more details.
+type RemoveServiceOptions struct {
+ ID string `qs:"-"`
+ Context context.Context
+}
+
+// RemoveService removes a service, returning an error in case of failure.
+//
+// See https://goo.gl/Tqrtya for more details.
+func (c *Client) RemoveService(opts RemoveServiceOptions) error {
+ path := "/services/" + 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 &NoSuchService{ID: opts.ID}
+ }
+ return err
+ }
+ resp.Body.Close()
+ return nil
+}
+
+// UpdateServiceOptions specify parameters to the UpdateService function.
+//
+// See https://goo.gl/wu3MmS for more details.
+type UpdateServiceOptions struct {
+ Auth AuthConfiguration `qs:"-"`
+ swarm.ServiceSpec `qs:"-"`
+ Context context.Context
+ Version uint64
+ Rollback string
+}
+
+// UpdateService updates the service at ID with the options
+//
+// See https://goo.gl/wu3MmS for more details.
+func (c *Client) UpdateService(id string, opts UpdateServiceOptions) error {
+ headers, err := headersWithAuth(opts.Auth)
+ if err != nil {
+ return err
+ }
+ resp, err := c.do("POST", "/services/"+id+"/update?"+queryString(opts), doOptions{
+ headers: headers,
+ data: opts.ServiceSpec,
+ forceJSON: true,
+ context: opts.Context,
+ })
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return &NoSuchService{ID: id}
+ }
+ return err
+ }
+ defer resp.Body.Close()
+ return nil
+}
+
+// InspectService returns information about a service by its ID.
+//
+// See https://goo.gl/dHmr75 for more details.
+func (c *Client) InspectService(id string) (*swarm.Service, error) {
+ path := "/services/" + id
+ resp, err := c.do("GET", path, doOptions{})
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return nil, &NoSuchService{ID: id}
+ }
+ return nil, err
+ }
+ defer resp.Body.Close()
+ var service swarm.Service
+ if err := json.NewDecoder(resp.Body).Decode(&service); err != nil {
+ return nil, err
+ }
+ return &service, nil
+}
+
+// ListServicesOptions specify parameters to the ListServices function.
+//
+// See https://goo.gl/DwvNMd for more details.
+type ListServicesOptions struct {
+ Filters map[string][]string
+ Context context.Context
+}
+
+// ListServices returns a slice of services matching the given criteria.
+//
+// See https://goo.gl/DwvNMd for more details.
+func (c *Client) ListServices(opts ListServicesOptions) ([]swarm.Service, error) {
+ path := "/services?" + queryString(opts)
+ resp, err := c.do("GET", path, doOptions{context: opts.Context})
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ var services []swarm.Service
+ if err := json.NewDecoder(resp.Body).Decode(&services); err != nil {
+ return nil, err
+ }
+ return services, nil
+}
+
+// LogsServiceOptions represents the set of options used when getting logs from a
+// service.
+type LogsServiceOptions struct {
+ Context context.Context
+ Service string `qs:"-"`
+ OutputStream io.Writer `qs:"-"`
+ ErrorStream io.Writer `qs:"-"`
+ InactivityTimeout time.Duration `qs:"-"`
+ Tail string
+
+ // Use raw terminal? Usually true when the container contains a TTY.
+ RawTerminal bool `qs:"-"`
+ Since int64
+ Follow bool
+ Stdout bool
+ Stderr bool
+ Timestamps bool
+ Details bool
+}
+
+// GetServiceLogs gets stdout and stderr logs from the specified service.
+//
+// When LogsServiceOptions.RawTerminal is set to false, go-dockerclient will multiplex
+// the streams and send the containers stdout to LogsServiceOptions.OutputStream, and
+// stderr to LogsServiceOptions.ErrorStream.
+//
+// When LogsServiceOptions.RawTerminal is true, callers will get the raw stream on
+// LogsServiceOptions.OutputStream.
+func (c *Client) GetServiceLogs(opts LogsServiceOptions) error {
+ if opts.Service == "" {
+ return &NoSuchService{ID: opts.Service}
+ }
+ if opts.Tail == "" {
+ opts.Tail = "all"
+ }
+ path := "/services/" + opts.Service + "/logs?" + queryString(opts)
+ return c.stream("GET", path, streamOptions{
+ setRawTerminal: opts.RawTerminal,
+ stdout: opts.OutputStream,
+ stderr: opts.ErrorStream,
+ inactivityTimeout: opts.InactivityTimeout,
+ context: opts.Context,
+ })
+}
diff --git a/vendor/github.com/fsouza/go-dockerclient/swarm_task.go b/vendor/github.com/fsouza/go-dockerclient/swarm_task.go
new file mode 100644
index 000000000..3b1161ab9
--- /dev/null
+++ b/vendor/github.com/fsouza/go-dockerclient/swarm_task.go
@@ -0,0 +1,70 @@
+// 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"
+
+ "github.com/docker/docker/api/types/swarm"
+)
+
+// NoSuchTask is the error returned when a given task does not exist.
+type NoSuchTask struct {
+ ID string
+ Err error
+}
+
+func (err *NoSuchTask) Error() string {
+ if err.Err != nil {
+ return err.Err.Error()
+ }
+ return "No such task: " + err.ID
+}
+
+// ListTasksOptions specify parameters to the ListTasks function.
+//
+// See http://goo.gl/rByLzw for more details.
+type ListTasksOptions struct {
+ Filters map[string][]string
+ Context context.Context
+}
+
+// ListTasks returns a slice of tasks matching the given criteria.
+//
+// See http://goo.gl/rByLzw for more details.
+func (c *Client) ListTasks(opts ListTasksOptions) ([]swarm.Task, error) {
+ path := "/tasks?" + queryString(opts)
+ resp, err := c.do("GET", path, doOptions{context: opts.Context})
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ var tasks []swarm.Task
+ if err := json.NewDecoder(resp.Body).Decode(&tasks); err != nil {
+ return nil, err
+ }
+ return tasks, nil
+}
+
+// InspectTask returns information about a task by its ID.
+//
+// See http://goo.gl/kyziuq for more details.
+func (c *Client) InspectTask(id string) (*swarm.Task, error) {
+ resp, err := c.do("GET", "/tasks/"+id, doOptions{})
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return nil, &NoSuchTask{ID: id}
+ }
+ return nil, err
+ }
+ defer resp.Body.Close()
+ var task swarm.Task
+ if err := json.NewDecoder(resp.Body).Decode(&task); err != nil {
+ return nil, err
+ }
+ return &task, nil
+}
diff --git a/vendor/github.com/fsouza/go-dockerclient/tar.go b/vendor/github.com/fsouza/go-dockerclient/tar.go
new file mode 100644
index 000000000..9716a7712
--- /dev/null
+++ b/vendor/github.com/fsouza/go-dockerclient/tar.go
@@ -0,0 +1,122 @@
+// Copyright 2014 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 (
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+
+ "github.com/docker/docker/pkg/archive"
+ "github.com/docker/docker/pkg/fileutils"
+)
+
+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
+ }
+
+ includes := []string{"."}
+
+ // If .dockerignore mentions .dockerignore or the Dockerfile
+ // then make sure we send both files over to the daemon
+ // because Dockerfile is, obviously, needed no matter what, and
+ // .dockerignore is needed to know if either one needs to be
+ // removed. The deamon will remove them for us, if needed, after it
+ // parses the Dockerfile.
+ //
+ // https://github.com/docker/docker/issues/8330
+ //
+ forceIncludeFiles := []string{".dockerignore", dockerfilePath}
+
+ for _, includeFile := range forceIncludeFiles {
+ if includeFile == "" {
+ continue
+ }
+ keepThem, err := fileutils.Matches(includeFile, excludes)
+ if err != nil {
+ return nil, fmt.Errorf("cannot match .dockerfile: '%s', error: %s", includeFile, err)
+ }
+ if keepThem {
+ includes = append(includes, includeFile)
+ }
+ }
+
+ if err := validateContextDirectory(srcPath, excludes); err != nil {
+ return nil, err
+ }
+ tarOpts := &archive.TarOptions{
+ ExcludePatterns: excludes,
+ IncludeFiles: includes,
+ Compression: archive.Uncompressed,
+ NoLchown: true,
+ }
+ return archive.TarWithOptions(srcPath, tarOpts)
+}
+
+// validateContextDirectory checks if all the contents of the directory
+// can be read and returns an error if some files can't be read.
+// Symlinks which point to non-existing files don't trigger an error
+func validateContextDirectory(srcPath string, excludes []string) error {
+ return filepath.Walk(filepath.Join(srcPath, "."), func(filePath string, f os.FileInfo, err error) error {
+ // skip this directory/file if it's not in the path, it won't get added to the context
+ if relFilePath, relErr := filepath.Rel(srcPath, filePath); relErr != nil {
+ return relErr
+ } else if skip, matchErr := fileutils.Matches(relFilePath, excludes); matchErr != nil {
+ return matchErr
+ } else if skip {
+ if f.IsDir() {
+ return filepath.SkipDir
+ }
+ return nil
+ }
+
+ if err != nil {
+ if os.IsPermission(err) {
+ return fmt.Errorf("can't stat '%s'", filePath)
+ }
+ if os.IsNotExist(err) {
+ return nil
+ }
+ return err
+ }
+
+ // skip checking if symlinks point to non-existing files, such symlinks can be useful
+ // also skip named pipes, because they hanging on open
+ if f.Mode()&(os.ModeSymlink|os.ModeNamedPipe) != 0 {
+ return nil
+ }
+
+ if !f.IsDir() {
+ currentFile, err := os.Open(filePath)
+ if err != nil && os.IsPermission(err) {
+ return fmt.Errorf("no permission to read from '%s'", filePath)
+ }
+ currentFile.Close()
+ }
+ return nil
+ })
+}
+
+func parseDockerignore(root string) ([]string, error) {
+ var excludes []string
+ ignore, err := ioutil.ReadFile(path.Join(root, ".dockerignore"))
+ if err != nil && !os.IsNotExist(err) {
+ return excludes, fmt.Errorf("error reading .dockerignore: '%s'", err)
+ }
+ excludes = strings.Split(string(ignore), "\n")
+
+ return excludes, nil
+}
diff --git a/vendor/github.com/fsouza/go-dockerclient/tls.go b/vendor/github.com/fsouza/go-dockerclient/tls.go
new file mode 100644
index 000000000..bb5790b5f
--- /dev/null
+++ b/vendor/github.com/fsouza/go-dockerclient/tls.go
@@ -0,0 +1,118 @@
+// Copyright 2014 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.
+//
+// The content is borrowed from Docker's own source code to provide a simple
+// tls based dialer
+
+package docker
+
+import (
+ "crypto/tls"
+ "errors"
+ "net"
+ "strings"
+ "time"
+)
+
+type tlsClientCon struct {
+ *tls.Conn
+ rawConn net.Conn
+}
+
+func (c *tlsClientCon) CloseWrite() error {
+ // Go standard tls.Conn doesn't provide the CloseWrite() method so we do it
+ // on its underlying connection.
+ if cwc, ok := c.rawConn.(interface {
+ CloseWrite() error
+ }); ok {
+ return cwc.CloseWrite()
+ }
+ return nil
+}
+
+func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Config) (net.Conn, error) {
+ // We want the Timeout and Deadline values from dialer to cover the
+ // whole process: TCP connection and TLS handshake. This means that we
+ // also need to start our own timers now.
+ timeout := dialer.Timeout
+
+ if !dialer.Deadline.IsZero() {
+ deadlineTimeout := dialer.Deadline.Sub(time.Now())
+ if timeout == 0 || deadlineTimeout < timeout {
+ timeout = deadlineTimeout
+ }
+ }
+
+ var errChannel chan error
+
+ if timeout != 0 {
+ errChannel = make(chan error, 2)
+ time.AfterFunc(timeout, func() {
+ errChannel <- errors.New("")
+ })
+ }
+
+ rawConn, err := dialer.Dial(network, addr)
+ if err != nil {
+ return nil, err
+ }
+
+ colonPos := strings.LastIndex(addr, ":")
+ if colonPos == -1 {
+ colonPos = len(addr)
+ }
+ hostname := addr[:colonPos]
+
+ // If no ServerName is set, infer the ServerName
+ // from the hostname we're connecting to.
+ if config.ServerName == "" {
+ // Make a copy to avoid polluting argument or default.
+ config = copyTLSConfig(config)
+ config.ServerName = hostname
+ }
+
+ conn := tls.Client(rawConn, config)
+
+ if timeout == 0 {
+ err = conn.Handshake()
+ } else {
+ go func() {
+ errChannel <- conn.Handshake()
+ }()
+
+ err = <-errChannel
+ }
+
+ if err != nil {
+ rawConn.Close()
+ return nil, err
+ }
+
+ // This is Docker difference with standard's crypto/tls package: returned a
+ // wrapper which holds both the TLS and raw connections.
+ return &tlsClientCon{conn, rawConn}, nil
+}
+
+// this exists to silent an error message in go vet
+func copyTLSConfig(cfg *tls.Config) *tls.Config {
+ return &tls.Config{
+ Certificates: cfg.Certificates,
+ CipherSuites: cfg.CipherSuites,
+ ClientAuth: cfg.ClientAuth,
+ ClientCAs: cfg.ClientCAs,
+ ClientSessionCache: cfg.ClientSessionCache,
+ CurvePreferences: cfg.CurvePreferences,
+ InsecureSkipVerify: cfg.InsecureSkipVerify,
+ MaxVersion: cfg.MaxVersion,
+ MinVersion: cfg.MinVersion,
+ NameToCertificate: cfg.NameToCertificate,
+ NextProtos: cfg.NextProtos,
+ PreferServerCipherSuites: cfg.PreferServerCipherSuites,
+ Rand: cfg.Rand,
+ RootCAs: cfg.RootCAs,
+ ServerName: cfg.ServerName,
+ SessionTicketKey: cfg.SessionTicketKey,
+ SessionTicketsDisabled: cfg.SessionTicketsDisabled,
+ }
+}
diff --git a/vendor/github.com/fsouza/go-dockerclient/volume.go b/vendor/github.com/fsouza/go-dockerclient/volume.go
new file mode 100644
index 000000000..021a262b7
--- /dev/null
+++ b/vendor/github.com/fsouza/go-dockerclient/volume.go
@@ -0,0 +1,190 @@
+// Copyright 2015 go-dockerclient authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package docker
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "net/http"
+)
+
+var (
+ // ErrNoSuchVolume is the error returned when the volume does not exist.
+ ErrNoSuchVolume = errors.New("no such volume")
+
+ // ErrVolumeInUse is the error returned when the volume requested to be removed is still in use.
+ ErrVolumeInUse = errors.New("volume in use and cannot be removed")
+)
+
+// Volume represents a volume.
+//
+// See https://goo.gl/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/3wgTsd for more details.
+type ListVolumesOptions struct {
+ Filters map[string][]string
+ Context context.Context
+}
+
+// ListVolumes returns a list of available volumes in the server.
+//
+// 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,
+ })
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ m := make(map[string]interface{})
+ if err = json.NewDecoder(resp.Body).Decode(&m); err != nil {
+ return nil, err
+ }
+ var volumes []Volume
+ volumesJSON, ok := m["Volumes"]
+ if !ok {
+ return volumes, nil
+ }
+ data, err := json.Marshal(volumesJSON)
+ if err != nil {
+ return nil, err
+ }
+ if err := json.Unmarshal(data, &volumes); err != nil {
+ return nil, err
+ }
+ return volumes, nil
+}
+
+// CreateVolumeOptions specify parameters to the CreateVolume function.
+//
+// See https://goo.gl/qEhmEC for more details.
+type CreateVolumeOptions struct {
+ Name string
+ Driver string
+ DriverOpts map[string]string
+ Context context.Context `json:"-"`
+ Labels map[string]string
+}
+
+// CreateVolume creates a volume on the server.
+//
+// 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,
+ context: opts.Context,
+ })
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ var volume Volume
+ if err := json.NewDecoder(resp.Body).Decode(&volume); err != nil {
+ return nil, err
+ }
+ return &volume, nil
+}
+
+// InspectVolume returns a volume by its name.
+//
+// See https://goo.gl/GMjsMc for more details.
+func (c *Client) InspectVolume(name string) (*Volume, error) {
+ resp, err := c.do("GET", "/volumes/"+name, doOptions{})
+ if err != nil {
+ if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
+ return nil, ErrNoSuchVolume
+ }
+ return nil, err
+ }
+ defer resp.Body.Close()
+ var volume Volume
+ if err := json.NewDecoder(resp.Body).Decode(&volume); err != nil {
+ return nil, err
+ }
+ return &volume, nil
+}
+
+// RemoveVolume removes a volume by its name.
+//
+// Deprecated: Use RemoveVolumeWithOptions instead.
+func (c *Client) RemoveVolume(name string) error {
+ 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 {
+ return ErrNoSuchVolume
+ }
+ if e.Status == http.StatusConflict {
+ return ErrVolumeInUse
+ }
+ }
+ return err
+ }
+ defer resp.Body.Close()
+ return nil
+}
+
+// PruneVolumesOptions specify parameters to the PruneVolumes function.
+//
+// See https://goo.gl/f9XDem for more details.
+type PruneVolumesOptions struct {
+ Filters map[string][]string
+ Context context.Context
+}
+
+// PruneVolumesResults specify results from the PruneVolumes function.
+//
+// See https://goo.gl/f9XDem for more details.
+type PruneVolumesResults struct {
+ VolumesDeleted []string
+ SpaceReclaimed int64
+}
+
+// PruneVolumes deletes volumes which are unused.
+//
+// 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})
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ var results PruneVolumesResults
+ if err := json.NewDecoder(resp.Body).Decode(&results); err != nil {
+ return nil, err
+ }
+ return &results, nil
+}