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", "ImportPath": "github.com/fsouza/go-dockerclient",
"Rev": "d19717788084716e4adff0515be6289aa04bec46" "Rev": "17d39bcb22e8103ba6d1c0cb2530c6434cb870a3"
}, },
{ {
"ImportPath": "github.com/garyburd/redigo/internal", "ImportPath": "github.com/garyburd/redigo/internal",

View File

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

View File

@ -1,25 +1,31 @@
# This is the official list of go-dockerclient authors for copyright purposes. # This is the official list of go-dockerclient authors for copyright purposes.
Adam Bell-Hanssen <adamb@aller.no>
Aldrin Leal <aldrin@leal.eng.br> Aldrin Leal <aldrin@leal.eng.br>
Andreas Jaekle <andreas@jaekle.net> Andreas Jaekle <andreas@jaekle.net>
Andrews Medina <andrewsmedina@gmail.com> Andrews Medina <andrewsmedina@gmail.com>
Artem Sidorenko <artem@2realities.com> Artem Sidorenko <artem@2realities.com>
Andy Goldstein <andy.goldstein@redhat.com> Andy Goldstein <andy.goldstein@redhat.com>
Ben Marini <ben@remind101.com>
Ben McCann <benmccann.com> Ben McCann <benmccann.com>
Brian Lalor <blalor@bravo5.org>
Burke Libbey <burke@libbey.me>
Carlos Diaz-Padron <cpadron@mozilla.com> Carlos Diaz-Padron <cpadron@mozilla.com>
Cezar Sa Espinola <cezar.sa@corp.globo.com> Cezar Sa Espinola <cezar.sa@corp.globo.com>
Cheah Chu Yeow <chuyeow@gmail.com> Cheah Chu Yeow <chuyeow@gmail.com>
cheneydeng <cheneydeng@qq.com> cheneydeng <cheneydeng@qq.com>
CMGS <ilskdw@gmail.com> CMGS <ilskdw@gmail.com>
Daniel, Dao Quang Minh <dqminh89@gmail.com> Daniel, Dao Quang Minh <dqminh89@gmail.com>
Darren Shepherd <darren@rancher.com>
David Huie <dahuie@gmail.com> David Huie <dahuie@gmail.com>
Dawn Chen <dawnchen@google.com> Dawn Chen <dawnchen@google.com>
Ed <edrocksit@gmail.com> Ed <edrocksit@gmail.com>
Eric Anderson <anderson@copperegg.com> Eric Anderson <anderson@copperegg.com>
Fabio Rehm <fgrehm@gmail.com> Fabio Rehm <fgrehm@gmail.com>
Fatih Arslan <ftharsln@gmail.com> Fatih Arslan <ftharsln@gmail.com>
Flavia Missi <flaviamissi@gmail.com> Flavia Missi <flaviamissi@gmail.com>
Francisco Souza <f@souza.cc> Francisco Souza <f@souza.cc>
Guillermo Álvarez Fernández <guillermo@cientifico.net>
Jari Kolehmainen <jari.kolehmainen@digia.com> Jari Kolehmainen <jari.kolehmainen@digia.com>
Jason Wilder <jwilder@litl.com> Jason Wilder <jwilder@litl.com>
Jawher Moussa <jawher.moussa@gmail.com> Jawher Moussa <jawher.moussa@gmail.com>
@ -30,19 +36,26 @@ Johan Euphrosine <proppy@google.com>
Kamil Domanski <kamil@domanski.co> Kamil Domanski <kamil@domanski.co>
Karan Misra <kidoman@gmail.com> Karan Misra <kidoman@gmail.com>
Kim, Hirokuni <hirokuni.kim@kvh.co.jp> Kim, Hirokuni <hirokuni.kim@kvh.co.jp>
liron-l <levinlir@gmail.com>
Lucas Clemente <lucas@clemente.io> Lucas Clemente <lucas@clemente.io>
Lucas Weiblen <lucasweiblen@gmail.com>
Mantas Matelis <mmatelis@coursera.org>
Martin Sweeney <martin@sweeney.io> Martin Sweeney <martin@sweeney.io>
Máximo Cuadros Ortiz <mcuadros@gmail.com> Máximo Cuadros Ortiz <mcuadros@gmail.com>
Michal Fojtik <mfojtik@redhat.com>
Mike Dillon <mike.dillon@synctree.com> Mike Dillon <mike.dillon@synctree.com>
Mrunal Patel <mrunalp@gmail.com> Mrunal Patel <mrunalp@gmail.com>
Nick Ethier <ncethier@gmail.com>
Omeid Matten <public@omeid.me> Omeid Matten <public@omeid.me>
Paul Morie <pmorie@gmail.com> Paul Morie <pmorie@gmail.com>
Paul Weil <pweil@redhat.com>
Peter Jihoon Kim <raingrove@gmail.com> Peter Jihoon Kim <raingrove@gmail.com>
Philippe Lafoucrière <philippe.lafoucriere@tech-angels.com> Philippe Lafoucrière <philippe.lafoucriere@tech-angels.com>
Rafe Colton <rafael.colton@gmail.com> Rafe Colton <rafael.colton@gmail.com>
Rob Miller <rob@kalistra.com> Rob Miller <rob@kalistra.com>
Robert Williamson <williamson.robert@gmail.com> Robert Williamson <williamson.robert@gmail.com>
Salvador Gironès <salvadorgirones@gmail.com> Salvador Gironès <salvadorgirones@gmail.com>
Sam Rijs <srijs@airpost.net>
Simon Eskildsen <sirup@sirupsen.com> Simon Eskildsen <sirup@sirupsen.com>
Simon Menke <simon.menke@gmail.com> Simon Menke <simon.menke@gmail.com>
Skolos <skolos@gopherlab.com> Skolos <skolos@gopherlab.com>
@ -51,5 +64,7 @@ Sridhar Ratnakumar <sridharr@activestate.com>
Summer Mousa <smousa@zenoss.com> Summer Mousa <smousa@zenoss.com>
Tarsis Azevedo <tarsis@corp.globo.com> Tarsis Azevedo <tarsis@corp.globo.com>
Tim Schindler <tim@catalyst-zero.com> Tim Schindler <tim@catalyst-zero.com>
Vincenzo Prignano <vincenzo.prignano@gmail.com>
Wiliam Souza <wiliamsouza83@gmail.com> Wiliam Souza <wiliamsouza83@gmail.com>
Ye Yin <eyniy@qq.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. All rights reserved.
Redistribution and use in source and binary forms, with or without 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://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) [![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 ## Example
@ -15,22 +16,49 @@ For more details, check the [remote API documentation](http://docs.docker.io/en/
package main package main
import ( import (
"fmt" "fmt"
"github.com/fsouza/go-dockerclient"
"github.com/fsouza/go-dockerclient"
) )
func main() { func main() {
endpoint := "unix:///var/run/docker.sock" endpoint := "unix:///var/run/docker.sock"
client, _ := docker.NewClient(endpoint) client, _ := docker.NewClient(endpoint)
imgs, _ := client.ListImages(docker.ListImagesOptions{All: false}) imgs, _ := client.ListImages(docker.ListImagesOptions{All: false})
for _, img := range imgs { for _, img := range imgs {
fmt.Println("ID: ", img.ID) fmt.Println("ID: ", img.ID)
fmt.Println("RepoTags: ", img.RepoTags) fmt.Println("RepoTags: ", img.RepoTags)
fmt.Println("Created: ", img.Created) fmt.Println("Created: ", img.Created)
fmt.Println("Size: ", img.Size) fmt.Println("Size: ", img.Size)
fmt.Println("VirtualSize: ", img.VirtualSize) fmt.Println("VirtualSize: ", img.VirtualSize)
fmt.Println("ParentId: ", img.ParentID) fmt.Println("ParentId: ", img.ParentID)
} }
}
```
## 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
} }
``` ```

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 // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // 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 is returned when the client cannot connect to the given endpoint.
ErrConnectionRefused = errors.New("cannot connect to Docker 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. // 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 // server endpoint, key and certificates . It will use the latest remote API version
// available in the server. // available in the server.
func NewTLSClient(endpoint string, cert, key, ca string) (*Client, error) { 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 { if err != nil {
return nil, err 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 // NewVersionedClient returns a Client instance ready for communication with
// the given server endpoint, using a specific remote API version. // the given server endpoint, using a specific remote API version.
func NewVersionedClient(endpoint string, apiVersionString string) (*Client, error) { func NewVersionedClient(endpoint string, apiVersionString string) (*Client, error) {
u, err := parseEndpoint(endpoint) u, err := parseEndpoint(endpoint, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -174,10 +174,15 @@ func NewVersionedClient(endpoint string, apiVersionString string) (*Client, erro
}, nil }, nil
} }
// NewVersionnedTLSClient returns a Client instance ready for TLS communications with the givens // NewVersionnedTLSClient has been DEPRECATED, please use NewVersionedTLSClient.
// server endpoint, key and certificates, using a specific remote API version.
func NewVersionnedTLSClient(endpoint string, cert, key, ca, apiVersionString string) (*Client, error) { 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 { if err != nil {
return nil, err return nil, err
} }
@ -247,7 +252,7 @@ func (c *Client) checkAPIVersion() error {
// See http://goo.gl/stJENm for more details. // See http://goo.gl/stJENm for more details.
func (c *Client) Ping() error { func (c *Client) Ping() error {
path := "/_ping" path := "/_ping"
body, status, err := c.do("GET", path, nil) body, status, err := c.do("GET", path, nil, false)
if err != nil { if err != nil {
return err return err
} }
@ -258,7 +263,7 @@ func (c *Client) Ping() error {
} }
func (c *Client) getServerAPIVersionString() (version string, err 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 { if err != nil {
return "", err return "", err
} }
@ -274,9 +279,9 @@ func (c *Client) getServerAPIVersionString() (version string, err error) {
return version, nil 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 var params io.Reader
if data != nil { if data != nil || forceJSON {
buf, err := json.Marshal(data) buf, err := json.Marshal(data)
if err != nil { if err != nil {
return nil, -1, err return nil, -1, err
@ -599,11 +604,14 @@ func (e *Error) Error() string {
return fmt.Sprintf("API error (%d): %s", e.Status, e.Message) 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) u, err := url.Parse(endpoint)
if err != nil { if err != nil {
return nil, ErrInvalidEndpoint return nil, ErrInvalidEndpoint
} }
if tls {
u.Scheme = "https"
}
if u.Scheme == "tcp" { if u.Scheme == "tcp" {
_, port, err := net.SplitHostPort(u.Host) _, port, err := net.SplitHostPort(u.Host)
if err != nil { 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 // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -93,7 +93,7 @@ func TestNewTLSVersionedClient(t *testing.T) {
keyPath := "testing/data/key.pem" keyPath := "testing/data/key.pem"
caPath := "testing/data/ca.pem" caPath := "testing/data/ca.pem"
endpoint := "https://localhost:4243" 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -113,7 +113,7 @@ func TestNewTLSVersionedClientInvalidCA(t *testing.T) {
keyPath := "testing/data/key.pem" keyPath := "testing/data/key.pem"
caPath := "testing/data/key.pem" caPath := "testing/data/key.pem"
endpoint := "https://localhost:4243" endpoint := "https://localhost:4243"
_, err := NewVersionnedTLSClient(endpoint, certPath, keyPath, caPath, "1.14") _, err := NewVersionedTLSClient(endpoint, certPath, keyPath, caPath, "1.14")
if err == nil { if err == nil {
t.Errorf("Expected invalid ca at %s", caPath) 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 { var tests = []struct {
endpoint string endpoint string
expected string expected string
}{ }{
{"tcp://localhost:2376", "https"}, {"tcp://localhost:2376", "https"},
{"tcp://localhost:2375", "http"}, {"tcp://localhost:2375", "https"},
{"tcp://localhost:4000", "http"}, {"tcp://localhost:4000", "https"},
{"http://localhost:4000", "https"},
} }
for _, tt := range tests { 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 // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -56,7 +56,7 @@ type APIContainers struct {
// See http://goo.gl/6Y4Gz7 for more details. // See http://goo.gl/6Y4Gz7 for more details.
func (c *Client) ListContainers(opts ListContainersOptions) ([]APIContainers, error) { func (c *Client) ListContainers(opts ListContainersOptions) ([]APIContainers, error) {
path := "/containers/json?" + queryString(opts) path := "/containers/json?" + queryString(opts)
body, _, err := c.do("GET", path, nil) body, _, err := c.do("GET", path, nil, false)
if err != nil { if err != nil {
return nil, err 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 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 // given host. Those are contained in HostConfig
type Config struct { type Config struct {
Hostname string `json:"Hostname,omitempty" yaml:"Hostname,omitempty"` Hostname string `json:"Hostname,omitempty" yaml:"Hostname,omitempty"`
@ -193,6 +193,26 @@ type Config struct {
WorkingDir string `json:"WorkingDir,omitempty" yaml:"WorkingDir,omitempty"` WorkingDir string `json:"WorkingDir,omitempty" yaml:"WorkingDir,omitempty"`
Entrypoint []string `json:"Entrypoint,omitempty" yaml:"Entrypoint,omitempty"` Entrypoint []string `json:"Entrypoint,omitempty" yaml:"Entrypoint,omitempty"`
NetworkDisabled bool `json:"NetworkDisabled,omitempty" yaml:"NetworkDisabled,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, // 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"` State State `json:"State,omitempty" yaml:"State,omitempty"`
Image string `json:"Image,omitempty" yaml:"Image,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"` NetworkSettings *NetworkSettings `json:"NetworkSettings,omitempty" yaml:"NetworkSettings,omitempty"`
SysInitPath string `json:"SysInitPath,omitempty" yaml:"SysInitPath,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"` Volumes map[string]string `json:"Volumes,omitempty" yaml:"Volumes,omitempty"`
VolumesRW map[string]bool `json:"VolumesRW,omitempty" yaml:"VolumesRW,omitempty"` VolumesRW map[string]bool `json:"VolumesRW,omitempty" yaml:"VolumesRW,omitempty"`
HostConfig *HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,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. // InspectContainer returns information about a container by its ID.
@ -228,7 +272,7 @@ type Container struct {
// See http://goo.gl/CxVuJ5 for more details. // See http://goo.gl/CxVuJ5 for more details.
func (c *Client) InspectContainer(id string) (*Container, error) { func (c *Client) InspectContainer(id string) (*Container, error) {
path := "/containers/" + id + "/json" 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 { if status == http.StatusNotFound {
return nil, &NoSuchContainer{ID: id} 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. // See http://goo.gl/QkW9sH for more details.
func (c *Client) ContainerChanges(id string) ([]Change, error) { func (c *Client) ContainerChanges(id string) ([]Change, error) {
path := "/containers/" + id + "/changes" 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 { if status == http.StatusNotFound {
return nil, &NoSuchContainer{ID: id} return nil, &NoSuchContainer{ID: id}
} }
@ -284,7 +328,7 @@ func (c *Client) CreateContainer(opts CreateContainerOptions) (*Container, error
}{ }{
opts.Config, opts.Config,
opts.HostConfig, opts.HostConfig,
}) }, false)
if status == http.StatusNotFound { if status == http.StatusNotFound {
return nil, ErrNoSuchImage return nil, ErrNoSuchImage
@ -341,6 +385,14 @@ func NeverRestart() RestartPolicy {
return RestartPolicy{Name: "no"} 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 // HostConfig contains the container options related to starting a container on
// a given host // a given host
type HostConfig struct { type HostConfig struct {
@ -359,20 +411,22 @@ type HostConfig struct {
VolumesFrom []string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"` VolumesFrom []string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"`
NetworkMode string `json:"NetworkMode,omitempty" yaml:"NetworkMode,omitempty"` NetworkMode string `json:"NetworkMode,omitempty" yaml:"NetworkMode,omitempty"`
IpcMode string `json:"IpcMode,omitempty" yaml:"IpcMode,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"` 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. // StartContainer starts a container, returning an error in case of failure.
// //
// See http://goo.gl/iM5GYs for more details. // See http://goo.gl/iM5GYs for more details.
func (c *Client) StartContainer(id string, hostConfig *HostConfig) error { func (c *Client) StartContainer(id string, hostConfig *HostConfig) error {
if hostConfig == nil {
hostConfig = &HostConfig{}
}
path := "/containers/" + id + "/start" path := "/containers/" + id + "/start"
_, status, err := c.do("POST", path, hostConfig) _, status, err := c.do("POST", path, hostConfig, true)
if status == http.StatusNotFound { if status == http.StatusNotFound {
return &NoSuchContainer{ID: id} return &NoSuchContainer{ID: id, Err: err}
} }
if status == http.StatusNotModified { if status == http.StatusNotModified {
return &ContainerAlreadyRunning{ID: id} 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. // See http://goo.gl/EbcpXt for more details.
func (c *Client) StopContainer(id string, timeout uint) error { func (c *Client) StopContainer(id string, timeout uint) error {
path := fmt.Sprintf("/containers/%s/stop?t=%d", id, timeout) 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 { if status == http.StatusNotFound {
return &NoSuchContainer{ID: id} 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. // See http://goo.gl/VOzR2n for more details.
func (c *Client) RestartContainer(id string, timeout uint) error { func (c *Client) RestartContainer(id string, timeout uint) error {
path := fmt.Sprintf("/containers/%s/restart?t=%d", id, timeout) 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 { if status == http.StatusNotFound {
return &NoSuchContainer{ID: id} 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. // See http://goo.gl/AM5t42 for more details.
func (c *Client) PauseContainer(id string) error { func (c *Client) PauseContainer(id string) error {
path := fmt.Sprintf("/containers/%s/pause", id) 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 { if status == http.StatusNotFound {
return &NoSuchContainer{ID: id} return &NoSuchContainer{ID: id}
} }
@ -438,7 +492,7 @@ func (c *Client) PauseContainer(id string) error {
// See http://goo.gl/eBrNSL for more details. // See http://goo.gl/eBrNSL for more details.
func (c *Client) UnpauseContainer(id string) error { func (c *Client) UnpauseContainer(id string) error {
path := fmt.Sprintf("/containers/%s/unpause", id) 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 { if status == http.StatusNotFound {
return &NoSuchContainer{ID: id} 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) args = fmt.Sprintf("?ps_args=%s", psArgs)
} }
path := fmt.Sprintf("/containers/%s/top%s", id, args) 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 { if status == http.StatusNotFound {
return result, &NoSuchContainer{ID: id} return result, &NoSuchContainer{ID: id}
} }
@ -499,7 +553,7 @@ type KillContainerOptions struct {
// See http://goo.gl/TFkECx for more details. // See http://goo.gl/TFkECx for more details.
func (c *Client) KillContainer(opts KillContainerOptions) error { func (c *Client) KillContainer(opts KillContainerOptions) error {
path := "/containers/" + opts.ID + "/kill" + "?" + queryString(opts) 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 { if status == http.StatusNotFound {
return &NoSuchContainer{ID: opts.ID} return &NoSuchContainer{ID: opts.ID}
} }
@ -530,7 +584,7 @@ type RemoveContainerOptions struct {
// See http://goo.gl/ZB83ji for more details. // See http://goo.gl/ZB83ji for more details.
func (c *Client) RemoveContainer(opts RemoveContainerOptions) error { func (c *Client) RemoveContainer(opts RemoveContainerOptions) error {
path := "/containers/" + opts.ID + "?" + queryString(opts) 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 { if status == http.StatusNotFound {
return &NoSuchContainer{ID: opts.ID} return &NoSuchContainer{ID: opts.ID}
} }
@ -559,7 +613,7 @@ func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error {
return &NoSuchContainer{ID: opts.Container} return &NoSuchContainer{ID: opts.Container}
} }
url := fmt.Sprintf("/containers/%s/copy", 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 { if status == http.StatusNotFound {
return &NoSuchContainer{ID: opts.Container} return &NoSuchContainer{ID: opts.Container}
} }
@ -575,7 +629,7 @@ func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error {
// //
// See http://goo.gl/J88DHU for more details. // See http://goo.gl/J88DHU for more details.
func (c *Client) WaitContainer(id string) (int, error) { 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 { if status == http.StatusNotFound {
return 0, &NoSuchContainer{ID: id} return 0, &NoSuchContainer{ID: id}
} }
@ -607,7 +661,7 @@ type CommitContainerOptions struct {
// See http://goo.gl/Jn8pe8 for more details. // See http://goo.gl/Jn8pe8 for more details.
func (c *Client) CommitContainer(opts CommitContainerOptions) (*Image, error) { func (c *Client) CommitContainer(opts CommitContainerOptions) (*Image, error) {
path := "/commit?" + queryString(opts) 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 { if status == http.StatusNotFound {
return nil, &NoSuchContainer{ID: opts.Container} 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 := make(url.Values)
params.Set("h", strconv.Itoa(height)) params.Set("h", strconv.Itoa(height))
params.Set("w", strconv.Itoa(width)) 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 return err
} }
@ -733,10 +787,14 @@ func (c *Client) ExportContainer(opts ExportContainerOptions) error {
// NoSuchContainer is the error returned when a given container does not exist. // NoSuchContainer is the error returned when a given container does not exist.
type NoSuchContainer struct { type NoSuchContainer struct {
ID string ID string
Err error
} }
func (err *NoSuchContainer) Error() string { func (err *NoSuchContainer) Error() string {
if err.Err != nil {
return err.Err.Error()
}
return "No such container: " + err.ID 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 // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -7,6 +7,7 @@ package docker
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors"
"io/ioutil" "io/ioutil"
"net" "net"
"net/http" "net/http"
@ -154,6 +155,7 @@ func TestListContainersFailure(t *testing.T) {
func TestInspectContainer(t *testing.T) { func TestInspectContainer(t *testing.T) {
jsonContainer := `{ jsonContainer := `{
"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2", "Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2",
"AppArmorProfile": "Profile",
"Created": "2013-05-07T14:51:42.087658+02:00", "Created": "2013-05-07T14:51:42.087658+02:00",
"Path": "date", "Path": "date",
"Args": [], "Args": [],
@ -175,7 +177,10 @@ func TestInspectContainer(t *testing.T) {
], ],
"Image": "base", "Image": "base",
"Volumes": {}, "Volumes": {},
"VolumesFrom": "" "VolumesFrom": "",
"SecurityOpt": [
"label:user:USER"
]
}, },
"State": { "State": {
"Running": false, "Running": false,
@ -184,6 +189,21 @@ func TestInspectContainer(t *testing.T) {
"StartedAt": "2013-05-07T14:51:42.087658+02:00", "StartedAt": "2013-05-07T14:51:42.087658+02:00",
"Ghost": false "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", "Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
"NetworkSettings": { "NetworkSettings": {
"IpAddress": "", "IpAddress": "",
@ -511,12 +531,17 @@ func TestStartContainerNilHostConfig(t *testing.T) {
if contentType := req.Header.Get("Content-Type"); contentType != expectedContentType { 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) 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) { func TestStartContainerNotFound(t *testing.T) {
client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound}) client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound})
err := client.StartContainer("a2344", &HostConfig{}) err := client.StartContainer("a2344", &HostConfig{})
expected := &NoSuchContainer{ID: "a2344"} expected := &NoSuchContainer{ID: "a2344", Err: err.(*NoSuchContainer).Err}
if !reflect.DeepEqual(err, expected) { if !reflect.DeepEqual(err, expected) {
t.Errorf("StartContainer: Wrong error returned. Want %#v. Got %#v.", expected, err) 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) { func TestExportContainer(t *testing.T) {
content := "exported container tar content" content := "exported container tar content"
out := stdoutMock{bytes.NewBufferString(content)} out := stdoutMock{bytes.NewBufferString(content)}
@ -1311,7 +1344,7 @@ func TestExportContainerViaUnixSocket(t *testing.T) {
tempSocket := tempfile("export_socket") tempSocket := tempfile("export_socket")
defer os.Remove(tempSocket) defer os.Remove(tempSocket)
endpoint := "unix://" + tempSocket endpoint := "unix://" + tempSocket
u, _ := parseEndpoint(endpoint) u, _ := parseEndpoint(endpoint, false)
client := Client{ client := Client{
HTTPClient: http.DefaultClient, HTTPClient: http.DefaultClient,
endpoint: endpoint, 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()) 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 // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -182,8 +182,8 @@ func (eventState *eventMonitoringState) monitorEvents(c *Client) {
eventState.terminate() eventState.terminate()
return return
} }
eventState.updateLastSeen(ev)
go eventState.sendEvent(ev) go eventState.sendEvent(ev)
go eventState.updateLastSeen(ev)
case err = <-eventState.errC: case err = <-eventState.errC:
if err == ErrNoListeners { if err == ErrNoListeners {
eventState.terminate() eventState.terminate()
@ -227,7 +227,7 @@ func (eventState *eventMonitoringState) sendEvent(event *APIEvents) {
eventState.Add(1) eventState.Add(1)
defer eventState.Done() defer eventState.Done()
if eventState.isEnabled() { if eventState.isEnabled() {
if eventState.noListeners() { if len(eventState.listeners) == 0 {
eventState.errC <- ErrNoListeners eventState.errC <- ErrNoListeners
return 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 // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -90,7 +90,7 @@ type ExecInspect struct {
// See http://goo.gl/8izrzI for more details // See http://goo.gl/8izrzI for more details
func (c *Client) CreateExec(opts CreateExecOptions) (*Exec, error) { func (c *Client) CreateExec(opts CreateExecOptions) (*Exec, error) {
path := fmt.Sprintf("/containers/%s/exec", opts.Container) 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 { if status == http.StatusNotFound {
return nil, &NoSuchContainer{ID: opts.Container} 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) path := fmt.Sprintf("/exec/%s/start", id)
if opts.Detach { if opts.Detach {
_, status, err := c.do("POST", path, opts) _, status, err := c.do("POST", path, opts, false)
if status == http.StatusNotFound { if status == http.StatusNotFound {
return &NoSuchExec{ID: id} return &NoSuchExec{ID: id}
} }
@ -143,7 +143,7 @@ func (c *Client) ResizeExecTTY(id string, height, width int) error {
params.Set("w", strconv.Itoa(width)) params.Set("w", strconv.Itoa(width))
path := fmt.Sprintf("/exec/%s/resize?%s", id, params.Encode()) 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 return err
} }
@ -152,7 +152,7 @@ func (c *Client) ResizeExecTTY(id string, height, width int) error {
// See http://goo.gl/ypQULN for more details // See http://goo.gl/ypQULN for more details
func (c *Client) InspectExec(id string) (*ExecInspect, error) { func (c *Client) InspectExec(id string) (*ExecInspect, error) {
path := fmt.Sprintf("/exec/%s/json", id) 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 { if status == http.StatusNotFound {
return nil, &NoSuchExec{ID: id} 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 // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // 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 // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -99,7 +99,7 @@ var (
// See http://goo.gl/2rOLFF for more details. // See http://goo.gl/2rOLFF for more details.
func (c *Client) ListImages(opts ListImagesOptions) ([]APIImages, error) { func (c *Client) ListImages(opts ListImagesOptions) ([]APIImages, error) {
path := "/images/json?" + queryString(opts) path := "/images/json?" + queryString(opts)
body, _, err := c.do("GET", path, nil) body, _, err := c.do("GET", path, nil, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -115,7 +115,7 @@ func (c *Client) ListImages(opts ListImagesOptions) ([]APIImages, error) {
// //
// See http://goo.gl/2oJmNs for more details. // See http://goo.gl/2oJmNs for more details.
func (c *Client) ImageHistory(name string) ([]ImageHistory, error) { 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 { if status == http.StatusNotFound {
return nil, ErrNoSuchImage return nil, ErrNoSuchImage
} }
@ -134,7 +134,29 @@ func (c *Client) ImageHistory(name string) ([]ImageHistory, error) {
// //
// See http://goo.gl/znj0wM for more details. // See http://goo.gl/znj0wM for more details.
func (c *Client) RemoveImage(name string) error { 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 { if status == http.StatusNotFound {
return ErrNoSuchImage return ErrNoSuchImage
} }
@ -145,7 +167,7 @@ func (c *Client) RemoveImage(name string) error {
// //
// See http://goo.gl/Q112NY for more details. // See http://goo.gl/Q112NY for more details.
func (c *Client) InspectImage(name string) (*Image, error) { 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 { if status == http.StatusNotFound {
return nil, ErrNoSuchImage return nil, ErrNoSuchImage
} }
@ -156,7 +178,7 @@ func (c *Client) InspectImage(name string) (*Image, error) {
var image Image var image Image
// if the caller elected to skip checking the server's version, assume it's the latest // 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) err = json.Unmarshal(body, &image)
if err != nil { if err != nil {
return nil, err return nil, err
@ -201,21 +223,6 @@ type PushImageOptions struct {
RawJSONStream bool `qs:"-"` 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. // PushImage pushes an image to a remote registry, logging progress to w.
// //
// An empty instance of AuthConfiguration may be used for unauthenticated // An empty instance of AuthConfiguration may be used for unauthenticated
@ -245,7 +252,7 @@ type PullImageOptions struct {
RawJSONStream bool `qs:"-"` 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. // See http://goo.gl/ACyYNS for more details.
func (c *Client) PullImage(opts PullImageOptions, auth AuthConfiguration) error { func (c *Client) PullImage(opts PullImageOptions, auth AuthConfiguration) error {
@ -333,6 +340,7 @@ func (c *Client) ImportImage(opts ImportImageOptions) error {
// http://goo.gl/tlPXPu. // http://goo.gl/tlPXPu.
type BuildImageOptions struct { type BuildImageOptions struct {
Name string `qs:"t"` Name string `qs:"t"`
Dockerfile string `qs:"dockerfile"`
NoCache bool `qs:"nocache"` NoCache bool `qs:"nocache"`
SuppressOutput bool `qs:"q"` SuppressOutput bool `qs:"q"`
RmTmpContainer bool `qs:"rm"` RmTmpContainer bool `qs:"rm"`
@ -395,7 +403,8 @@ func (c *Client) TagImage(name string, opts TagImageOptions) error {
return ErrNoSuchImage return ErrNoSuchImage
} }
_, status, err := c.do("POST", fmt.Sprintf("/images/"+name+"/tag?%s", _, status, err := c.do("POST", fmt.Sprintf("/images/"+name+"/tag?%s",
queryString(&opts)), nil) queryString(&opts)), nil, false)
if status == http.StatusNotFound { if status == http.StatusNotFound {
return ErrNoSuchImage return ErrNoSuchImage
} }
@ -445,7 +454,7 @@ type APIImageSearch struct {
// //
// See http://goo.gl/xI5lLZ for more details. // See http://goo.gl/xI5lLZ for more details.
func (c *Client) SearchImages(term string) ([]APIImageSearch, error) { 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 { if err != nil {
return nil, err 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 // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -19,7 +19,7 @@ import (
func newTestClient(rt *FakeRoundTripper) Client { func newTestClient(rt *FakeRoundTripper) Client {
endpoint := "http://localhost:4243" endpoint := "http://localhost:4243"
u, _ := parseEndpoint("http://localhost:4243") u, _ := parseEndpoint("http://localhost:4243", false)
client := Client{ client := Client{
HTTPClient: &http.Client{Transport: rt}, HTTPClient: &http.Client{Transport: rt},
endpoint: endpoint, 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) { func TestInspectImage(t *testing.T) {
body := `{ body := `{
"id":"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", "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 // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -13,7 +13,7 @@ import (
// //
// See http://goo.gl/BOZrF5 for more details. // See http://goo.gl/BOZrF5 for more details.
func (c *Client) Version() (*Env, error) { func (c *Client) Version() (*Env, error) {
body, _, err := c.do("GET", "/version", nil) body, _, err := c.do("GET", "/version", nil, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -28,7 +28,7 @@ func (c *Client) Version() (*Env, error) {
// //
// See http://goo.gl/wmqZsW for more details. // See http://goo.gl/wmqZsW for more details.
func (c *Client) Info() (*Env, error) { func (c *Client) Info() (*Env, error) {
body, _, err := c.do("GET", "/info", nil) body, _, err := c.do("GET", "/info", nil, false)
if err != nil { if err != nil {
return nil, err 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 // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // 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 // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -35,6 +35,7 @@ import (
type DockerServer struct { type DockerServer struct {
containers []*docker.Container containers []*docker.Container
execs []*docker.ExecInspect execs []*docker.ExecInspect
execMut sync.RWMutex
cMut sync.RWMutex cMut sync.RWMutex
images []docker.Image images []docker.Image
iMut sync.RWMutex iMut sync.RWMutex
@ -43,6 +44,8 @@ type DockerServer struct {
mux *mux.Router mux *mux.Router
hook func(*http.Request) hook func(*http.Request)
failures map[string]string failures map[string]string
multiFailures []map[string]string
execCallbacks map[string]func()
customHandlers map[string]http.Handler customHandlers map[string]http.Handler
handlerMutex sync.RWMutex handlerMutex sync.RWMutex
cChan chan<- *docker.Container cChan chan<- *docker.Container
@ -69,6 +72,7 @@ func NewServer(bind string, containerChan chan<- *docker.Container, hook func(*h
imgIDs: make(map[string]string), imgIDs: make(map[string]string),
hook: hook, hook: hook,
failures: make(map[string]string), failures: make(map[string]string),
execCallbacks: make(map[string]func()),
customHandlers: make(map[string]http.Handler), customHandlers: make(map[string]http.Handler),
cChan: containerChan, 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/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.listContainers))
s.mux.Path("/containers/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.createContainer)) 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:.*}/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:.*}/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:.*}/start").Methods("POST").HandlerFunc(s.handlerWrapper(s.startContainer))
s.mux.Path("/containers/{id:.*}/kill").Methods("POST").HandlerFunc(s.handlerWrapper(s.stopContainer)) 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)) 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 // PrepareFailure adds a new expected failure based on a URL regexp it receives
// an id for the failure. // an id for the failure.
func (s *DockerServer) PrepareFailure(id string, urlRegexp string) { func (s *DockerServer) PrepareFailure(id string, urlRegexp string) {
s.failures[id] = urlRegexp 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. // ResetFailure removes an expected failure identified by the given id.
func (s *DockerServer) ResetFailure(id string) { func (s *DockerServer) ResetFailure(id string) {
delete(s.failures, id) 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. // CustomHandler registers a custom handler for a specific path.
// //
// For example: // For example:
@ -170,9 +217,11 @@ func (s *DockerServer) URL() string {
func (s *DockerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (s *DockerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.handlerMutex.RLock() s.handlerMutex.RLock()
defer s.handlerMutex.RUnlock() defer s.handlerMutex.RUnlock()
if handler, ok := s.customHandlers[r.URL.Path]; ok { for re, handler := range s.customHandlers {
handler.ServeHTTP(w, r) if m, _ := regexp.MatchString(re, r.URL.Path); m {
return handler.ServeHTTP(w, r)
return
}
} }
s.mux.ServeHTTP(w, r) s.mux.ServeHTTP(w, r)
if s.hook != nil { if s.hook != nil {
@ -200,6 +249,19 @@ func (s *DockerServer) handlerWrapper(f func(http.ResponseWriter, *http.Request)
http.Error(w, errorID, http.StatusBadRequest) http.Error(w, errorID, http.StatusBadRequest)
return 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) f(w, r)
} }
} }
@ -336,6 +398,23 @@ func (s *DockerServer) generateID() string {
return fmt.Sprintf("%x", buf) 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) { func (s *DockerServer) inspectContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"] id := mux.Vars(r)["id"]
container, _, err := s.findContainer(id) container, _, err := s.findContainer(id)
@ -524,9 +603,13 @@ func (s *DockerServer) commitContainer(w http.ResponseWriter, r *http.Request) {
Config: config, Config: config,
} }
repository := r.URL.Query().Get("repo") repository := r.URL.Query().Get("repo")
tag := r.URL.Query().Get("tag")
s.iMut.Lock() s.iMut.Lock()
s.images = append(s.images, image) s.images = append(s.images, image)
if repository != "" { if repository != "" {
if tag != "" {
repository += ":" + tag
}
s.imgIDs[repository] = image.ID s.imgIDs[repository] = image.ID
} }
s.iMut.Unlock() 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) { 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{ image := docker.Image{
ID: s.generateID(), ID: s.generateID(),
} }
s.iMut.Lock() s.iMut.Lock()
s.images = append(s.images, image) s.images = append(s.images, image)
if repository != "" { if fromImageName != "" {
s.imgIDs[repository] = image.ID if tag != "" {
fromImageName = fmt.Sprintf("%s:%s", fromImageName, tag)
}
s.imgIDs[fromImageName] = image.ID
} }
s.iMut.Unlock() s.iMut.Unlock()
} }
func (s *DockerServer) pushImage(w http.ResponseWriter, r *http.Request) { func (s *DockerServer) pushImage(w http.ResponseWriter, r *http.Request) {
name := mux.Vars(r)["name"] name := mux.Vars(r)["name"]
tag := r.URL.Query().Get("tag")
if tag != "" {
name += ":" + tag
}
s.iMut.RLock() s.iMut.RLock()
if _, ok := s.imgIDs[name]; !ok { if _, ok := s.imgIDs[name]; !ok {
s.iMut.RUnlock() s.iMut.RUnlock()
@ -619,6 +710,10 @@ func (s *DockerServer) tagImage(w http.ResponseWriter, r *http.Request) {
s.iMut.Lock() s.iMut.Lock()
defer s.iMut.Unlock() defer s.iMut.Unlock()
newRepo := r.URL.Query().Get("repo") newRepo := r.URL.Query().Get("repo")
newTag := r.URL.Query().Get("tag")
if newTag != "" {
newRepo += ":" + newTag
}
s.imgIDs[newRepo] = s.imgIDs[name] s.imgIDs[newRepo] = s.imgIDs[name]
w.WriteHeader(http.StatusCreated) 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) { func (s *DockerServer) getImage(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/tar") w.Header().Set("Content-Type", "application/tar")
} }
func (s *DockerServer) createExecContainer(w http.ResponseWriter, r *http.Request) { 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 return
} }
exec := docker.ExecInspect{ exec := docker.ExecInspect{
ID: "id-exec-created-by-test", ID: s.generateID(),
Container: *container, Container: *container,
} }
var params docker.CreateExecOptions var params docker.CreateExecOptions
@ -748,44 +842,63 @@ func (s *DockerServer) createExecContainer(w http.ResponseWriter, r *http.Reques
exec.ProcessConfig.Arguments = params.Cmd[1:] exec.ProcessConfig.Arguments = params.Cmd[1:]
} }
} }
s.execMut.Lock()
s.execs = append(s.execs, &exec) s.execs = append(s.execs, &exec)
s.execMut.Unlock()
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"Id": exec.ID}) json.NewEncoder(w).Encode(map[string]string{"Id": exec.ID})
} }
func (s *DockerServer) startExecContainer(w http.ResponseWriter, r *http.Request) { func (s *DockerServer) startExecContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"] id := mux.Vars(r)["id"]
for _, exec := range s.execs { if exec, err := s.getExec(id); err == nil {
if exec.ID == id { s.execMut.Lock()
w.WriteHeader(http.StatusOK) exec.Running = true
return 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) w.WriteHeader(http.StatusNotFound)
} }
func (s *DockerServer) resizeExecContainer(w http.ResponseWriter, r *http.Request) { func (s *DockerServer) resizeExecContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"] id := mux.Vars(r)["id"]
for _, exec := range s.execs { if _, err := s.getExec(id); err == nil {
if exec.ID == id { w.WriteHeader(http.StatusOK)
w.WriteHeader(http.StatusOK) return
return
}
} }
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
} }
func (s *DockerServer) inspectExecContainer(w http.ResponseWriter, r *http.Request) { func (s *DockerServer) inspectExecContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"] id := mux.Vars(r)["id"]
for _, exec := range s.execs { if exec, err := s.getExec(id); err == nil {
if exec.ID == id { w.WriteHeader(http.StatusOK)
w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json")
w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(exec)
json.NewEncoder(w).Encode(exec) return
return
}
} }
w.WriteHeader(http.StatusNotFound) 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 // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // 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) { func TestCustomHandler(t *testing.T) {
var called bool var called bool
server, _ := NewServer("127.0.0.1:0", nil, nil) 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) { func TestListContainers(t *testing.T) {
server := DockerServer{} server := DockerServer{}
addContainers(&server, 2) 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) { func TestCommitContainer(t *testing.T) {
server := DockerServer{} server := DockerServer{}
addContainers(&server, 2) 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) { func TestCommitContainerInvalidRun(t *testing.T) {
server := DockerServer{} server := DockerServer{}
addContainers(&server, 1) 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) { func TestPushImage(t *testing.T) {
server := DockerServer{imgIDs: map[string]string{"tsuru/python": "a123"}} server := DockerServer{imgIDs: map[string]string{"tsuru/python": "a123"}}
server.buildMuxer() 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) { func TestPushImageNotFound(t *testing.T) {
server := DockerServer{} server := DockerServer{}
server.buildMuxer() 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) { func TestTagImageNotFound(t *testing.T) {
server := DockerServer{} server := DockerServer{}
server.buildMuxer() 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) { func TestRemoveFailure(t *testing.T) {
server := DockerServer{failures: make(map[string]string)} server := DockerServer{failures: make(map[string]string)}
server.buildMuxer() 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) { func TestMutateContainer(t *testing.T) {
server := DockerServer{failures: make(map[string]string)} server := DockerServer{failures: make(map[string]string)}
server.buildMuxer() 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) 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
}