bump(github.com/fsouza/go-dockerclient): 17d39bcb22e8103ba6d1c0cb2530c6434cb870a3

pull/6/head
Paul Weil 2015-04-10 16:05:43 -04:00
parent 1db1894850
commit fb63370451
20 changed files with 864 additions and 131 deletions

2
Godeps/Godeps.json generated
View File

@ -179,7 +179,7 @@
},
{
"ImportPath": "github.com/fsouza/go-dockerclient",
"Rev": "d19717788084716e4adff0515be6289aa04bec46"
"Rev": "17d39bcb22e8103ba6d1c0cb2530c6434cb870a3"
},
{
"ImportPath": "github.com/garyburd/redigo/internal",

View File

@ -1,6 +1,5 @@
language: go
go:
- 1.2.2
- 1.3.1
- 1.4
- tip

View File

@ -1,25 +1,31 @@
# This is the official list of go-dockerclient authors for copyright purposes.
Adam Bell-Hanssen <adamb@aller.no>
Aldrin Leal <aldrin@leal.eng.br>
Andreas Jaekle <andreas@jaekle.net>
Andrews Medina <andrewsmedina@gmail.com>
Artem Sidorenko <artem@2realities.com>
Andy Goldstein <andy.goldstein@redhat.com>
Ben Marini <ben@remind101.com>
Ben McCann <benmccann.com>
Brian Lalor <blalor@bravo5.org>
Burke Libbey <burke@libbey.me>
Carlos Diaz-Padron <cpadron@mozilla.com>
Cezar Sa Espinola <cezar.sa@corp.globo.com>
Cheah Chu Yeow <chuyeow@gmail.com>
cheneydeng <cheneydeng@qq.com>
CMGS <ilskdw@gmail.com>
Daniel, Dao Quang Minh <dqminh89@gmail.com>
Darren Shepherd <darren@rancher.com>
David Huie <dahuie@gmail.com>
Dawn Chen <dawnchen@google.com>
Ed <edrocksit@gmail.com>
Eric Anderson <anderson@copperegg.com>
Fabio Rehm <fgrehm@gmail.com>
Fatih Arslan <ftharsln@gmail.com>
Fatih Arslan <ftharsln@gmail.com>
Flavia Missi <flaviamissi@gmail.com>
Francisco Souza <f@souza.cc>
Guillermo Álvarez Fernández <guillermo@cientifico.net>
Jari Kolehmainen <jari.kolehmainen@digia.com>
Jason Wilder <jwilder@litl.com>
Jawher Moussa <jawher.moussa@gmail.com>
@ -30,19 +36,26 @@ Johan Euphrosine <proppy@google.com>
Kamil Domanski <kamil@domanski.co>
Karan Misra <kidoman@gmail.com>
Kim, Hirokuni <hirokuni.kim@kvh.co.jp>
liron-l <levinlir@gmail.com>
Lucas Clemente <lucas@clemente.io>
Lucas Weiblen <lucasweiblen@gmail.com>
Mantas Matelis <mmatelis@coursera.org>
Martin Sweeney <martin@sweeney.io>
Máximo Cuadros Ortiz <mcuadros@gmail.com>
Michal Fojtik <mfojtik@redhat.com>
Mike Dillon <mike.dillon@synctree.com>
Mrunal Patel <mrunalp@gmail.com>
Nick Ethier <ncethier@gmail.com>
Omeid Matten <public@omeid.me>
Paul Morie <pmorie@gmail.com>
Paul Weil <pweil@redhat.com>
Peter Jihoon Kim <raingrove@gmail.com>
Philippe Lafoucrière <philippe.lafoucriere@tech-angels.com>
Rafe Colton <rafael.colton@gmail.com>
Rob Miller <rob@kalistra.com>
Robert Williamson <williamson.robert@gmail.com>
Salvador Gironès <salvadorgirones@gmail.com>
Sam Rijs <srijs@airpost.net>
Simon Eskildsen <sirup@sirupsen.com>
Simon Menke <simon.menke@gmail.com>
Skolos <skolos@gopherlab.com>
@ -51,5 +64,7 @@ Sridhar Ratnakumar <sridharr@activestate.com>
Summer Mousa <smousa@zenoss.com>
Tarsis Azevedo <tarsis@corp.globo.com>
Tim Schindler <tim@catalyst-zero.com>
Vincenzo Prignano <vincenzo.prignano@gmail.com>
Wiliam Souza <wiliamsouza83@gmail.com>
Ye Yin <eyniy@qq.com>
Yuriy Bogdanov <chinsay@gmail.com>

View File

@ -1,4 +1,4 @@
Copyright (c) 2014, go-dockerclient authors
Copyright (c) 2015, go-dockerclient authors
All rights reserved.
Redistribution and use in source and binary forms, with or without

View File

@ -3,11 +3,12 @@
[![Build Status](https://drone.io/github.com/fsouza/go-dockerclient/status.png)](https://drone.io/github.com/fsouza/go-dockerclient/latest)
[![Build Status](https://travis-ci.org/fsouza/go-dockerclient.png)](https://travis-ci.org/fsouza/go-dockerclient)
[![GoDoc](http://godoc.org/github.com/fsouza/go-dockerclient?status.png)](http://godoc.org/github.com/fsouza/go-dockerclient)
[![GoDoc](https://godoc.org/github.com/fsouza/go-dockerclient?status.png)](https://godoc.org/github.com/fsouza/go-dockerclient)
This package presents a client for the Docker remote API.
This package presents a client for the Docker remote API. It also provides
support for the extensions in the [Swarm API](https://docs.docker.com/swarm/API/).
For more details, check the [remote API documentation](http://docs.docker.io/en/latest/reference/api/docker_remote_api/).
For more details, check the [remote API documentation](http://docs.docker.com/en/latest/reference/api/docker_remote_api/).
## Example
@ -16,6 +17,7 @@ package main
import (
"fmt"
"github.com/fsouza/go-dockerclient"
)
@ -34,6 +36,32 @@ func main() {
}
```
## Using with Boot2Docker
Boot2Docker runs Docker with TLS enabled. In order to instantiate the client you should use NewTLSClient, passing the endpoint and path for key and certificates as parameters.
For more details about TLS support in Boot2Docker, please refer to [TLS support](https://github.com/boot2docker/boot2docker#tls-support) on Boot2Docker's readme.
```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
}
```
## Developing
You can run the tests with:

View File

@ -0,0 +1,83 @@
// 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 (
"encoding/base64"
"encoding/json"
"io"
"os"
"path"
"strings"
)
// 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"`
}
// dockerConfig represents a registry authentation configuration from the
// .dockercfg file.
type dockerConfig struct {
Auth string `json:"auth"`
Email string `json:"email"`
}
// NewAuthConfigurationsFromDockerCfg returns AuthConfigurations from the
// ~/.dockercfg file.
func NewAuthConfigurationsFromDockerCfg() (*AuthConfigurations, error) {
p := path.Join(os.Getenv("HOME"), ".dockercfg")
r, err := os.Open(p)
if err != nil {
return nil, err
}
return NewAuthConfigurations(r)
}
// 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
var confs map[string]dockerConfig
if err := json.NewDecoder(r).Decode(&confs); err != nil {
return nil, err
}
auth, err := authConfigs(confs)
if err != nil {
return nil, err
}
return auth, 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 {
data, err := base64.StdEncoding.DecodeString(conf.Auth)
if err != nil {
return nil, err
}
userpass := strings.Split(string(data), ":")
c.Configs[reg] = AuthConfiguration{
Email: conf.Email,
Username: userpass[0],
Password: userpass[1],
ServerAddress: reg,
}
}
return c, nil
}

View File

@ -0,0 +1,37 @@
// 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 (
"encoding/base64"
"fmt"
"strings"
"testing"
)
func TestAuthConfig(t *testing.T) {
auth := base64.StdEncoding.EncodeToString([]byte("user:pass"))
read := strings.NewReader(fmt.Sprintf(`{"docker.io":{"auth":"%s","email":"user@example.com"}}`, auth))
ac, err := NewAuthConfigurations(read)
if err != nil {
t.Error(err)
}
c, ok := ac.Configs["docker.io"]
if !ok {
t.Error("NewAuthConfigurations: Expected Configs to contain docker.io")
}
if got, want := c.Email, "user@example.com"; got != want {
t.Errorf(`AuthConfigurations.Configs["docker.io"].Email: wrong result. Want %q. Got %q`, want, got)
}
if got, want := c.Username, "user"; got != want {
t.Errorf(`AuthConfigurations.Configs["docker.io"].Username: wrong result. Want %q. Got %q`, want, got)
}
if got, want := c.Password, "pass"; got != want {
t.Errorf(`AuthConfigurations.Configs["docker.io"].Password: wrong result. Want %q. Got %q`, want, got)
}
if got, want := c.ServerAddress, "docker.io"; got != want {
t.Errorf(`AuthConfigurations.Configs["docker.io"].ServerAddress: wrong result. Want %q. Got %q`, want, got)
}
}

View File

@ -1,4 +1,4 @@
// Copyright 2014 go-dockerclient authors. All rights reserved.
// 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.
@ -34,7 +34,7 @@ var (
// ErrConnectionRefused is returned when the client cannot connect to the given endpoint.
ErrConnectionRefused = errors.New("cannot connect to Docker endpoint")
apiVersion1_12, _ = NewAPIVersion("1.12")
apiVersion112, _ = NewAPIVersion("1.12")
)
// APIVersion is an internal representation of a version of the Remote API.
@ -143,7 +143,7 @@ func NewClient(endpoint string) (*Client, error) {
// 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 := NewVersionnedTLSClient(endpoint, cert, key, ca, "")
client, err := NewVersionedTLSClient(endpoint, cert, key, ca, "")
if err != nil {
return nil, err
}
@ -154,7 +154,7 @@ func NewTLSClient(endpoint string, cert, key, ca string) (*Client, error) {
// 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)
u, err := parseEndpoint(endpoint, false)
if err != nil {
return nil, err
}
@ -174,10 +174,15 @@ func NewVersionedClient(endpoint string, apiVersionString string) (*Client, erro
}, nil
}
// NewVersionnedTLSClient returns a Client instance ready for TLS communications with the givens
// server endpoint, key and certificates, using a specific remote API version.
// NewVersionnedTLSClient has been DEPRECATED, please use NewVersionedTLSClient.
func NewVersionnedTLSClient(endpoint string, cert, key, ca, apiVersionString string) (*Client, error) {
u, err := parseEndpoint(endpoint)
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) {
u, err := parseEndpoint(endpoint, true)
if err != nil {
return nil, err
}
@ -247,7 +252,7 @@ func (c *Client) checkAPIVersion() error {
// See http://goo.gl/stJENm for more details.
func (c *Client) Ping() error {
path := "/_ping"
body, status, err := c.do("GET", path, nil)
body, status, err := c.do("GET", path, nil, false)
if err != nil {
return err
}
@ -258,7 +263,7 @@ func (c *Client) Ping() error {
}
func (c *Client) getServerAPIVersionString() (version string, err error) {
body, status, err := c.do("GET", "/version", nil)
body, status, err := c.do("GET", "/version", nil, false)
if err != nil {
return "", err
}
@ -274,9 +279,9 @@ func (c *Client) getServerAPIVersionString() (version string, err error) {
return version, nil
}
func (c *Client) do(method, path string, data interface{}) ([]byte, int, error) {
func (c *Client) do(method, path string, data interface{}, forceJSON bool) ([]byte, int, error) {
var params io.Reader
if data != nil {
if data != nil || forceJSON {
buf, err := json.Marshal(data)
if err != nil {
return nil, -1, err
@ -599,11 +604,14 @@ func (e *Error) Error() string {
return fmt.Sprintf("API error (%d): %s", e.Status, e.Message)
}
func parseEndpoint(endpoint string) (*url.URL, error) {
func parseEndpoint(endpoint string, tls bool) (*url.URL, error) {
u, err := url.Parse(endpoint)
if err != nil {
return nil, ErrInvalidEndpoint
}
if tls {
u.Scheme = "https"
}
if u.Scheme == "tcp" {
_, port, err := net.SplitHostPort(u.Host)
if err != nil {

View File

@ -1,4 +1,4 @@
// Copyright 2014 go-dockerclient authors. All rights reserved.
// 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.
@ -93,7 +93,7 @@ func TestNewTLSVersionedClient(t *testing.T) {
keyPath := "testing/data/key.pem"
caPath := "testing/data/ca.pem"
endpoint := "https://localhost:4243"
client, err := NewVersionnedTLSClient(endpoint, certPath, keyPath, caPath, "1.14")
client, err := NewVersionedTLSClient(endpoint, certPath, keyPath, caPath, "1.14")
if err != nil {
t.Fatal(err)
}
@ -113,7 +113,7 @@ func TestNewTLSVersionedClientInvalidCA(t *testing.T) {
keyPath := "testing/data/key.pem"
caPath := "testing/data/key.pem"
endpoint := "https://localhost:4243"
_, err := NewVersionnedTLSClient(endpoint, certPath, keyPath, caPath, "1.14")
_, err := NewVersionedTLSClient(endpoint, certPath, keyPath, caPath, "1.14")
if err == nil {
t.Errorf("Expected invalid ca at %s", caPath)
}
@ -136,14 +136,15 @@ func TestNewClientInvalidEndpoint(t *testing.T) {
}
}
func TestNewTLSClient2376(t *testing.T) {
func TestNewTLSClient(t *testing.T) {
var tests = []struct {
endpoint string
expected string
}{
{"tcp://localhost:2376", "https"},
{"tcp://localhost:2375", "http"},
{"tcp://localhost:4000", "http"},
{"tcp://localhost:2375", "https"},
{"tcp://localhost:4000", "https"},
{"http://localhost:4000", "https"},
}
for _, tt := range tests {

View File

@ -1,4 +1,4 @@
// Copyright 2014 go-dockerclient authors. All rights reserved.
// 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.
@ -56,7 +56,7 @@ type APIContainers struct {
// See http://goo.gl/6Y4Gz7 for more details.
func (c *Client) ListContainers(opts ListContainersOptions) ([]APIContainers, error) {
path := "/containers/json?" + queryString(opts)
body, _, err := c.do("GET", path, nil)
body, _, err := c.do("GET", path, nil, false)
if err != nil {
return nil, err
}
@ -166,7 +166,7 @@ func parsePort(rawPort string) (int, error) {
}
// Config is the list of configuration options used when creating a container.
// Config does not the options that are specific to starting a container on a
// 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"`
@ -193,6 +193,26 @@ type Config struct {
WorkingDir string `json:"WorkingDir,omitempty" yaml:"WorkingDir,omitempty"`
Entrypoint []string `json:"Entrypoint,omitempty" yaml:"Entrypoint,omitempty"`
NetworkDisabled bool `json:"NetworkDisabled,omitempty" yaml:"NetworkDisabled,omitempty"`
SecurityOpts []string `json:"SecurityOpts,omitempty" yaml:"SecurityOpts,omitempty"`
OnBuild []string `json:"OnBuild,omitempty" yaml:"OnBuild,omitempty"`
Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty"`
}
// LogConfig defines the log driver type and the configuration for it.
type LogConfig struct {
Type string `json:"Type,omitempty" yaml:"Type,omitempty"`
Config map[string]string `json:"Config,omitempty" yaml:"Config,omitempty"`
}
// SwarmNode containers information about which Swarm node the container is on
type SwarmNode struct {
ID string `json:"ID,omitempty" yaml:"ID,omitempty"`
IP string `json:"IP,omitempty" yaml:"IP,omitempty"`
Addr string `json:"Addr,omitempty" yaml:"Addr,omitempty"`
Name string `json:"Name,omitempty" yaml:"Name,omitempty"`
CPUs int64 `json:"CPUs,omitempty" yaml:"CPUs,omitempty"`
Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty"`
Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty"`
}
// Container is the type encompasing everything about a container - its config,
@ -209,6 +229,8 @@ type Container struct {
State State `json:"State,omitempty" yaml:"State,omitempty"`
Image string `json:"Image,omitempty" yaml:"Image,omitempty"`
Node *SwarmNode `json:"Node,omitempty" yaml:"Node,omitempty"`
NetworkSettings *NetworkSettings `json:"NetworkSettings,omitempty" yaml:"NetworkSettings,omitempty"`
SysInitPath string `json:"SysInitPath,omitempty" yaml:"SysInitPath,omitempty"`
@ -221,6 +243,28 @@ type Container struct {
Volumes map[string]string `json:"Volumes,omitempty" yaml:"Volumes,omitempty"`
VolumesRW map[string]bool `json:"VolumesRW,omitempty" yaml:"VolumesRW,omitempty"`
HostConfig *HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty"`
ExecIDs []string `json:"ExecIDs,omitempty" yaml:"ExecIDs,omitempty"`
AppArmorProfile string `json:"AppArmorProfile,omitempty" yaml:"AppArmorProfile,omitempty"`
}
// RenameContainerOptions specify parameters to the RenameContainer function.
//
// See http://goo.gl/L00hoj for more details.
type RenameContainerOptions struct {
// ID of container to rename
ID string `qs:"-"`
// New name
Name string `json:"name,omitempty" yaml:"name,omitempty"`
}
// RenameContainer updates and existing containers name
//
// See http://goo.gl/L00hoj for more details.
func (c *Client) RenameContainer(opts RenameContainerOptions) error {
_, _, err := c.do("POST", fmt.Sprintf("/containers/"+opts.ID+"/rename?%s", queryString(opts)), nil, false)
return err
}
// InspectContainer returns information about a container by its ID.
@ -228,7 +272,7 @@ type Container struct {
// See http://goo.gl/CxVuJ5 for more details.
func (c *Client) InspectContainer(id string) (*Container, error) {
path := "/containers/" + id + "/json"
body, status, err := c.do("GET", path, nil)
body, status, err := c.do("GET", path, nil, false)
if status == http.StatusNotFound {
return nil, &NoSuchContainer{ID: id}
}
@ -248,7 +292,7 @@ func (c *Client) InspectContainer(id string) (*Container, error) {
// See http://goo.gl/QkW9sH for more details.
func (c *Client) ContainerChanges(id string) ([]Change, error) {
path := "/containers/" + id + "/changes"
body, status, err := c.do("GET", path, nil)
body, status, err := c.do("GET", path, nil, false)
if status == http.StatusNotFound {
return nil, &NoSuchContainer{ID: id}
}
@ -284,7 +328,7 @@ func (c *Client) CreateContainer(opts CreateContainerOptions) (*Container, error
}{
opts.Config,
opts.HostConfig,
})
}, false)
if status == http.StatusNotFound {
return nil, ErrNoSuchImage
@ -341,6 +385,14 @@ 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"`
PathInContainer string `json:"PathInContainer,omitempty" yaml:"PathInContainer,omitempty"`
CgroupPermissions string `json:"CgroupPermissions,omitempty" yaml:"CgroupPermissions,omitempty"`
}
// HostConfig contains the container options related to starting a container on
// a given host
type HostConfig struct {
@ -359,20 +411,22 @@ type HostConfig struct {
VolumesFrom []string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"`
NetworkMode string `json:"NetworkMode,omitempty" yaml:"NetworkMode,omitempty"`
IpcMode string `json:"IpcMode,omitempty" yaml:"IpcMode,omitempty"`
PidMode string `json:"PidMode,omitempty" yaml:"PidMode,omitempty"`
RestartPolicy RestartPolicy `json:"RestartPolicy,omitempty" yaml:"RestartPolicy,omitempty"`
Devices []Device `json:"Devices,omitempty" yaml:"Devices,omitempty"`
LogConfig LogConfig `json:"LogConfig,omitempty" yaml:"LogConfig,omitempty"`
ReadonlyRootfs bool `json:"ReadonlyRootfs,omitempty" yaml:"ReadonlyRootfs,omitempty"`
SecurityOpt []string `json:"SecurityOpt,omitempty" yaml:"SecurityOpt,omitempty"`
}
// StartContainer starts a container, returning an error in case of failure.
//
// See http://goo.gl/iM5GYs for more details.
func (c *Client) StartContainer(id string, hostConfig *HostConfig) error {
if hostConfig == nil {
hostConfig = &HostConfig{}
}
path := "/containers/" + id + "/start"
_, status, err := c.do("POST", path, hostConfig)
_, status, err := c.do("POST", path, hostConfig, true)
if status == http.StatusNotFound {
return &NoSuchContainer{ID: id}
return &NoSuchContainer{ID: id, Err: err}
}
if status == http.StatusNotModified {
return &ContainerAlreadyRunning{ID: id}
@ -389,7 +443,7 @@ func (c *Client) StartContainer(id string, hostConfig *HostConfig) error {
// See http://goo.gl/EbcpXt for more details.
func (c *Client) StopContainer(id string, timeout uint) error {
path := fmt.Sprintf("/containers/%s/stop?t=%d", id, timeout)
_, status, err := c.do("POST", path, nil)
_, status, err := c.do("POST", path, nil, false)
if status == http.StatusNotFound {
return &NoSuchContainer{ID: id}
}
@ -408,7 +462,7 @@ func (c *Client) StopContainer(id string, timeout uint) error {
// See http://goo.gl/VOzR2n for more details.
func (c *Client) RestartContainer(id string, timeout uint) error {
path := fmt.Sprintf("/containers/%s/restart?t=%d", id, timeout)
_, status, err := c.do("POST", path, nil)
_, status, err := c.do("POST", path, nil, false)
if status == http.StatusNotFound {
return &NoSuchContainer{ID: id}
}
@ -423,7 +477,7 @@ func (c *Client) RestartContainer(id string, timeout uint) error {
// See http://goo.gl/AM5t42 for more details.
func (c *Client) PauseContainer(id string) error {
path := fmt.Sprintf("/containers/%s/pause", id)
_, status, err := c.do("POST", path, nil)
_, status, err := c.do("POST", path, nil, false)
if status == http.StatusNotFound {
return &NoSuchContainer{ID: id}
}
@ -438,7 +492,7 @@ func (c *Client) PauseContainer(id string) error {
// See http://goo.gl/eBrNSL for more details.
func (c *Client) UnpauseContainer(id string) error {
path := fmt.Sprintf("/containers/%s/unpause", id)
_, status, err := c.do("POST", path, nil)
_, status, err := c.do("POST", path, nil, false)
if status == http.StatusNotFound {
return &NoSuchContainer{ID: id}
}
@ -467,7 +521,7 @@ func (c *Client) TopContainer(id string, psArgs string) (TopResult, error) {
args = fmt.Sprintf("?ps_args=%s", psArgs)
}
path := fmt.Sprintf("/containers/%s/top%s", id, args)
body, status, err := c.do("GET", path, nil)
body, status, err := c.do("GET", path, nil, false)
if status == http.StatusNotFound {
return result, &NoSuchContainer{ID: id}
}
@ -499,7 +553,7 @@ type KillContainerOptions struct {
// See http://goo.gl/TFkECx for more details.
func (c *Client) KillContainer(opts KillContainerOptions) error {
path := "/containers/" + opts.ID + "/kill" + "?" + queryString(opts)
_, status, err := c.do("POST", path, nil)
_, status, err := c.do("POST", path, nil, false)
if status == http.StatusNotFound {
return &NoSuchContainer{ID: opts.ID}
}
@ -530,7 +584,7 @@ type RemoveContainerOptions struct {
// See http://goo.gl/ZB83ji for more details.
func (c *Client) RemoveContainer(opts RemoveContainerOptions) error {
path := "/containers/" + opts.ID + "?" + queryString(opts)
_, status, err := c.do("DELETE", path, nil)
_, status, err := c.do("DELETE", path, nil, false)
if status == http.StatusNotFound {
return &NoSuchContainer{ID: opts.ID}
}
@ -559,7 +613,7 @@ func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error {
return &NoSuchContainer{ID: opts.Container}
}
url := fmt.Sprintf("/containers/%s/copy", opts.Container)
body, status, err := c.do("POST", url, opts)
body, status, err := c.do("POST", url, opts, false)
if status == http.StatusNotFound {
return &NoSuchContainer{ID: opts.Container}
}
@ -575,7 +629,7 @@ func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error {
//
// See http://goo.gl/J88DHU for more details.
func (c *Client) WaitContainer(id string) (int, error) {
body, status, err := c.do("POST", "/containers/"+id+"/wait", nil)
body, status, err := c.do("POST", "/containers/"+id+"/wait", nil, false)
if status == http.StatusNotFound {
return 0, &NoSuchContainer{ID: id}
}
@ -607,7 +661,7 @@ type CommitContainerOptions struct {
// See http://goo.gl/Jn8pe8 for more details.
func (c *Client) CommitContainer(opts CommitContainerOptions) (*Image, error) {
path := "/commit?" + queryString(opts)
body, status, err := c.do("POST", path, opts.Run)
body, status, err := c.do("POST", path, opts.Run, false)
if status == http.StatusNotFound {
return nil, &NoSuchContainer{ID: opts.Container}
}
@ -706,7 +760,7 @@ func (c *Client) ResizeContainerTTY(id string, height, width int) error {
params := make(url.Values)
params.Set("h", strconv.Itoa(height))
params.Set("w", strconv.Itoa(width))
_, _, err := c.do("POST", "/containers/"+id+"/resize?"+params.Encode(), nil)
_, _, err := c.do("POST", "/containers/"+id+"/resize?"+params.Encode(), nil, false)
return err
}
@ -734,9 +788,13 @@ func (c *Client) ExportContainer(opts ExportContainerOptions) error {
// 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
}

View File

@ -1,4 +1,4 @@
// Copyright 2014 go-dockerclient authors. All rights reserved.
// 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.
@ -7,6 +7,7 @@ package docker
import (
"bytes"
"encoding/json"
"errors"
"io/ioutil"
"net"
"net/http"
@ -154,6 +155,7 @@ func TestListContainersFailure(t *testing.T) {
func TestInspectContainer(t *testing.T) {
jsonContainer := `{
"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2",
"AppArmorProfile": "Profile",
"Created": "2013-05-07T14:51:42.087658+02:00",
"Path": "date",
"Args": [],
@ -175,7 +177,10 @@ func TestInspectContainer(t *testing.T) {
],
"Image": "base",
"Volumes": {},
"VolumesFrom": ""
"VolumesFrom": "",
"SecurityOpt": [
"label:user:USER"
]
},
"State": {
"Running": false,
@ -184,6 +189,21 @@ func TestInspectContainer(t *testing.T) {
"StartedAt": "2013-05-07T14:51:42.087658+02:00",
"Ghost": false
},
"Node": {
"ID": "4I4E:QR4I:Z733:QEZK:5X44:Q4T7:W2DD:JRDY:KB2O:PODO:Z5SR:XRB6",
"IP": "192.168.99.105",
"Addra": "192.168.99.105:2376",
"Name": "node-01",
"Cpus": 4,
"Memory": 1048436736,
"Labels": {
"executiondriver": "native-0.2",
"kernelversion": "3.18.5-tinycore64",
"operatingsystem": "Boot2Docker 1.5.0 (TCL 5.4); master : a66bce5 - Tue Feb 10 23:31:27 UTC 2015",
"provider": "virtualbox",
"storagedriver": "aufs"
}
},
"Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
"NetworkSettings": {
"IpAddress": "",
@ -511,12 +531,17 @@ func TestStartContainerNilHostConfig(t *testing.T) {
if contentType := req.Header.Get("Content-Type"); contentType != expectedContentType {
t.Errorf("StartContainer(%q): Wrong content-type in request. Want %q. Got %q.", id, expectedContentType, contentType)
}
var buf [4]byte
req.Body.Read(buf[:])
if string(buf[:]) != "null" {
t.Errorf("Startcontainer(%q): Wrong body. Want null. Got %s", buf[:])
}
}
func TestStartContainerNotFound(t *testing.T) {
client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound})
err := client.StartContainer("a2344", &HostConfig{})
expected := &NoSuchContainer{ID: "a2344"}
expected := &NoSuchContainer{ID: "a2344", Err: err.(*NoSuchContainer).Err}
if !reflect.DeepEqual(err, expected) {
t.Errorf("StartContainer: Wrong error returned. Want %#v. Got %#v.", expected, err)
}
@ -1287,6 +1312,14 @@ func TestNoSuchContainerError(t *testing.T) {
}
}
func TestNoSuchContainerErrorMessage(t *testing.T) {
var err = &NoSuchContainer{ID: "i345", Err: errors.New("some advanced error info")}
expected := "some advanced error info"
if got := err.Error(); got != expected {
t.Errorf("NoSuchContainer: wrong message. Want %q. Got %q.", expected, got)
}
}
func TestExportContainer(t *testing.T) {
content := "exported container tar content"
out := stdoutMock{bytes.NewBufferString(content)}
@ -1311,7 +1344,7 @@ func TestExportContainerViaUnixSocket(t *testing.T) {
tempSocket := tempfile("export_socket")
defer os.Remove(tempSocket)
endpoint := "unix://" + tempSocket
u, _ := parseEndpoint(endpoint)
u, _ := parseEndpoint(endpoint, false)
client := Client{
HTTPClient: http.DefaultClient,
endpoint: endpoint,
@ -1522,3 +1555,26 @@ func TestTopContainerWithPsArgs(t *testing.T) {
t.Errorf("TopContainer: Expected URI to have %q. Got %q.", expectedURI, fakeRT.requests[0].URL.String())
}
}
func TestRenameContainer(t *testing.T) {
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
client := newTestClient(fakeRT)
opts := RenameContainerOptions{ID: "something_old", Name: "something_new"}
err := client.RenameContainer(opts)
if err != nil {
t.Fatal(err)
}
req := fakeRT.requests[0]
if req.Method != "POST" {
t.Errorf("RenameContainer: wrong HTTP method. Want %q. Got %q.", "POST", req.Method)
}
expectedURL, _ := url.Parse(client.getURL("/containers/something_old/rename?name=something_new"))
if gotPath := req.URL.Path; gotPath != expectedURL.Path {
t.Errorf("RenameContainer: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath)
}
expectedValues := expectedURL.Query()["name"]
actualValues := req.URL.Query()["name"]
if len(actualValues) != 1 || expectedValues[0] != actualValues[0] {
t.Errorf("RenameContainer: Wrong params in request. Want %q. Got %q.", expectedValues, actualValues)
}
}

View File

@ -1,4 +1,4 @@
// Copyright 2014 go-dockerclient authors. All rights reserved.
// 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.
@ -182,8 +182,8 @@ func (eventState *eventMonitoringState) monitorEvents(c *Client) {
eventState.terminate()
return
}
eventState.updateLastSeen(ev)
go eventState.sendEvent(ev)
go eventState.updateLastSeen(ev)
case err = <-eventState.errC:
if err == ErrNoListeners {
eventState.terminate()
@ -227,7 +227,7 @@ func (eventState *eventMonitoringState) sendEvent(event *APIEvents) {
eventState.Add(1)
defer eventState.Done()
if eventState.isEnabled() {
if eventState.noListeners() {
if len(eventState.listeners) == 0 {
eventState.errC <- ErrNoListeners
return
}

View File

@ -1,4 +1,4 @@
// Copyright 2014 go-dockerclient authors. All rights reserved.
// 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.
@ -90,7 +90,7 @@ type ExecInspect struct {
// See http://goo.gl/8izrzI for more details
func (c *Client) CreateExec(opts CreateExecOptions) (*Exec, error) {
path := fmt.Sprintf("/containers/%s/exec", opts.Container)
body, status, err := c.do("POST", path, opts)
body, status, err := c.do("POST", path, opts, false)
if status == http.StatusNotFound {
return nil, &NoSuchContainer{ID: opts.Container}
}
@ -119,7 +119,7 @@ func (c *Client) StartExec(id string, opts StartExecOptions) error {
path := fmt.Sprintf("/exec/%s/start", id)
if opts.Detach {
_, status, err := c.do("POST", path, opts)
_, status, err := c.do("POST", path, opts, false)
if status == http.StatusNotFound {
return &NoSuchExec{ID: id}
}
@ -143,7 +143,7 @@ func (c *Client) ResizeExecTTY(id string, height, width int) error {
params.Set("w", strconv.Itoa(width))
path := fmt.Sprintf("/exec/%s/resize?%s", id, params.Encode())
_, _, err := c.do("POST", path, nil)
_, _, err := c.do("POST", path, nil, false)
return err
}
@ -152,7 +152,7 @@ func (c *Client) ResizeExecTTY(id string, height, width int) error {
// See http://goo.gl/ypQULN for more details
func (c *Client) InspectExec(id string) (*ExecInspect, error) {
path := fmt.Sprintf("/exec/%s/json", id)
body, status, err := c.do("GET", path, nil)
body, status, err := c.do("GET", path, nil, false)
if status == http.StatusNotFound {
return nil, &NoSuchExec{ID: id}
}

View File

@ -1,4 +1,4 @@
// Copyright 2014 go-dockerclient authors. All rights reserved.
// 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.

View File

@ -1,4 +1,4 @@
// Copyright 2014 go-dockerclient authors. All rights reserved.
// 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.
@ -99,7 +99,7 @@ var (
// See http://goo.gl/2rOLFF for more details.
func (c *Client) ListImages(opts ListImagesOptions) ([]APIImages, error) {
path := "/images/json?" + queryString(opts)
body, _, err := c.do("GET", path, nil)
body, _, err := c.do("GET", path, nil, false)
if err != nil {
return nil, err
}
@ -115,7 +115,7 @@ func (c *Client) ListImages(opts ListImagesOptions) ([]APIImages, error) {
//
// See http://goo.gl/2oJmNs for more details.
func (c *Client) ImageHistory(name string) ([]ImageHistory, error) {
body, status, err := c.do("GET", "/images/"+name+"/history", nil)
body, status, err := c.do("GET", "/images/"+name+"/history", nil, false)
if status == http.StatusNotFound {
return nil, ErrNoSuchImage
}
@ -134,7 +134,29 @@ func (c *Client) ImageHistory(name string) ([]ImageHistory, error) {
//
// See http://goo.gl/znj0wM for more details.
func (c *Client) RemoveImage(name string) error {
_, status, err := c.do("DELETE", "/images/"+name, nil)
_, status, err := c.do("DELETE", "/images/"+name, nil, false)
if status == http.StatusNotFound {
return ErrNoSuchImage
}
return err
}
// RemoveImageOptions present the set of options available for removing an image
// from a registry.
//
// See http://goo.gl/6V48bF for more details.
type RemoveImageOptions struct {
Force bool `qs:"force"`
NoPrune bool `qs:"noprune"`
}
// RemoveImageExtended removes an image by its name or ID.
// Extra params can be passed, see RemoveImageOptions
//
// See http://goo.gl/znj0wM for more details.
func (c *Client) RemoveImageExtended(name string, opts RemoveImageOptions) error {
uri := fmt.Sprintf("/images/%s?%s", name, queryString(&opts))
_, status, err := c.do("DELETE", uri, nil, false)
if status == http.StatusNotFound {
return ErrNoSuchImage
}
@ -145,7 +167,7 @@ func (c *Client) RemoveImage(name string) error {
//
// See http://goo.gl/Q112NY for more details.
func (c *Client) InspectImage(name string) (*Image, error) {
body, status, err := c.do("GET", "/images/"+name+"/json", nil)
body, status, err := c.do("GET", "/images/"+name+"/json", nil, false)
if status == http.StatusNotFound {
return nil, ErrNoSuchImage
}
@ -156,7 +178,7 @@ func (c *Client) InspectImage(name string) (*Image, error) {
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(apiVersion1_12) {
if c.SkipServerVersionCheck || c.expectedAPIVersion.GreaterThanOrEqualTo(apiVersion112) {
err = json.Unmarshal(body, &image)
if err != nil {
return nil, err
@ -201,21 +223,6 @@ type PushImageOptions struct {
RawJSONStream bool `qs:"-"`
}
// 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"`
}
// PushImage pushes an image to a remote registry, logging progress to w.
//
// An empty instance of AuthConfiguration may be used for unauthenticated
@ -245,7 +252,7 @@ type PullImageOptions struct {
RawJSONStream bool `qs:"-"`
}
// PullImage pulls an image from a remote registry, logging progress to w.
// PullImage pulls an image from a remote registry, logging progress to opts.OutputStream.
//
// See http://goo.gl/ACyYNS for more details.
func (c *Client) PullImage(opts PullImageOptions, auth AuthConfiguration) error {
@ -333,6 +340,7 @@ func (c *Client) ImportImage(opts ImportImageOptions) error {
// http://goo.gl/tlPXPu.
type BuildImageOptions struct {
Name string `qs:"t"`
Dockerfile string `qs:"dockerfile"`
NoCache bool `qs:"nocache"`
SuppressOutput bool `qs:"q"`
RmTmpContainer bool `qs:"rm"`
@ -395,7 +403,8 @@ func (c *Client) TagImage(name string, opts TagImageOptions) error {
return ErrNoSuchImage
}
_, status, err := c.do("POST", fmt.Sprintf("/images/"+name+"/tag?%s",
queryString(&opts)), nil)
queryString(&opts)), nil, false)
if status == http.StatusNotFound {
return ErrNoSuchImage
}
@ -445,7 +454,7 @@ type APIImageSearch struct {
//
// See http://goo.gl/xI5lLZ for more details.
func (c *Client) SearchImages(term string) ([]APIImageSearch, error) {
body, _, err := c.do("GET", "/images/search?term="+term, nil)
body, _, err := c.do("GET", "/images/search?term="+term, nil, false)
if err != nil {
return nil, err
}

View File

@ -1,4 +1,4 @@
// Copyright 2014 go-dockerclient authors. All rights reserved.
// 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.
@ -19,7 +19,7 @@ import (
func newTestClient(rt *FakeRoundTripper) Client {
endpoint := "http://localhost:4243"
u, _ := parseEndpoint("http://localhost:4243")
u, _ := parseEndpoint("http://localhost:4243", false)
client := Client{
HTTPClient: &http.Client{Transport: rt},
endpoint: endpoint,
@ -208,6 +208,29 @@ func TestRemoveImageNotFound(t *testing.T) {
}
}
func TestRemoveImageExtended(t *testing.T) {
name := "test"
fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent}
client := newTestClient(fakeRT)
err := client.RemoveImageExtended(name, RemoveImageOptions{Force: true, NoPrune: true})
if err != nil {
t.Fatal(err)
}
req := fakeRT.requests[0]
expectedMethod := "DELETE"
if req.Method != expectedMethod {
t.Errorf("RemoveImage(%q): Wrong HTTP method. Want %s. Got %s.", name, expectedMethod, req.Method)
}
u, _ := url.Parse(client.getURL("/images/" + name))
if req.URL.Path != u.Path {
t.Errorf("RemoveImage(%q): Wrong request path. Want %q. Got %q.", name, u.Path, req.URL.Path)
}
expectedQuery := "force=1&noprune=1"
if query := req.URL.Query().Encode(); query != expectedQuery {
t.Errorf("PushImage: Wrong query string. Want %q. Got %q.", expectedQuery, query)
}
}
func TestInspectImage(t *testing.T) {
body := `{
"id":"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",

View File

@ -1,4 +1,4 @@
// Copyright 2014 go-dockerclient authors. All rights reserved.
// 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.
@ -13,7 +13,7 @@ import (
//
// See http://goo.gl/BOZrF5 for more details.
func (c *Client) Version() (*Env, error) {
body, _, err := c.do("GET", "/version", nil)
body, _, err := c.do("GET", "/version", nil, false)
if err != nil {
return nil, err
}
@ -28,7 +28,7 @@ func (c *Client) Version() (*Env, error) {
//
// See http://goo.gl/wmqZsW for more details.
func (c *Client) Info() (*Env, error) {
body, _, err := c.do("GET", "/info", nil)
body, _, err := c.do("GET", "/info", nil, false)
if err != nil {
return nil, err
}

View File

@ -1,4 +1,4 @@
// Copyright 2014 go-dockerclient authors. All rights reserved.
// 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.

View File

@ -1,4 +1,4 @@
// Copyright 2014 go-dockerclient authors. All rights reserved.
// 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.
@ -35,6 +35,7 @@ import (
type DockerServer struct {
containers []*docker.Container
execs []*docker.ExecInspect
execMut sync.RWMutex
cMut sync.RWMutex
images []docker.Image
iMut sync.RWMutex
@ -43,6 +44,8 @@ type DockerServer struct {
mux *mux.Router
hook func(*http.Request)
failures map[string]string
multiFailures []map[string]string
execCallbacks map[string]func()
customHandlers map[string]http.Handler
handlerMutex sync.RWMutex
cChan chan<- *docker.Container
@ -69,6 +72,7 @@ func NewServer(bind string, containerChan chan<- *docker.Container, hook func(*h
imgIDs: make(map[string]string),
hook: hook,
failures: make(map[string]string),
execCallbacks: make(map[string]func()),
customHandlers: make(map[string]http.Handler),
cChan: containerChan,
}
@ -89,6 +93,7 @@ func (s *DockerServer) buildMuxer() {
s.mux.Path("/containers/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.listContainers))
s.mux.Path("/containers/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.createContainer))
s.mux.Path("/containers/{id:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectContainer))
s.mux.Path("/containers/{id:.*}/rename").Methods("POST").HandlerFunc(s.handlerWrapper(s.renameContainer))
s.mux.Path("/containers/{id:.*}/top").Methods("GET").HandlerFunc(s.handlerWrapper(s.topContainer))
s.mux.Path("/containers/{id:.*}/start").Methods("POST").HandlerFunc(s.handlerWrapper(s.startContainer))
s.mux.Path("/containers/{id:.*}/kill").Methods("POST").HandlerFunc(s.handlerWrapper(s.stopContainer))
@ -115,17 +120,59 @@ func (s *DockerServer) buildMuxer() {
s.mux.Path("/images/{id:.*}/get").Methods("GET").HandlerFunc(s.handlerWrapper(s.getImage))
}
// SetHook changes the hook function used by the server.
//
// The hook function is a function called on every request.
func (s *DockerServer) SetHook(hook func(*http.Request)) {
s.hook = hook
}
// PrepareExec adds a callback to a container exec in the fake server.
//
// This function will be called whenever the given exec id is started, and the
// given exec id will remain in the "Running" start while the function is
// running, so it's useful for emulating an exec that runs for two seconds, for
// example:
//
// opts := docker.CreateExecOptions{
// AttachStdin: true,
// AttachStdout: true,
// AttachStderr: true,
// Tty: true,
// Cmd: []string{"/bin/bash", "-l"},
// }
// // Client points to a fake server.
// exec, err := client.CreateExec(opts)
// // handle error
// server.PrepareExec(exec.ID, func() {time.Sleep(2 * time.Second)})
// err = client.StartExec(exec.ID, docker.StartExecOptions{Tty: true}) // will block for 2 seconds
// // handle error
func (s *DockerServer) PrepareExec(id string, callback func()) {
s.execCallbacks[id] = callback
}
// PrepareFailure adds a new expected failure based on a URL regexp it receives
// an id for the failure.
func (s *DockerServer) PrepareFailure(id string, urlRegexp string) {
s.failures[id] = urlRegexp
}
// PrepareMultiFailures enqueues a new expected failure based on a URL regexp
// it receives an id for the failure.
func (s *DockerServer) PrepareMultiFailures(id string, urlRegexp string) {
s.multiFailures = append(s.multiFailures, map[string]string{"error": id, "url": urlRegexp})
}
// ResetFailure removes an expected failure identified by the given id.
func (s *DockerServer) ResetFailure(id string) {
delete(s.failures, id)
}
// ResetMultiFailures removes all enqueued failures.
func (s *DockerServer) ResetMultiFailures() {
s.multiFailures = []map[string]string{}
}
// CustomHandler registers a custom handler for a specific path.
//
// For example:
@ -170,10 +217,12 @@ func (s *DockerServer) URL() string {
func (s *DockerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.handlerMutex.RLock()
defer s.handlerMutex.RUnlock()
if handler, ok := s.customHandlers[r.URL.Path]; ok {
for re, handler := range s.customHandlers {
if m, _ := regexp.MatchString(re, r.URL.Path); m {
handler.ServeHTTP(w, r)
return
}
}
s.mux.ServeHTTP(w, r)
if s.hook != nil {
s.hook(r)
@ -200,6 +249,19 @@ func (s *DockerServer) handlerWrapper(f func(http.ResponseWriter, *http.Request)
http.Error(w, errorID, http.StatusBadRequest)
return
}
for i, failure := range s.multiFailures {
matched, err := regexp.MatchString(failure["url"], r.URL.Path)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if !matched {
continue
}
http.Error(w, failure["error"], http.StatusBadRequest)
s.multiFailures = append(s.multiFailures[:i], s.multiFailures[i+1:]...)
return
}
f(w, r)
}
}
@ -336,6 +398,23 @@ func (s *DockerServer) generateID() string {
return fmt.Sprintf("%x", buf)
}
func (s *DockerServer) renameContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
container, index, err := s.findContainer(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
copy := *container
copy.Name = r.URL.Query().Get("name")
s.cMut.Lock()
defer s.cMut.Unlock()
if s.containers[index].ID == copy.ID {
s.containers[index] = &copy
}
w.WriteHeader(http.StatusNoContent)
}
func (s *DockerServer) inspectContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
container, _, err := s.findContainer(id)
@ -524,9 +603,13 @@ func (s *DockerServer) commitContainer(w http.ResponseWriter, r *http.Request) {
Config: config,
}
repository := r.URL.Query().Get("repo")
tag := r.URL.Query().Get("tag")
s.iMut.Lock()
s.images = append(s.images, image)
if repository != "" {
if tag != "" {
repository += ":" + tag
}
s.imgIDs[repository] = image.ID
}
s.iMut.Unlock()
@ -582,20 +665,28 @@ func (s *DockerServer) buildImage(w http.ResponseWriter, r *http.Request) {
}
func (s *DockerServer) pullImage(w http.ResponseWriter, r *http.Request) {
repository := r.URL.Query().Get("fromImage")
fromImageName := r.URL.Query().Get("fromImage")
tag := r.URL.Query().Get("tag")
image := docker.Image{
ID: s.generateID(),
}
s.iMut.Lock()
s.images = append(s.images, image)
if repository != "" {
s.imgIDs[repository] = image.ID
if fromImageName != "" {
if tag != "" {
fromImageName = fmt.Sprintf("%s:%s", fromImageName, tag)
}
s.imgIDs[fromImageName] = image.ID
}
s.iMut.Unlock()
}
func (s *DockerServer) pushImage(w http.ResponseWriter, r *http.Request) {
name := mux.Vars(r)["name"]
tag := r.URL.Query().Get("tag")
if tag != "" {
name += ":" + tag
}
s.iMut.RLock()
if _, ok := s.imgIDs[name]; !ok {
s.iMut.RUnlock()
@ -619,6 +710,10 @@ func (s *DockerServer) tagImage(w http.ResponseWriter, r *http.Request) {
s.iMut.Lock()
defer s.iMut.Unlock()
newRepo := r.URL.Query().Get("repo")
newTag := r.URL.Query().Get("tag")
if newTag != "" {
newRepo += ":" + newTag
}
s.imgIDs[newRepo] = s.imgIDs[name]
w.WriteHeader(http.StatusCreated)
}
@ -722,7 +817,6 @@ func (s *DockerServer) loadImage(w http.ResponseWriter, r *http.Request) {
func (s *DockerServer) getImage(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/tar")
}
func (s *DockerServer) createExecContainer(w http.ResponseWriter, r *http.Request) {
@ -733,7 +827,7 @@ func (s *DockerServer) createExecContainer(w http.ResponseWriter, r *http.Reques
return
}
exec := docker.ExecInspect{
ID: "id-exec-created-by-test",
ID: s.generateID(),
Container: *container,
}
var params docker.CreateExecOptions
@ -748,44 +842,63 @@ func (s *DockerServer) createExecContainer(w http.ResponseWriter, r *http.Reques
exec.ProcessConfig.Arguments = params.Cmd[1:]
}
}
s.execMut.Lock()
s.execs = append(s.execs, &exec)
s.execMut.Unlock()
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"Id": exec.ID})
}
func (s *DockerServer) startExecContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
for _, exec := range s.execs {
if exec.ID == id {
if exec, err := s.getExec(id); err == nil {
s.execMut.Lock()
exec.Running = true
s.execMut.Unlock()
if callback, ok := s.execCallbacks[id]; ok {
callback()
delete(s.execCallbacks, id)
} else if callback, ok := s.execCallbacks["*"]; ok {
callback()
delete(s.execCallbacks, "*")
}
s.execMut.Lock()
exec.Running = false
s.execMut.Unlock()
w.WriteHeader(http.StatusOK)
return
}
}
w.WriteHeader(http.StatusNotFound)
}
func (s *DockerServer) resizeExecContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
for _, exec := range s.execs {
if exec.ID == id {
if _, err := s.getExec(id); err == nil {
w.WriteHeader(http.StatusOK)
return
}
}
w.WriteHeader(http.StatusNotFound)
}
func (s *DockerServer) inspectExecContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
for _, exec := range s.execs {
if exec.ID == id {
if exec, err := s.getExec(id); err == nil {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(exec)
return
}
}
w.WriteHeader(http.StatusNotFound)
}
func (s *DockerServer) getExec(id string) (*docker.ExecInspect, error) {
s.execMut.RLock()
defer s.execMut.RUnlock()
for _, exec := range s.execs {
if exec.ID == id {
return exec, nil
}
}
return nil, errors.New("exec not found")
}

View File

@ -1,4 +1,4 @@
// Copyright 2014 go-dockerclient authors. All rights reserved.
// 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.
@ -82,6 +82,19 @@ func TestHandleWithHook(t *testing.T) {
}
}
func TestSetHook(t *testing.T) {
var called bool
server, _ := NewServer("127.0.0.1:0", nil, nil)
defer server.Stop()
server.SetHook(func(*http.Request) { called = true })
recorder := httptest.NewRecorder()
request, _ := http.NewRequest("GET", "/containers/json?all=1", nil)
server.ServeHTTP(recorder, request)
if !called {
t.Error("ServeHTTP did not call the hook function.")
}
}
func TestCustomHandler(t *testing.T) {
var called bool
server, _ := NewServer("127.0.0.1:0", nil, nil)
@ -101,6 +114,25 @@ func TestCustomHandler(t *testing.T) {
}
}
func TestCustomHandlerRegexp(t *testing.T) {
var called bool
server, _ := NewServer("127.0.0.1:0", nil, nil)
addContainers(server, 2)
server.CustomHandler("/containers/.*/json", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
called = true
fmt.Fprint(w, "Hello world")
}))
recorder := httptest.NewRecorder()
request, _ := http.NewRequest("GET", "/containers/.*/json?all=1", nil)
server.ServeHTTP(recorder, request)
if !called {
t.Error("Did not call the custom handler")
}
if got := recorder.Body.String(); got != "Hello world" {
t.Errorf("Wrong output for custom handler: want %q. Got %q.", "Hello world", got)
}
}
func TestListContainers(t *testing.T) {
server := DockerServer{}
addContainers(&server, 2)
@ -223,6 +255,35 @@ func TestCreateContainerImageNotFound(t *testing.T) {
}
}
func TestRenameContainer(t *testing.T) {
server := DockerServer{}
addContainers(&server, 2)
server.buildMuxer()
recorder := httptest.NewRecorder()
newName := server.containers[0].Name + "abc"
path := fmt.Sprintf("/containers/%s/rename?name=%s", server.containers[0].ID, newName)
request, _ := http.NewRequest("POST", path, nil)
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusNoContent {
t.Errorf("RenameContainer: wrong status. Want %d. Got %d.", http.StatusNoContent, recorder.Code)
}
container := server.containers[0]
if container.Name != newName {
t.Errorf("RenameContainer: did not rename the container. Want %q. Got %q.", newName, container.Name)
}
}
func TestRenameContainerNotFound(t *testing.T) {
server := DockerServer{}
server.buildMuxer()
recorder := httptest.NewRecorder()
request, _ := http.NewRequest("POST", "/containers/blabla/rename?name=something", nil)
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusNotFound {
t.Errorf("RenameContainer: wrong status. Want %d. Got %d.", http.StatusNotFound, recorder.Code)
}
}
func TestCommitContainer(t *testing.T) {
server := DockerServer{}
addContainers(&server, 2)
@ -277,6 +338,27 @@ func TestCommitContainerComplete(t *testing.T) {
}
}
func TestCommitContainerWithTag(t *testing.T) {
server := DockerServer{}
server.imgIDs = make(map[string]string)
addContainers(&server, 2)
server.buildMuxer()
recorder := httptest.NewRecorder()
queryString := "container=" + server.containers[0].ID + "&repo=tsuru/python&tag=v1"
request, _ := http.NewRequest("POST", "/commit?"+queryString, nil)
server.ServeHTTP(recorder, request)
image := server.images[0]
if image.Parent != server.containers[0].Image {
t.Errorf("CommitContainer: wrong parent image. Want %q. Got %q.", server.containers[0].Image, image.Parent)
}
if image.Container != server.containers[0].ID {
t.Errorf("CommitContainer: wrong container. Want %q. Got %q.", server.containers[0].ID, image.Container)
}
if id := server.imgIDs["tsuru/python:v1"]; id != image.ID {
t.Errorf("CommitContainer: wrong ID saved for repository. Want %q. Got %q.", image.ID, id)
}
}
func TestCommitContainerInvalidRun(t *testing.T) {
server := DockerServer{}
addContainers(&server, 1)
@ -767,6 +849,23 @@ func TestPullImage(t *testing.T) {
}
}
func TestPullImageWithTag(t *testing.T) {
server := DockerServer{imgIDs: make(map[string]string)}
server.buildMuxer()
recorder := httptest.NewRecorder()
request, _ := http.NewRequest("POST", "/images/create?fromImage=base&tag=tag", nil)
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Errorf("PullImage: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
}
if len(server.images) != 1 {
t.Errorf("PullImage: Want 1 image. Got %d.", len(server.images))
}
if _, ok := server.imgIDs["base:tag"]; !ok {
t.Error("PullImage: Repository should not be empty.")
}
}
func TestPushImage(t *testing.T) {
server := DockerServer{imgIDs: map[string]string{"tsuru/python": "a123"}}
server.buildMuxer()
@ -778,6 +877,17 @@ func TestPushImage(t *testing.T) {
}
}
func TestPushImageWithTag(t *testing.T) {
server := DockerServer{imgIDs: map[string]string{"tsuru/python:v1": "a123"}}
server.buildMuxer()
recorder := httptest.NewRecorder()
request, _ := http.NewRequest("POST", "/images/tsuru/python/push?tag=v1", nil)
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Errorf("PushImage: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
}
}
func TestPushImageNotFound(t *testing.T) {
server := DockerServer{}
server.buildMuxer()
@ -803,6 +913,20 @@ func TestTagImage(t *testing.T) {
}
}
func TestTagImageWithRepoAndTag(t *testing.T) {
server := DockerServer{imgIDs: map[string]string{"tsuru/python": "a123"}}
server.buildMuxer()
recorder := httptest.NewRecorder()
request, _ := http.NewRequest("POST", "/images/tsuru/python/tag?repo=tsuru/new-python&tag=v1", nil)
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusCreated {
t.Errorf("TagImage: wrong status. Want %d. Got %d.", http.StatusCreated, recorder.Code)
}
if server.imgIDs["tsuru/python"] != server.imgIDs["tsuru/new-python:v1"] {
t.Errorf("TagImage: did not tag the image")
}
}
func TestTagImageNotFound(t *testing.T) {
server := DockerServer{}
server.buildMuxer()
@ -986,6 +1110,41 @@ func TestPrepareFailure(t *testing.T) {
}
}
func TestPrepareMultiFailures(t *testing.T) {
server := DockerServer{multiFailures: []map[string]string{}}
server.buildMuxer()
errorID := "multi error"
server.PrepareMultiFailures(errorID, "containers/json")
server.PrepareMultiFailures(errorID, "containers/json")
recorder := httptest.NewRecorder()
request, _ := http.NewRequest("GET", "/containers/json?all=1", nil)
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusBadRequest {
t.Errorf("PrepareFailure: wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code)
}
if recorder.Body.String() != errorID+"\n" {
t.Errorf("PrepareFailure: wrong message. Want %s. Got %s.", errorID, recorder.Body.String())
}
recorder = httptest.NewRecorder()
request, _ = http.NewRequest("GET", "/containers/json?all=1", nil)
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusBadRequest {
t.Errorf("PrepareFailure: wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code)
}
if recorder.Body.String() != errorID+"\n" {
t.Errorf("PrepareFailure: wrong message. Want %s. Got %s.", errorID, recorder.Body.String())
}
recorder = httptest.NewRecorder()
request, _ = http.NewRequest("GET", "/containers/json?all=1", nil)
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Errorf("PrepareFailure: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
}
if recorder.Body.String() == errorID+"\n" {
t.Errorf("PrepareFailure: wrong message. Want %s. Got %s.", errorID, recorder.Body.String())
}
}
func TestRemoveFailure(t *testing.T) {
server := DockerServer{failures: make(map[string]string)}
server.buildMuxer()
@ -1006,6 +1165,21 @@ func TestRemoveFailure(t *testing.T) {
}
}
func TestResetMultiFailures(t *testing.T) {
server := DockerServer{multiFailures: []map[string]string{}}
server.buildMuxer()
errorID := "multi error"
server.PrepareMultiFailures(errorID, "containers/json")
server.PrepareMultiFailures(errorID, "containers/json")
if len(server.multiFailures) != 2 {
t.Errorf("PrepareMultiFailures: error adding multi failures.")
}
server.ResetMultiFailures()
if len(server.multiFailures) != 0 {
t.Errorf("ResetMultiFailures: error reseting multi failures.")
}
}
func TestMutateContainer(t *testing.T) {
server := DockerServer{failures: make(map[string]string)}
server.buildMuxer()
@ -1169,3 +1343,132 @@ func TestInspectExecContainer(t *testing.T) {
t.Errorf("InspectContainer: wrong value. Want:\n%#v\nGot:\n%#v\n", expected, got2)
}
}
func TestStartExecContainer(t *testing.T) {
server, _ := NewServer("127.0.0.1:0", nil, nil)
addContainers(server, 1)
server.buildMuxer()
recorder := httptest.NewRecorder()
body := `{"Cmd": ["bash", "-c", "ls"]}`
path := fmt.Sprintf("/containers/%s/exec", server.containers[0].ID)
request, _ := http.NewRequest("POST", path, strings.NewReader(body))
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("CreateExec: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
}
var exec docker.Exec
err := json.NewDecoder(recorder.Body).Decode(&exec)
if err != nil {
t.Fatal(err)
}
unleash := make(chan bool)
server.PrepareExec(exec.ID, func() {
<-unleash
})
codes := make(chan int, 1)
sent := make(chan bool)
go func() {
recorder := httptest.NewRecorder()
path := fmt.Sprintf("/exec/%s/start", exec.ID)
body := `{"Tty":true}`
request, _ := http.NewRequest("POST", path, strings.NewReader(body))
close(sent)
server.ServeHTTP(recorder, request)
codes <- recorder.Code
}()
<-sent
execInfo, err := waitExec(server.URL(), exec.ID, true, 5)
if err != nil {
t.Fatal(err)
}
if !execInfo.Running {
t.Error("StartExec: expected exec to be running, but it's not running")
}
close(unleash)
if code := <-codes; code != http.StatusOK {
t.Errorf("StartExec: wrong status. Want %d. Got %d.", http.StatusOK, code)
}
execInfo, err = waitExec(server.URL(), exec.ID, false, 5)
if err != nil {
t.Fatal(err)
}
if execInfo.Running {
t.Error("StartExec: expected exec to be not running after start returns, but it's running")
}
}
func TestStartExecContainerWildcardCallback(t *testing.T) {
server, _ := NewServer("127.0.0.1:0", nil, nil)
addContainers(server, 1)
server.buildMuxer()
recorder := httptest.NewRecorder()
body := `{"Cmd": ["bash", "-c", "ls"]}`
path := fmt.Sprintf("/containers/%s/exec", server.containers[0].ID)
request, _ := http.NewRequest("POST", path, strings.NewReader(body))
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("CreateExec: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
}
unleash := make(chan bool)
server.PrepareExec("*", func() {
<-unleash
})
var exec docker.Exec
err := json.NewDecoder(recorder.Body).Decode(&exec)
if err != nil {
t.Fatal(err)
}
codes := make(chan int, 1)
sent := make(chan bool)
go func() {
recorder := httptest.NewRecorder()
path := fmt.Sprintf("/exec/%s/start", exec.ID)
body := `{"Tty":true}`
request, _ := http.NewRequest("POST", path, strings.NewReader(body))
close(sent)
server.ServeHTTP(recorder, request)
codes <- recorder.Code
}()
<-sent
execInfo, err := waitExec(server.URL(), exec.ID, true, 5)
if err != nil {
t.Fatal(err)
}
if !execInfo.Running {
t.Error("StartExec: expected exec to be running, but it's not running")
}
close(unleash)
if code := <-codes; code != http.StatusOK {
t.Errorf("StartExec: wrong status. Want %d. Got %d.", http.StatusOK, code)
}
execInfo, err = waitExec(server.URL(), exec.ID, false, 5)
if err != nil {
t.Fatal(err)
}
if execInfo.Running {
t.Error("StartExec: expected exec to be not running after start returns, but it's running")
}
}
func TestStartExecContainerNotFound(t *testing.T) {
server, _ := NewServer("127.0.0.1:0", nil, nil)
addContainers(server, 1)
server.buildMuxer()
recorder := httptest.NewRecorder()
body := `{"Tty":true}`
request, _ := http.NewRequest("POST", "/exec/something-wat/start", strings.NewReader(body))
server.ServeHTTP(recorder, request)
}
func waitExec(url, execID string, running bool, maxTry int) (*docker.ExecInspect, error) {
client, err := docker.NewClient(url)
if err != nil {
return nil, err
}
exec, err := client.InspectExec(execID)
for i := 0; i < maxTry && exec.Running != running && err == nil; i++ {
time.Sleep(100e6)
exec, err = client.InspectExec(exec.ID)
}
return exec, err
}