update fsouza/go-dockerclient to 37a1d72

This commit is contained in:
Clint Armstrong
2017-08-10 12:19:19 -04:00
parent 02808f9568
commit bcb0ea0c44
15 changed files with 411 additions and 51 deletions

View File

@@ -8,6 +8,7 @@ Alex Dadgar
Alfonso Acosta
André Carvalho
Andreas Jaekle
Andrew Snodgrass
Andrews Medina
Andrey Sibiryov
Andy Goldstein
@@ -35,25 +36,33 @@ Changping Chen
Cheah Chu Yeow
cheneydeng
Chris Bednarski
Chris Stavropoulos
Christian Stewart
Christophe Mourette
Clint Armstrong
CMGS
Colin Hebert
Craig Jellick
Damon Wang
Dan Williams
Daniel, Dao Quang Minh
Daniel Garcia
Daniel Hiltgen
Daniel Tsui
Darren Shepherd
Dave Choi
David Huie
Dawn Chen
Derek Petersen
Dinesh Subhraveti
Drew Wells
Ed
Elias G. Schneevoigt
Erez Horev
Eric Anderson
Eric J. Holmes
Eric Mountain
Erwin van Eyk
Ethan Mosbaugh
Ewout Prangsma
Fabio Rehm
@@ -72,6 +81,8 @@ He Simei
Ivan Mikushin
James Bardin
James Nugent
Jamie Snell
Januar Wayong
Jari Kolehmainen
Jason Wilder
Jawher Moussa
@@ -82,8 +93,10 @@ Jen Andre
Jérôme Laurens
Jim Minter
Johan Euphrosine
Johannes Scheuermann
John Hughes
Jorge Marey
Julian Einwag
Kamil Domanski
Karan Misra
Ken Herner

View File

@@ -103,10 +103,23 @@ 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://godoc.org/golang.org/x/tools/cmd/vet)
* [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.
## 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
```

View File

@@ -4,13 +4,18 @@ clone_depth: 2
clone_folder: c:\gopath\src\github.com\fsouza\go-dockerclient
environment:
GOPATH: c:\gopath
GOVERSION: 1.7.5
matrix:
- GOVERSION: 1.7.5
- GOVERSION: 1.8.3
- GOVERSION: 1.9rc1
install:
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
- rmdir c:\go /s /q
- appveyor DownloadFile https://storage.googleapis.com/golang/go%GOVERSION%.windows-amd64.zip
- 7z x go%GOVERSION%.windows-amd64.zip -y -oC:\ > NUL
build_script:
- go get -d -t ./...
- go get -race -d -t ./...
test_script:
- go test ./...
- go test -race ./...
matrix:
fast_finish: true

View File

@@ -24,6 +24,7 @@ import (
"os"
"path/filepath"
"reflect"
"runtime"
"strconv"
"strings"
"sync/atomic"
@@ -33,7 +34,6 @@ import (
"github.com/docker/docker/pkg/homedir"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/stdcopy"
"github.com/hashicorp/go-cleanhttp"
"golang.org/x/net/context"
"golang.org/x/net/context/ctxhttp"
)
@@ -58,6 +58,7 @@ var (
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.
@@ -211,7 +212,7 @@ func NewVersionedClient(endpoint string, apiVersionString string) (*Client, erro
}
}
c := &Client{
HTTPClient: cleanhttp.DefaultClient(),
HTTPClient: defaultClient(),
Dialer: &net.Dialer{},
endpoint: endpoint,
endpointURL: u,
@@ -325,7 +326,7 @@ func NewVersionedTLSClientFromBytes(endpoint string, certPEMBlock, keyPEMBlock,
}
tlsConfig.RootCAs = caPool
}
tr := cleanhttp.DefaultTransport()
tr := defaultTransport()
tr.TLSClientConfig = tlsConfig
if err != nil {
return nil, err
@@ -497,6 +498,7 @@ type streamOptions struct {
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
@@ -575,6 +577,9 @@ func (c *Client) stream(method, path string, streamOptions streamOptions) error
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 {
@@ -593,6 +598,9 @@ func (c *Client) stream(method, path string, streamOptions streamOptions) error
}
return chooseError(subCtx, err)
}
if streamOptions.reqSent != nil {
close(streamOptions.reqSent)
}
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
@@ -1032,3 +1040,41 @@ func getDockerEnv() (*dockerEnv, error) {
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(),
}
}

View File

@@ -1,16 +1,15 @@
// +build !windows
// Copyright 2016 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !windows
package docker
import (
"context"
"net"
"net/http"
"github.com/hashicorp/go-cleanhttp"
)
// initializeNativeClient initializes the native Unix domain socket client on
@@ -20,9 +19,9 @@ func (c *Client) initializeNativeClient() {
return
}
socketPath := c.endpointURL.Path
tr := cleanhttp.DefaultTransport()
tr := defaultTransport()
tr.Dial = func(network, addr string) (net.Conn, error) {
return c.Dialer.Dial(network, addr)
return c.Dialer.Dial(unixProtocol, socketPath)
}
tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
return c.Dialer.Dial(unixProtocol, socketPath)

View File

@@ -13,7 +13,6 @@ import (
"time"
"github.com/Microsoft/go-winio"
"github.com/hashicorp/go-cleanhttp"
)
const namedPipeConnectTimeout = 2 * time.Second
@@ -36,7 +35,7 @@ func (c *Client) initializeNativeClient() {
timeout := namedPipeConnectTimeout
return winio.DialPipe(namedPipePath, &timeout)
}
tr := cleanhttp.DefaultTransport()
tr := defaultTransport()
tr.Dial = dialFunc
tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialFunc(network, addr)

View File

@@ -204,7 +204,7 @@ func (s *State) StateString() string {
// 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"`
HostIP string `json:"HostIp,omitempty" yaml:"HostIp,omitempty" toml:"HostIp,omitempty"`
HostPort string `json:"HostPort,omitempty" yaml:"HostPort,omitempty" toml:"HostPort,omitempty"`
}
@@ -214,15 +214,16 @@ type PortMapping map[string]string
// ContainerNetwork represents the networking settings of a container per network.
type ContainerNetwork struct {
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"`
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
@@ -327,6 +328,43 @@ type Config struct {
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
@@ -707,7 +745,7 @@ type HostConfig struct {
MemoryReservation int64 `json:"MemoryReservation,omitempty" yaml:"MemoryReservation,omitempty" toml:"MemoryReservation,omitempty"`
KernelMemory int64 `json:"KernelMemory,omitempty" yaml:"KernelMemory,omitempty" toml:"KernelMemory,omitempty"`
MemorySwap int64 `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty" toml:"MemorySwap,omitempty"`
MemorySwappiness int64 `json:"MemorySwappiness,omitempty" yaml:"MemorySwappiness,omitempty" toml:"MemorySwappiness,omitempty"`
MemorySwappiness int64 `json:"MemorySwappiness" yaml:"MemorySwappiness" toml:"MemorySwappiness"`
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"`
@@ -735,6 +773,11 @@ type HostConfig struct {
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"`
}
// NetworkingConfig represents the container's networking configuration for each of its interfaces
@@ -910,6 +953,8 @@ func (c *Client) TopContainer(id string, psArgs string) (TopResult, error) {
// 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"`
@@ -949,10 +994,13 @@ type Stats struct {
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"`
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"`
@@ -964,8 +1012,14 @@ type Stats struct {
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"`
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
@@ -1051,6 +1105,7 @@ func (c *Client) Stats(opts StatsOptions) (retErr error) {
}
}()
reqSent := make(chan struct{})
go func() {
err := c.stream("GET", fmt.Sprintf("/containers/%s/stats?stream=%v", opts.ID, opts.Stream), streamOptions{
rawJSONStream: true,
@@ -1059,6 +1114,7 @@ func (c *Client) Stats(opts StatsOptions) (retErr error) {
timeout: opts.Timeout,
inactivityTimeout: opts.InactivityTimeout,
context: opts.Context,
reqSent: reqSent,
})
if err != nil {
dockerError, ok := err.(*Error)
@@ -1089,6 +1145,7 @@ func (c *Client) Stats(opts StatsOptions) (retErr error) {
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
@@ -1293,7 +1350,8 @@ type CommitContainerOptions struct {
Tag string
Message string `qs:"comment"`
Author string
Run *Config `qs:"-"`
Changes []string `qs:"changes"`
Run *Config `qs:"-"`
Context context.Context
}
@@ -1478,6 +1536,39 @@ func (c *Client) ExportContainer(opts ExportContainerOptions) error {
})
}
// 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

View File

@@ -216,7 +216,7 @@ func (eventState *eventMonitoringState) monitorEvents(c *Client) {
return
}
eventState.updateLastSeen(ev)
go eventState.sendEvent(ev)
eventState.sendEvent(ev)
case err = <-eventState.errC:
if err == ErrNoListeners {
eventState.disableEventMonitoring()
@@ -274,7 +274,10 @@ func (eventState *eventMonitoringState) sendEvent(event *APIEvents) {
}
for _, listener := range eventState.listeners {
listener <- event
select {
case listener <- event:
default:
}
}
}
}
@@ -342,11 +345,12 @@ func (c *Client) eventHijack(startTime int64, eventChan chan *APIEvents, errChan
if event.Time == 0 {
continue
}
if !c.eventMonitor.isEnabled() || c.eventMonitor.C != eventChan {
return
}
transformEvent(&event)
eventChan <- &event
c.eventMonitor.RLock()
if c.eventMonitor.enabled && c.eventMonitor.C == eventChan {
eventChan <- &event
}
c.eventMonitor.RUnlock()
}
}(res, conn)
return nil

View File

@@ -6,6 +6,7 @@ package docker
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
@@ -29,6 +30,7 @@ type CreateExecOptions struct {
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"`
@@ -41,6 +43,9 @@ type CreateExecOptions struct {
//
// 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 {
@@ -174,7 +179,9 @@ type ExecInspect struct {
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"`
Container Container `json:"Container,omitempty" yaml:"Container,omitempty" toml:"Container,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.

View File

@@ -14,6 +14,7 @@ import (
"net/http"
"net/url"
"os"
"strings"
"time"
"golang.org/x/net/context"
@@ -54,6 +55,7 @@ type Image struct {
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
@@ -313,6 +315,11 @@ func (c *Client) PullImage(opts PullImageOptions, auth AuthConfiguration) error
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)
}
@@ -333,8 +340,9 @@ func (c *Client) createImage(qs string, headers map[string]string, in io.Reader,
//
// See https://goo.gl/rEsBV3 for more details.
type LoadImageOptions struct {
InputStream io.Reader
Context context.Context
InputStream io.Reader
OutputStream io.Writer
Context context.Context
}
// LoadImage imports a tarball docker image
@@ -344,6 +352,7 @@ 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,
})
}
@@ -440,6 +449,7 @@ 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"`
@@ -462,6 +472,7 @@ type BuildImageOptions struct {
BuildArgs []BuildArg `qs:"-"`
NetworkMode string `qs:"networkmode"`
InactivityTimeout time.Duration `qs:"-"`
CgroupParent string `qs:"cgroupparent"`
Context context.Context
}
@@ -505,8 +516,16 @@ func (c *Client) BuildImage(opts BuildImageOptions) error {
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{})
@@ -665,3 +684,36 @@ func (c *Client) SearchImagesEx(term string, auth AuthConfiguration) ([]APIImage
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
}

View File

@@ -79,6 +79,10 @@ type DockerInfo struct {
ServerVersion string
ClusterStore string
ClusterAdvertise string
Isolation string
InitBinary string
DefaultRuntime string
LiveRestoreEnabled bool
Swarm swarm.Info
}

View File

@@ -268,6 +268,38 @@ func (c *Client) DisconnectNetwork(id string, opts NetworkConnectionOptions) err
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

View File

@@ -6,9 +6,11 @@ package docker
import (
"encoding/json"
"io"
"net/http"
"net/url"
"strconv"
"time"
"github.com/docker/docker/api/types/swarm"
"golang.org/x/net/context"
@@ -31,6 +33,7 @@ func (err *NoSuchService) Error() string {
//
// See https://goo.gl/KrVjHz for more details.
type CreateServiceOptions struct {
Auth AuthConfiguration `qs:"-"`
swarm.ServiceSpec
Context context.Context
}
@@ -40,8 +43,13 @@ type CreateServiceOptions struct {
//
// 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,
@@ -85,6 +93,7 @@ func (c *Client) RemoveService(opts RemoveServiceOptions) error {
//
// See https://goo.gl/wu3MmS for more details.
type UpdateServiceOptions struct {
Auth AuthConfiguration `qs:"-"`
swarm.ServiceSpec
Context context.Context
Version uint64
@@ -94,9 +103,14 @@ type UpdateServiceOptions struct {
//
// 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
}
params := make(url.Values)
params.Set("version", strconv.FormatUint(opts.Version, 10))
resp, err := c.do("POST", "/services/"+id+"/update?"+params.Encode(), doOptions{
headers: headers,
data: opts.ServiceSpec,
forceJSON: true,
context: opts.Context,
@@ -155,3 +169,48 @@ func (c *Client) ListServices(opts ListServicesOptions) ([]swarm.Service, error)
}
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,
})
}

View File

@@ -33,7 +33,7 @@ type InitSwarmOptions struct {
}
// InitSwarm initializes a new Swarm and returns the node ID.
// See https://goo.gl/hzkgWu for more details.
// 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{
@@ -42,7 +42,7 @@ func (c *Client) InitSwarm(opts InitSwarmOptions) (string, error) {
context: opts.Context,
})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotAcceptable {
if e, ok := err.(*Error); ok && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) {
return "", ErrNodeAlreadyInSwarm
}
return "", err
@@ -63,7 +63,7 @@ type JoinSwarmOptions struct {
}
// JoinSwarm joins an existing Swarm.
// See https://goo.gl/TdhJWU for more details.
// 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{
@@ -72,7 +72,7 @@ func (c *Client) JoinSwarm(opts JoinSwarmOptions) error {
context: opts.Context,
})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotAcceptable {
if e, ok := err.(*Error); ok && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) {
return ErrNodeAlreadyInSwarm
}
}
@@ -88,7 +88,7 @@ type LeaveSwarmOptions struct {
}
// LeaveSwarm leaves a Swarm.
// See https://goo.gl/UWDlLg for more details.
// 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))
@@ -97,7 +97,7 @@ func (c *Client) LeaveSwarm(opts LeaveSwarmOptions) error {
context: opts.Context,
})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotAcceptable {
if e, ok := err.(*Error); ok && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) {
return ErrNodeNotInSwarm
}
}
@@ -116,7 +116,7 @@ type UpdateSwarmOptions struct {
}
// UpdateSwarm updates a Swarm.
// See https://goo.gl/vFbq36 for more details.
// 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))
@@ -129,7 +129,7 @@ func (c *Client) UpdateSwarm(opts UpdateSwarmOptions) error {
context: opts.Context,
})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotAcceptable {
if e, ok := err.(*Error); ok && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) {
return ErrNodeNotInSwarm
}
}
@@ -138,13 +138,16 @@ func (c *Client) UpdateSwarm(opts UpdateSwarmOptions) error {
}
// InspectSwarm inspects a Swarm.
// See http://goo.gl/nvwytL for more details.
// 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()

View File

@@ -136,3 +136,36 @@ func (c *Client) RemoveVolume(name string) error {
defer resp.Body.Close()
return nil
}
// PruneVolumesOptions specify parameters to the PruneVolumes function.
//
// See https://goo.gl/pFN1Hj for more details.
type PruneVolumesOptions struct {
Filters map[string][]string
Context context.Context
}
// PruneVolumesResults specify results from the PruneVolumes function.
//
// See https://goo.gl/pFN1Hj for more details.
type PruneVolumesResults struct {
VolumesDeleted []string
SpaceReclaimed int64
}
// PruneVolumes deletes volumes which are unused.
//
// See https://goo.gl/pFN1Hj 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
}