Really upgrade fsouza/go-dockerclient pkg to latest revision, so that we can have

Error and OOMKilled from docker ContainerStatus.
This commit is contained in:
Dawn Chen 2014-12-18 11:02:09 -08:00
parent b489757b5a
commit 95e3efdad8
30 changed files with 1041 additions and 155 deletions

4
Godeps/Godeps.json generated
View File

@ -104,8 +104,8 @@
},
{
"ImportPath": "github.com/fsouza/go-dockerclient",
"Comment": "0.2.1-267-g15d2c6e",
"Rev": "15d2c6e3eb670c545d0af0604d7f9aff3871af04"
"Comment": "0.2.1-334-g9c377ff",
"Rev": "9c377ffd9aed48a012adf1c3fd517fe98394120b"
},
{
"ImportPath": "github.com/ghodss/yaml",

View File

@ -1,6 +1,5 @@
language: go
go:
- 1.1.2
- 1.2.2
- 1.3.1
- tip
@ -11,3 +10,4 @@ install:
- go get -d ./...
script:
- go test ./...
- ./testing/bin/fmtpolice

View File

@ -12,6 +12,7 @@ cheneydeng <cheneydeng@qq.com>
CMGS <ilskdw@gmail.com>
Daniel, Dao Quang Minh <dqminh89@gmail.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>
@ -19,6 +20,7 @@ Flavia Missi <flaviamissi@gmail.com>
Francisco Souza <f@souza.cc>
Jari Kolehmainen <jari.kolehmainen@digia.com>
Jason Wilder <jwilder@litl.com>
Jawher Moussa <jawher.moussa@gmail.com>
Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
Jeff Mitchell <jeffrey.mitchell@gmail.com>
Jeffrey Hulten <jhulten@gmail.com>
@ -26,11 +28,14 @@ Johan Euphrosine <proppy@google.com>
Karan Misra <kidoman@gmail.com>
Kim, Hirokuni <hirokuni.kim@kvh.co.jp>
Lucas Clemente <lucas@clemente.io>
Máximo Cuadros Ortiz <mcuadros@gmail.com>
Mike Dillon <mike.dillon@synctree.com>
Omeid Matten <public@omeid.me>
Paul Morie <pmorie@gmail.com>
Peter Jihoon Kim <raingrove@gmail.com>
Philippe Lafoucrière <philippe.lafoucriere@tech-angels.com>
Rafe Colton <r.colton@modcloth.com>
Rafe Colton <rafael.colton@gmail.com>
Rob Miller <rob@kalistra.com>
Robert Williamson <williamson.robert@gmail.com>
Salvador Gironès <salvadorgirones@gmail.com>
Simon Eskildsen <sirup@sirupsen.com>
@ -42,3 +47,4 @@ Summer Mousa <smousa@zenoss.com>
Tarsis Azevedo <tarsis@corp.globo.com>
Tim Schindler <tim@catalyst-zero.com>
Wiliam Souza <wiliamsouza83@gmail.com>
Ye Yin <eyniy@qq.com>

View File

@ -29,7 +29,7 @@ func main() {
fmt.Println("Created: ", img.Created)
fmt.Println("Size: ", img.Size)
fmt.Println("VirtualSize: ", img.VirtualSize)
fmt.Println("ParentId: ", img.ParentId)
fmt.Println("ParentId: ", img.ParentID)
}
}
```

View File

@ -0,0 +1,144 @@
package docker
import (
"bytes"
"io"
"io/ioutil"
"net/http"
"os"
"reflect"
"testing"
"github.com/docker/docker/pkg/archive"
)
func TestBuildImageMultipleContextsError(t *testing.T) {
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
client := newTestClient(fakeRT)
var buf bytes.Buffer
opts := BuildImageOptions{
Name: "testImage",
NoCache: true,
SuppressOutput: true,
RmTmpContainer: true,
ForceRmTmpContainer: true,
InputStream: &buf,
OutputStream: &buf,
ContextDir: "testing/data",
}
err := client.BuildImage(opts)
if err != ErrMultipleContexts {
t.Errorf("BuildImage: providing both InputStream and ContextDir should produce an error")
}
}
func TestBuildImageContextDirDockerignoreParsing(t *testing.T) {
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
client := newTestClient(fakeRT)
var buf bytes.Buffer
opts := BuildImageOptions{
Name: "testImage",
NoCache: true,
SuppressOutput: true,
RmTmpContainer: true,
ForceRmTmpContainer: true,
OutputStream: &buf,
ContextDir: "testing/data",
}
err := client.BuildImage(opts)
if err != nil {
t.Fatal(err)
}
reqBody := fakeRT.requests[0].Body
tmpdir, err := unpackBodyTarball(reqBody)
if err != nil {
t.Fatal(err)
}
defer func() {
if err := os.RemoveAll(tmpdir); err != nil {
t.Fatal(err)
}
}()
files, err := ioutil.ReadDir(tmpdir)
if err != nil {
t.Fatal(err)
}
foundFiles := []string{}
for _, file := range files {
foundFiles = append(foundFiles, file.Name())
}
expectedFiles := []string{
".dockerignore",
"Dockerfile",
"barfile",
"ca.pem",
"cert.pem",
"key.pem",
"server.pem",
"serverkey.pem",
"symlink",
}
if !reflect.DeepEqual(expectedFiles, foundFiles) {
t.Errorf(
"BuildImage: incorrect files sent in tarball to docker server\nexpected %+v, found %+v",
expectedFiles, foundFiles,
)
}
}
func TestBuildImageSendXRegistryConfig(t *testing.T) {
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
client := newTestClient(fakeRT)
var buf bytes.Buffer
opts := BuildImageOptions{
Name: "testImage",
NoCache: true,
SuppressOutput: true,
RmTmpContainer: true,
ForceRmTmpContainer: true,
OutputStream: &buf,
ContextDir: "testing/data",
AuthConfigs: AuthConfigurations{
Configs: map[string]AuthConfiguration{
"quay.io": {
Username: "foo",
Password: "bar",
Email: "baz",
ServerAddress: "quay.io",
},
},
},
}
encodedConfig := "eyJjb25maWdzIjp7InF1YXkuaW8iOnsidXNlcm5hbWUiOiJmb28iLCJwYXNzd29yZCI6ImJhciIsImVtYWlsIjoiYmF6Iiwic2VydmVyYWRkcmVzcyI6InF1YXkuaW8ifX19Cg=="
if err := client.BuildImage(opts); err != nil {
t.Fatal(err)
}
xRegistryConfig := fakeRT.requests[0].Header["X-Registry-Config"][0]
if xRegistryConfig != encodedConfig {
t.Errorf(
"BuildImage: X-Registry-Config not set currectly: expected %q, got %q",
encodedConfig,
xRegistryConfig,
)
}
}
func unpackBodyTarball(req io.ReadCloser) (tmpdir string, err error) {
tmpdir, err = ioutil.TempDir("", "go-dockerclient-test")
if err != nil {
return
}
err = archive.Untar(req, tmpdir, &archive.TarOptions{
Compression: archive.Uncompressed,
NoLchown: true,
})
return
}

View File

@ -6,11 +6,18 @@ package docker
import "fmt"
// ChangeType is a type for constants indicating the type of change
// in a container
type ChangeType int
const (
// ChangeModify is the ChangeType for container modifications
ChangeModify ChangeType = iota
// ChangeAdd is the ChangeType for additions to a container
ChangeAdd
// ChangeDelete is the ChangeType for deletions from a container
ChangeDelete
)

View File

@ -9,6 +9,8 @@ package docker
import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
@ -32,22 +34,22 @@ var (
// ErrConnectionRefused is returned when the client cannot connect to the given endpoint.
ErrConnectionRefused = errors.New("cannot connect to Docker endpoint")
apiVersion_1_12, _ = NewApiVersion("1.12")
apiVersion1_12, _ = NewAPIVersion("1.12")
)
// ApiVersion is an internal representation of a version of the Remote API.
type ApiVersion []int
// APIVersion is an internal representation of a version of the Remote API.
type APIVersion []int
// NewApiVersion returns an instance of ApiVersion for the given string.
// NewAPIVersion returns an instance of APIVersion for the given string.
//
// The given string must be in the form <major>.<minor>.<patch>, where <major>,
// <minor> and <patch> are integer numbers.
func NewApiVersion(input string) (ApiVersion, error) {
func NewAPIVersion(input string) (APIVersion, error) {
if !strings.Contains(input, ".") {
return nil, fmt.Errorf("Unable to parse version %q", input)
}
arr := strings.Split(input, ".")
ret := make(ApiVersion, len(arr))
ret := make(APIVersion, len(arr))
var err error
for i, val := range arr {
ret[i], err = strconv.Atoi(val)
@ -58,7 +60,7 @@ func NewApiVersion(input string) (ApiVersion, error) {
return ret, nil
}
func (version ApiVersion) String() string {
func (version APIVersion) String() string {
var str string
for i, val := range version {
str += strconv.Itoa(val)
@ -69,23 +71,27 @@ func (version ApiVersion) String() string {
return str
}
func (version ApiVersion) LessThan(other ApiVersion) bool {
// LessThan is a function for comparing APIVersion structs
func (version APIVersion) LessThan(other APIVersion) bool {
return version.compare(other) < 0
}
func (version ApiVersion) LessThanOrEqualTo(other ApiVersion) bool {
// LessThanOrEqualTo is a function for comparing APIVersion structs
func (version APIVersion) LessThanOrEqualTo(other APIVersion) bool {
return version.compare(other) <= 0
}
func (version ApiVersion) GreaterThan(other ApiVersion) bool {
// GreaterThan is a function for comparing APIVersion structs
func (version APIVersion) GreaterThan(other APIVersion) bool {
return version.compare(other) > 0
}
func (version ApiVersion) GreaterThanOrEqualTo(other ApiVersion) bool {
// GreaterThanOrEqualTo is a function for comparing APIVersion structs
func (version APIVersion) GreaterThanOrEqualTo(other APIVersion) bool {
return version.compare(other) >= 0
}
func (version ApiVersion) compare(other ApiVersion) int {
func (version APIVersion) compare(other APIVersion) int {
for i, v := range version {
if i <= len(other)-1 {
otherVersion := other[i]
@ -111,13 +117,14 @@ func (version ApiVersion) compare(other ApiVersion) int {
type Client struct {
SkipServerVersionCheck bool
HTTPClient *http.Client
TLSConfig *tls.Config
endpoint string
endpointURL *url.URL
eventMonitor *eventMonitoringState
requestedApiVersion ApiVersion
serverApiVersion ApiVersion
expectedApiVersion ApiVersion
requestedAPIVersion APIVersion
serverAPIVersion APIVersion
expectedAPIVersion APIVersion
}
// NewClient returns a Client instance ready for communication with the given
@ -132,6 +139,18 @@ func NewClient(endpoint string) (*Client, error) {
return client, nil
}
// NewTLSClient returns a Client instance ready for TLS communications with the givens
// server endpoint, key and certificates . It will use the latest remote API version
// available in the server.
func NewTLSClient(endpoint string, cert, key, ca string) (*Client, error) {
client, err := NewVersionnedTLSClient(endpoint, cert, key, ca, "")
if err != nil {
return nil, err
}
client.SkipServerVersionCheck = true
return client, nil
}
// NewVersionedClient returns a Client instance ready for communication with
// the given server endpoint, using a specific remote API version.
func NewVersionedClient(endpoint string, apiVersionString string) (*Client, error) {
@ -139,9 +158,9 @@ func NewVersionedClient(endpoint string, apiVersionString string) (*Client, erro
if err != nil {
return nil, err
}
var requestedApiVersion ApiVersion
var requestedAPIVersion APIVersion
if strings.Contains(apiVersionString, ".") {
requestedApiVersion, err = NewApiVersion(apiVersionString)
requestedAPIVersion, err = NewAPIVersion(apiVersionString)
if err != nil {
return nil, err
}
@ -151,23 +170,74 @@ func NewVersionedClient(endpoint string, apiVersionString string) (*Client, erro
endpoint: endpoint,
endpointURL: u,
eventMonitor: new(eventMonitoringState),
requestedApiVersion: requestedApiVersion,
requestedAPIVersion: requestedAPIVersion,
}, nil
}
func (c *Client) checkApiVersion() error {
serverApiVersionString, err := c.getServerApiVersionString()
// NewVersionnedTLSClient returns a Client instance ready for TLS communications with the givens
// server endpoint, key and certificates, using a specific remote API version.
func NewVersionnedTLSClient(endpoint string, cert, key, ca, apiVersionString string) (*Client, error) {
u, err := parseEndpoint(endpoint)
if err != nil {
return err
return nil, err
}
c.serverApiVersion, err = NewApiVersion(serverApiVersionString)
var requestedAPIVersion APIVersion
if strings.Contains(apiVersionString, ".") {
requestedAPIVersion, err = NewAPIVersion(apiVersionString)
if err != nil {
return nil, err
}
}
if cert == "" || key == "" {
return nil, errors.New("Both cert and key path are required")
}
tlsCert, err := tls.LoadX509KeyPair(cert, key)
if err != nil {
return err
return nil, err
}
if c.requestedApiVersion == nil {
c.expectedApiVersion = c.serverApiVersion
tlsConfig := &tls.Config{Certificates: []tls.Certificate{tlsCert}}
if ca == "" {
tlsConfig.InsecureSkipVerify = true
} else {
c.expectedApiVersion = c.requestedApiVersion
cert, err := ioutil.ReadFile(ca)
if err != nil {
return nil, err
}
caPool := x509.NewCertPool()
if !caPool.AppendCertsFromPEM(cert) {
return nil, errors.New("Could not add RootCA pem")
}
tlsConfig.RootCAs = caPool
}
tr := &http.Transport{
TLSClientConfig: tlsConfig,
}
if err != nil {
return nil, err
}
return &Client{
HTTPClient: &http.Client{Transport: tr},
TLSConfig: tlsConfig,
endpoint: endpoint,
endpointURL: u,
eventMonitor: new(eventMonitoringState),
requestedAPIVersion: requestedAPIVersion,
}, nil
}
func (c *Client) checkAPIVersion() error {
serverAPIVersionString, err := c.getServerAPIVersionString()
if err != nil {
return err
}
c.serverAPIVersion, err = NewAPIVersion(serverAPIVersionString)
if err != nil {
return err
}
if c.requestedAPIVersion == nil {
c.expectedAPIVersion = c.serverAPIVersion
} else {
c.expectedAPIVersion = c.requestedAPIVersion
}
return nil
}
@ -187,7 +257,7 @@ func (c *Client) Ping() error {
return nil
}
func (c *Client) getServerApiVersionString() (version string, err error) {
func (c *Client) getServerAPIVersionString() (version string, err error) {
body, status, err := c.do("GET", "/version", nil)
if err != nil {
return "", err
@ -213,8 +283,8 @@ func (c *Client) do(method, path string, data interface{}) ([]byte, int, error)
}
params = bytes.NewBuffer(buf)
}
if path != "/version" && !c.SkipServerVersionCheck && c.expectedApiVersion == nil {
err := c.checkApiVersion()
if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil {
err := c.checkAPIVersion()
if err != nil {
return nil, -1, err
}
@ -268,8 +338,8 @@ func (c *Client) stream(method, path string, setRawTerminal, rawJSONStream bool,
if (method == "POST" || method == "PUT") && in == nil {
in = bytes.NewReader(nil)
}
if path != "/version" && !c.SkipServerVersionCheck && c.expectedApiVersion == nil {
err := c.checkApiVersion()
if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil {
err := c.checkAPIVersion()
if err != nil {
return err
}
@ -357,8 +427,8 @@ func (c *Client) stream(method, path string, setRawTerminal, rawJSONStream bool,
}
func (c *Client) hijack(method, path string, success chan struct{}, setRawTerminal bool, in io.Reader, stderr, stdout io.Writer, data interface{}) error {
if path != "/version" && !c.SkipServerVersionCheck && c.expectedApiVersion == nil {
err := c.checkApiVersion()
if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil {
err := c.checkAPIVersion()
if err != nil {
return err
}
@ -434,11 +504,10 @@ func (c *Client) getURL(path string) string {
urlStr = ""
}
if c.requestedApiVersion != nil {
return fmt.Sprintf("%s/v%s%s", urlStr, c.requestedApiVersion, path)
} else {
return fmt.Sprintf("%s%s", urlStr, path)
if c.requestedAPIVersion != nil {
return fmt.Sprintf("%s/v%s%s", urlStr, c.requestedAPIVersion, path)
}
return fmt.Sprintf("%s%s", urlStr, path)
}
type jsonMessage struct {
@ -495,6 +564,12 @@ func queryString(opts interface{}) string {
items.Add(key, string(b))
}
}
case reflect.Map:
if len(v.MapKeys()) > 0 {
if b, err := json.Marshal(v.Interface()); err == nil {
items.Add(key, string(b))
}
}
}
}
return items.Encode()
@ -520,7 +595,22 @@ func parseEndpoint(endpoint string) (*url.URL, error) {
return nil, ErrInvalidEndpoint
}
if u.Scheme == "tcp" {
u.Scheme = "http"
_, port, err := net.SplitHostPort(u.Host)
if err != nil {
if e, ok := err.(*net.AddrError); ok {
if e.Err == "missing port in address" {
return u, nil
}
}
return nil, ErrInvalidEndpoint
}
number, err := strconv.ParseInt(port, 10, 64)
if err == nil && number == 2376 {
u.Scheme = "https"
} else {
u.Scheme = "http"
}
}
if u.Scheme != "http" && u.Scheme != "https" && u.Scheme != "unix" {
return nil, ErrInvalidEndpoint

View File

@ -39,8 +39,32 @@ func TestNewAPIClient(t *testing.T) {
if !client.SkipServerVersionCheck {
t.Error("Expected SkipServerVersionCheck to be true, got false")
}
if client.requestedApiVersion != nil {
t.Errorf("Expected requestedApiVersion to be nil, got %#v.", client.requestedApiVersion)
if client.requestedAPIVersion != nil {
t.Errorf("Expected requestedAPIVersion to be nil, got %#v.", client.requestedAPIVersion)
}
}
func newTLSClient(endpoint string) (*Client, error) {
return NewTLSClient(endpoint,
"testing/data/cert.pem",
"testing/data/key.pem",
"testing/data/ca.pem")
}
func TestNewTSLAPIClient(t *testing.T) {
endpoint := "https://localhost:4243"
client, err := newTLSClient(endpoint)
if err != nil {
t.Fatal(err)
}
if client.endpoint != endpoint {
t.Errorf("Expected endpoint %s. Got %s.", endpoint, client.endpoint)
}
if !client.SkipServerVersionCheck {
t.Error("Expected SkipServerVersionCheck to be true, got false")
}
if client.requestedAPIVersion != nil {
t.Errorf("Expected requestedAPIVersion to be nil, got %#v.", client.requestedAPIVersion)
}
}
@ -56,14 +80,45 @@ func TestNewVersionedClient(t *testing.T) {
if client.HTTPClient != http.DefaultClient {
t.Errorf("Expected http.Client %#v. Got %#v.", http.DefaultClient, client.HTTPClient)
}
if reqVersion := client.requestedApiVersion.String(); reqVersion != "1.12" {
t.Errorf("Wrong requestApiVersion. Want %q. Got %q.", "1.12", reqVersion)
if reqVersion := client.requestedAPIVersion.String(); reqVersion != "1.12" {
t.Errorf("Wrong requestAPIVersion. Want %q. Got %q.", "1.12", reqVersion)
}
if client.SkipServerVersionCheck {
t.Error("Expected SkipServerVersionCheck to be false, got true")
}
}
func TestNewTLSVersionedClient(t *testing.T) {
certPath := "testing/data/cert.pem"
keyPath := "testing/data/key.pem"
caPath := "testing/data/ca.pem"
endpoint := "https://localhost:4243"
client, err := NewVersionnedTLSClient(endpoint, certPath, keyPath, caPath, "1.14")
if err != nil {
t.Fatal(err)
}
if client.endpoint != endpoint {
t.Errorf("Expected endpoint %s. Got %s.", endpoint, client.endpoint)
}
if reqVersion := client.requestedAPIVersion.String(); reqVersion != "1.14" {
t.Errorf("Wrong requestAPIVersion. Want %q. Got %q.", "1.14", reqVersion)
}
if client.SkipServerVersionCheck {
t.Error("Expected SkipServerVersionCheck to be false, got true")
}
}
func TestNewTLSVersionedClientInvalidCA(t *testing.T) {
certPath := "testing/data/cert.pem"
keyPath := "testing/data/key.pem"
caPath := "testing/data/key.pem"
endpoint := "https://localhost:4243"
_, err := NewVersionnedTLSClient(endpoint, certPath, keyPath, caPath, "1.14")
if err == nil {
t.Errorf("Expected invalid ca at %s", caPath)
}
}
func TestNewClientInvalidEndpoint(t *testing.T) {
cases := []string{
"htp://localhost:3243", "http://localhost:a", "localhost:8080",
@ -81,6 +136,28 @@ func TestNewClientInvalidEndpoint(t *testing.T) {
}
}
func TestNewTLSClient2376(t *testing.T) {
var tests = []struct {
endpoint string
expected string
}{
{"tcp://localhost:2376", "https"},
{"tcp://localhost:2375", "http"},
{"tcp://localhost:4000", "http"},
}
for _, tt := range tests {
client, err := newTLSClient(tt.endpoint)
if err != nil {
t.Error(err)
}
got := client.endpointURL.Scheme
if got != tt.expected {
t.Errorf("endpointURL.Scheme: Got %s. Want %s.", got, tt.expected)
}
}
}
func TestGetURL(t *testing.T) {
var tests = []struct {
endpoint string
@ -129,6 +206,7 @@ func TestQueryString(t *testing.T) {
{ListContainersOptions{All: true}, "all=1"},
{ListContainersOptions{Before: "something"}, "before=something"},
{ListContainersOptions{Before: "something", Since: "other"}, "before=something&since=other"},
{ListContainersOptions{Filters: map[string][]string{"status": {"paused", "running"}}}, "filters=%7B%22status%22%3A%5B%22paused%22%2C%22running%22%5D%7D"},
{dumb{X: 10, Y: 10.35000}, "x=10&y=10.35"},
{dumb{W: v, X: 10, Y: 10.35000}, f32QueryString},
{dumb{X: 10, Y: 10.35000, Z: 10}, "x=10&y=10.35&zee=10"},
@ -147,7 +225,7 @@ func TestQueryString(t *testing.T) {
}
}
func TestNewApiVersionFailures(t *testing.T) {
func TestNewAPIVersionFailures(t *testing.T) {
var tests = []struct {
input string
expectedError string
@ -156,17 +234,17 @@ func TestNewApiVersionFailures(t *testing.T) {
{"1.0-beta", `Unable to parse version "1.0-beta": "0-beta" is not an integer`},
}
for _, tt := range tests {
v, err := NewApiVersion(tt.input)
v, err := NewAPIVersion(tt.input)
if v != nil {
t.Errorf("Expected <nil> version, got %v.", v)
}
if err.Error() != tt.expectedError {
t.Errorf("NewApiVersion(%q): wrong error. Want %q. Got %q", tt.input, tt.expectedError, err.Error())
t.Errorf("NewAPIVersion(%q): wrong error. Want %q. Got %q", tt.input, tt.expectedError, err.Error())
}
}
}
func TestApiVersions(t *testing.T) {
func TestAPIVersions(t *testing.T) {
var tests = []struct {
a string
b string
@ -192,8 +270,8 @@ func TestApiVersions(t *testing.T) {
}
for _, tt := range tests {
a, _ := NewApiVersion(tt.a)
b, _ := NewApiVersion(tt.b)
a, _ := NewAPIVersion(tt.a)
b, _ := NewAPIVersion(tt.b)
if tt.expectedALessThanB && !a.LessThan(b) {
t.Errorf("Expected %#v < %#v", a, b)

View File

@ -18,15 +18,17 @@ import (
// ListContainersOptions specify parameters to the ListContainers function.
//
// See http://goo.gl/XqtcyU for more details.
// See http://goo.gl/6Y4Gz7 for more details.
type ListContainersOptions struct {
All bool
Size bool
Limit int
Since string
Before string
All bool
Size bool
Limit int
Since string
Before string
Filters map[string][]string
}
// APIPort is a type that represents a port mapping returned by the Docker API
type APIPort struct {
PrivatePort int64 `json:"PrivatePort,omitempty" yaml:"PrivatePort,omitempty"`
PublicPort int64 `json:"PublicPort,omitempty" yaml:"PublicPort,omitempty"`
@ -51,7 +53,7 @@ type APIContainers struct {
// ListContainers returns a slice of containers matching the given criteria.
//
// See http://goo.gl/XqtcyU for more details.
// 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)
@ -88,8 +90,10 @@ func (p Port) Proto() string {
type State struct {
Running bool `json:"Running,omitempty" yaml:"Running,omitempty"`
Paused bool `json:"Paused,omitempty" yaml:"Paused,omitempty"`
OOMKilled bool `json:"OOMKilled,omitempty" yaml:"OOMKilled,omitempty"`
Pid int `json:"Pid,omitempty" yaml:"Pid,omitempty"`
ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty"`
Error string `json:"Error,omitempty" yaml:"Error,omitempty"`
StartedAt time.Time `json:"StartedAt,omitempty" yaml:"StartedAt,omitempty"`
FinishedAt time.Time `json:"FinishedAt,omitempty" yaml:"FinishedAt,omitempty"`
}
@ -105,13 +109,18 @@ func (s *State) String() string {
return fmt.Sprintf("Exit %d", s.ExitCode)
}
// PortBinding represents the host/container port mapping as returned in the
// `docker inspect` json
type PortBinding struct {
HostIp string `json:"HostIP,omitempty" yaml:"HostIP,omitempty"`
HostIP string `json:"HostIP,omitempty" yaml:"HostIP,omitempty"`
HostPort string `json:"HostPort,omitempty" yaml:"HostPort,omitempty"`
}
// PortMapping represents a deprecated field in the `docker inspect` output,
// and its value as found in NetworkSettings should always be nil
type PortMapping map[string]string
// NetworkSettings contains network-related information about a container
type NetworkSettings struct {
IPAddress string `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty"`
IPPrefixLen int `json:"IPPrefixLen,omitempty" yaml:"IPPrefixLen,omitempty"`
@ -121,6 +130,8 @@ type NetworkSettings struct {
Ports map[Port][]PortBinding `json:"Ports,omitempty" yaml:"Ports,omitempty"`
}
// PortMappingAPI translates the port mappings as contained in NetworkSettings
// into the format in which they would appear when returned by the API
func (settings *NetworkSettings) PortMappingAPI() []APIPort {
var mapping []APIPort
for port, bindings := range settings.Ports {
@ -139,7 +150,7 @@ func (settings *NetworkSettings) PortMappingAPI() []APIPort {
PrivatePort: int64(p),
PublicPort: int64(h),
Type: port.Proto(),
IP: binding.HostIp,
IP: binding.HostIP,
})
}
}
@ -154,14 +165,17 @@ func parsePort(rawPort string) (int, error) {
return int(port), nil
}
// 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
// given host. Those are contained in HostConfig
type Config struct {
Hostname string `json:"Hostname,omitempty" yaml:"Hostname,omitempty"`
Domainname string `json:"Domainname,omitempty" yaml:"Domainname,omitempty"`
User string `json:"User,omitempty" yaml:"User,omitempty"`
Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty"`
MemorySwap int64 `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty"`
CpuShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty"`
CpuSet string `json:"CpuSet,omitempty" yaml:"CpuSet,omitempty"`
CPUShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty"`
CPUSet string `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty"`
AttachStdin bool `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty"`
AttachStdout bool `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty"`
AttachStderr bool `json:"AttachStderr,omitempty" yaml:"AttachStderr,omitempty"`
@ -172,7 +186,7 @@ type Config struct {
StdinOnce bool `json:"StdinOnce,omitempty" yaml:"StdinOnce,omitempty"`
Env []string `json:"Env,omitempty" yaml:"Env,omitempty"`
Cmd []string `json:"Cmd,omitempty" yaml:"Cmd,omitempty"`
Dns []string `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.9 and below only
DNS []string `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.9 and below only
Image string `json:"Image,omitempty" yaml:"Image,omitempty"`
Volumes map[string]struct{} `json:"Volumes,omitempty" yaml:"Volumes,omitempty"`
VolumesFrom string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"`
@ -181,6 +195,8 @@ type Config struct {
NetworkDisabled bool `json:"NetworkDisabled,omitempty" yaml:"NetworkDisabled,omitempty"`
}
// Container is the type encompasing everything about a container - its config,
// hostconfig, etc.
type Container struct {
ID string `json:"Id" yaml:"Id"`
@ -249,10 +265,11 @@ func (c *Client) ContainerChanges(id string) ([]Change, error) {
// CreateContainerOptions specify parameters to the CreateContainer function.
//
// See http://goo.gl/mErxNp for more details.
// See http://goo.gl/2xxQQK for more details.
type CreateContainerOptions struct {
Name string
Config *Config `qs:"-"`
Name string
Config *Config `qs:"-"`
HostConfig *HostConfig
}
// CreateContainer creates a new container, returning the container instance,
@ -261,7 +278,14 @@ type CreateContainerOptions struct {
// See http://goo.gl/mErxNp for more details.
func (c *Client) CreateContainer(opts CreateContainerOptions) (*Container, error) {
path := "/containers/create?" + queryString(opts)
body, status, err := c.do("POST", path, opts.Config)
body, status, err := c.do("POST", path, struct {
*Config
HostConfig *HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty"`
}{
opts.Config,
opts.HostConfig,
})
if status == http.StatusNotFound {
return nil, ErrNoSuchImage
}
@ -279,6 +303,8 @@ func (c *Client) CreateContainer(opts CreateContainerOptions) (*Container, error
return &container, nil
}
// KeyValuePair is a type for generic key/value pairs as used in the Lxc
// configuration
type KeyValuePair struct {
Key string `json:"Key,omitempty" yaml:"Key,omitempty"`
Value string `json:"Value,omitempty" yaml:"Value,omitempty"`
@ -315,6 +341,8 @@ func NeverRestart() RestartPolicy {
return RestartPolicy{Name: "no"}
}
// HostConfig contains the container options related to starting a container on
// a given host
type HostConfig struct {
Binds []string `json:"Binds,omitempty" yaml:"Binds,omitempty"`
CapAdd []string `json:"CapAdd,omitempty" yaml:"CapAdd,omitempty"`
@ -325,8 +353,9 @@ type HostConfig struct {
PortBindings map[Port][]PortBinding `json:"PortBindings,omitempty" yaml:"PortBindings,omitempty"`
Links []string `json:"Links,omitempty" yaml:"Links,omitempty"`
PublishAllPorts bool `json:"PublishAllPorts,omitempty" yaml:"PublishAllPorts,omitempty"`
Dns []string `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.10 and above only
DnsSearch []string `json:"DnsSearch,omitempty" yaml:"DnsSearch,omitempty"`
DNS []string `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.10 and above only
DNSSearch []string `json:"DnsSearch,omitempty" yaml:"DnsSearch,omitempty"`
ExtraHosts []string `json:"ExtraHosts,omitempty" yaml:"ExtraHosts,omitempty"`
VolumesFrom []string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"`
NetworkMode string `json:"NetworkMode,omitempty" yaml:"NetworkMode,omitempty"`
RestartPolicy RestartPolicy `json:"RestartPolicy,omitempty" yaml:"RestartPolicy,omitempty"`

View File

@ -102,6 +102,14 @@ func TestListContainersParams(t *testing.T) {
ListContainersOptions{All: true, Limit: 10, Since: "adf9983", Before: "abdeef"},
map[string][]string{"all": {"1"}, "limit": {"10"}, "since": {"adf9983"}, "before": {"abdeef"}},
},
{
ListContainersOptions{Filters: map[string][]string{"status": {"paused", "running"}}},
map[string][]string{"filters": {"{\"status\":[\"paused\",\"running\"]}"}},
},
{
ListContainersOptions{All: true, Filters: map[string][]string{"exited": {"0"}, "status": {"exited"}}},
map[string][]string{"all": {"1"}, "filters": {"{\"exited\":[\"0\"],\"status\":[\"exited\"]}"}},
},
}
fakeRT := &FakeRoundTripper{message: "[]", status: http.StatusOK}
client := newTestClient(fakeRT)
@ -440,6 +448,27 @@ func TestCreateContainerImageNotFound(t *testing.T) {
}
}
func TestCreateContainerWithHostConfig(t *testing.T) {
fakeRT := &FakeRoundTripper{message: "{}", status: http.StatusOK}
client := newTestClient(fakeRT)
config := Config{}
hostConfig := HostConfig{PublishAllPorts: true}
opts := CreateContainerOptions{Name: "TestCreateContainerWithHostConfig", Config: &config, HostConfig: &hostConfig}
_, err := client.CreateContainer(opts)
if err != nil {
t.Fatal(err)
}
req := fakeRT.requests[0]
var gotBody map[string]interface{}
err = json.NewDecoder(req.Body).Decode(&gotBody)
if err != nil {
t.Fatal(err)
}
if _, ok := gotBody["HostConfig"]; !ok {
t.Errorf("CreateContainer: wrong body. HostConfig was not serialized")
}
}
func TestStartContainer(t *testing.T) {
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
client := newTestClient(fakeRT)
@ -1251,7 +1280,7 @@ func TestLogsNoContainer(t *testing.T) {
}
func TestNoSuchContainerError(t *testing.T) {
var err error = &NoSuchContainer{ID: "i345"}
var err = &NoSuchContainer{ID: "i345"}
expected := "No such container: i345"
if got := err.Error(); got != expected {
t.Errorf("NoSuchContainer: wrong message. Want %q. Got %q.", expected, got)

View File

@ -5,6 +5,7 @@
package docker
import (
"crypto/tls"
"encoding/json"
"errors"
"fmt"
@ -49,6 +50,11 @@ var (
// ErrListenerAlreadyExists is the error returned when the listerner already
// exists.
ErrListenerAlreadyExists = errors.New("listener already exists for docker events")
// EOFEvent is sent when the event listener receives an EOF error.
EOFEvent = &APIEvents{
Status: "EOF",
}
)
// AddEventListener adds a new listener to container events in the Docker API.
@ -111,6 +117,16 @@ func (eventState *eventMonitoringState) removeListener(listener chan<- *APIEvent
return nil
}
func (eventState *eventMonitoringState) closeListeners() {
eventState.Lock()
defer eventState.Unlock()
for _, l := range eventState.listeners {
close(l)
eventState.Add(-1)
}
eventState.listeners = nil
}
func listenerExists(a chan<- *APIEvents, list *[]chan<- *APIEvents) bool {
for _, b := range *list {
if b == a {
@ -152,7 +168,7 @@ func (eventState *eventMonitoringState) monitorEvents(c *Client) {
time.Sleep(10 * time.Millisecond)
}
if err = eventState.connectWithRetry(c); err != nil {
eventState.terminate(err)
eventState.terminate()
}
for eventState.isEnabled() {
timeout := time.After(100 * time.Millisecond)
@ -161,11 +177,16 @@ func (eventState *eventMonitoringState) monitorEvents(c *Client) {
if !ok {
return
}
if ev == EOFEvent {
eventState.closeListeners()
eventState.terminate()
return
}
go eventState.sendEvent(ev)
go eventState.updateLastSeen(ev)
case err = <-eventState.errC:
if err == ErrNoListeners {
eventState.terminate(nil)
eventState.terminate()
return
} else if err != nil {
defer func() { go eventState.monitorEvents(c) }()
@ -225,7 +246,7 @@ func (eventState *eventMonitoringState) updateLastSeen(e *APIEvents) {
}
}
func (eventState *eventMonitoringState) terminate(err error) {
func (eventState *eventMonitoringState) terminate() {
eventState.disableEventMonitoring()
}
@ -240,7 +261,13 @@ func (c *Client) eventHijack(startTime int64, eventChan chan *APIEvents, errChan
protocol = "tcp"
address = c.endpointURL.Host
}
dial, err := net.Dial(protocol, address)
var dial net.Conn
var err error
if c.TLSConfig == nil {
dial, err = net.Dial(protocol, address)
} else {
dial, err = tls.Dial(protocol, address, c.TLSConfig)
}
if err != nil {
return err
}
@ -261,6 +288,10 @@ func (c *Client) eventHijack(startTime int64, eventChan chan *APIEvents, errChan
var event APIEvents
if err = decoder.Decode(&event); err != nil {
if err == io.EOF || err == io.ErrUnexpectedEOF {
if c.eventMonitor.isEnabled() {
// Signal that we're exiting.
eventChan <- EOFEvent
}
break
}
errChan <- err
@ -271,7 +302,7 @@ func (c *Client) eventHijack(startTime int64, eventChan chan *APIEvents, errChan
if !c.eventMonitor.isEnabled() {
return
}
c.eventMonitor.C <- &event
eventChan <- &event
}
}(res, conn)
return nil

View File

@ -6,7 +6,10 @@ package docker
import (
"bufio"
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
@ -15,6 +18,39 @@ import (
)
func TestEventListeners(t *testing.T) {
testEventListeners("TestEventListeners", t, httptest.NewServer, NewClient)
}
func TestTLSEventListeners(t *testing.T) {
testEventListeners("TestTLSEventListeners", t, func(handler http.Handler) *httptest.Server {
server := httptest.NewUnstartedServer(handler)
cert, err := tls.LoadX509KeyPair("testing/data/server.pem", "testing/data/serverkey.pem")
if err != nil {
t.Fatalf("Error loading server key pair: %s", err)
}
caCert, err := ioutil.ReadFile("testing/data/ca.pem")
if err != nil {
t.Fatalf("Error loading ca certificate: %s", err)
}
caPool := x509.NewCertPool()
if !caPool.AppendCertsFromPEM(caCert) {
t.Fatalf("Could not add ca certificate")
}
server.TLS = &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: caPool,
}
server.StartTLS()
return server
}, func(url string) (*Client, error) {
return NewTLSClient(url, "testing/data/cert.pem", "testing/data/key.pem", "testing/data/ca.pem")
})
}
func testEventListeners(testName string, t *testing.T, buildServer func(http.Handler) *httptest.Server, buildClient func(string) (*Client, error)) {
response := `{"status":"create","id":"dfdf82bd3881","from":"base:latest","time":1374067924}
{"status":"start","id":"dfdf82bd3881","from":"base:latest","time":1374067924}
{"status":"stop","id":"dfdf82bd3881","from":"base:latest","time":1374067966}
@ -22,7 +58,7 @@ func TestEventListeners(t *testing.T) {
`
var req http.Request
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
server := buildServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rsc := bufio.NewScanner(strings.NewReader(response))
for rsc.Scan() {
w.Write([]byte(rsc.Text()))
@ -33,7 +69,7 @@ func TestEventListeners(t *testing.T) {
}))
defer server.Close()
client, err := NewClient(server.URL)
client, err := buildClient(server.URL)
if err != nil {
t.Errorf("Failed to create client: %s", err)
}
@ -53,7 +89,7 @@ func TestEventListeners(t *testing.T) {
for {
select {
case msg := <-listener:
t.Logf("Recieved: %s", *msg)
t.Logf("Received: %s", *msg)
count++
err = checkEvent(count, msg)
if err != nil {
@ -63,7 +99,7 @@ func TestEventListeners(t *testing.T) {
return
}
case <-timeout:
t.Fatal("TestAddEventListener timed out waiting on events")
t.Fatalf("%s timed out waiting on events", testName)
}
}
}

View File

@ -50,8 +50,10 @@ type StartExecOptions struct {
Success chan struct{} `json:"-"`
}
// Exec is the type representing a `docker exec` instance and containing the
// instance ID
type Exec struct {
Id string `json:"Id,omitempty" yaml:"Id,omitempty"`
ID string `json:"Id,omitempty" yaml:"Id,omitempty"`
}
// CreateExec sets up an exec instance in a running container `id`, returning the exec
@ -76,9 +78,9 @@ func (c *Client) CreateExec(opts CreateExecOptions) (*Exec, error) {
return &exec, nil
}
// Starts a previously set up exec instance id. If opts.Detach is true, it returns
// after starting the exec command. Otherwise, it sets up an interactive session
// with the exec command.
// StartExec starts a previously set up exec instance id. If opts.Detach is
// true, it returns after starting the exec command. Otherwise, it sets up an
// interactive session with the exec command.
//
// See http://goo.gl/JW8Lxl for more details
func (c *Client) StartExec(id string, opts StartExecOptions) error {
@ -102,8 +104,9 @@ func (c *Client) StartExec(id string, opts StartExecOptions) error {
return c.hijack("POST", path, opts.Success, opts.RawTerminal, opts.InputStream, opts.ErrorStream, opts.OutputStream, opts)
}
// Resizes the tty session used by the exec command id. This API is valid only
// if Tty was specified as part of creating and starting the exec command.
// ResizeExecTTY resizes the tty session used by the exec command id. This API
// is valid only if Tty was specified as part of creating and starting the exec
// command.
//
// See http://goo.gl/YDSx1f for more details
func (c *Client) ResizeExecTTY(id string, height, width int) error {

View File

@ -16,7 +16,7 @@ import (
func TestExecCreate(t *testing.T) {
jsonContainer := `{"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"}`
var expected struct{ Id string }
var expected struct{ ID string }
err := json.Unmarshal([]byte(jsonContainer), &expected)
if err != nil {
t.Fatal(err)
@ -35,9 +35,9 @@ func TestExecCreate(t *testing.T) {
if err != nil {
t.Fatal(err)
}
expectedId := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
if execObj.Id != expectedId {
t.Errorf("ExecCreate: wrong ID. Want %q. Got %q.", expectedId, execObj.Id)
expectedID := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
if execObj.ID != expectedID {
t.Errorf("ExecCreate: wrong ID. Want %q. Got %q.", expectedID, execObj.ID)
}
req := fakeRT.requests[0]
if req.Method != "POST" {
@ -47,7 +47,7 @@ func TestExecCreate(t *testing.T) {
if gotPath := req.URL.Path; gotPath != expectedURL.Path {
t.Errorf("ExecCreate: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath)
}
var gotBody struct{ Id string }
var gotBody struct{ ID string }
err = json.NewDecoder(req.Body).Decode(&gotBody)
if err != nil {
t.Fatal(err)
@ -55,13 +55,13 @@ func TestExecCreate(t *testing.T) {
}
func TestExecStartDetached(t *testing.T) {
execId := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
execID := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
fakeRT := &FakeRoundTripper{status: http.StatusOK}
client := newTestClient(fakeRT)
config := StartExecOptions{
Detach: true,
}
err := client.StartExec(execId, config)
err := client.StartExec(execID, config)
if err != nil {
t.Fatal(err)
}
@ -69,7 +69,7 @@ func TestExecStartDetached(t *testing.T) {
if req.Method != "POST" {
t.Errorf("ExecStart: wrong HTTP method. Want %q. Got %q.", "POST", req.Method)
}
expectedURL, _ := url.Parse(client.getURL("/exec/" + execId + "/start"))
expectedURL, _ := url.Parse(client.getURL("/exec/" + execID + "/start"))
if gotPath := req.URL.Path; gotPath != expectedURL.Path {
t.Errorf("ExecCreate: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath)
}
@ -97,7 +97,7 @@ func TestExecStartAndAttach(t *testing.T) {
client.SkipServerVersionCheck = true
var stdout, stderr bytes.Buffer
success := make(chan struct{})
execId := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
execID := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
opts := StartExecOptions{
OutputStream: &stdout,
ErrorStream: &stderr,
@ -105,15 +105,15 @@ func TestExecStartAndAttach(t *testing.T) {
RawTerminal: true,
Success: success,
}
go client.StartExec(execId, opts)
go client.StartExec(execID, opts)
<-success
}
func TestExecResize(t *testing.T) {
execId := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
execID := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
fakeRT := &FakeRoundTripper{status: http.StatusOK}
client := newTestClient(fakeRT)
err := client.ResizeExecTTY(execId, 10, 20)
err := client.ResizeExecTTY(execID, 10, 20)
if err != nil {
t.Fatal(err)
}
@ -121,7 +121,7 @@ func TestExecResize(t *testing.T) {
if req.Method != "POST" {
t.Errorf("ExecStart: wrong HTTP method. Want %q. Got %q.", "POST", req.Method)
}
expectedURL, _ := url.Parse(client.getURL("/exec/" + execId + "/resize?h=10&w=20"))
expectedURL, _ := url.Parse(client.getURL("/exec/" + execID + "/resize?h=10&w=20"))
if gotPath := req.URL.RequestURI(); gotPath != expectedURL.RequestURI() {
t.Errorf("ExecCreate: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath)
}

View File

@ -25,9 +25,10 @@ type APIImages struct {
Created int64 `json:"Created,omitempty" yaml:"Created,omitempty"`
Size int64 `json:"Size,omitempty" yaml:"Size,omitempty"`
VirtualSize int64 `json:"VirtualSize,omitempty" yaml:"VirtualSize,omitempty"`
ParentId string `json:"ParentId,omitempty" yaml:"ParentId,omitempty"`
ParentID string `json:"ParentId,omitempty" yaml:"ParentId,omitempty"`
}
// Image is the type representing a docker image and its various properties
type Image struct {
ID string `json:"Id" yaml:"Id"`
Parent string `json:"Parent,omitempty" yaml:"Parent,omitempty"`
@ -52,6 +53,8 @@ type ImageHistory struct {
Size int64 `json:"Size,omitempty" yaml:"Size,omitempty"`
}
// ImagePre012 serves the same purpose as the Image type except that it is for
// earlier versions of the Docker API (pre-012 to be specific)
type ImagePre012 struct {
ID string `json:"id"`
Parent string `json:"parent,omitempty"`
@ -66,6 +69,14 @@ type ImagePre012 struct {
Size int64 `json:"size,omitempty"`
}
// ListImagesOptions specify parameters to the ListImages function.
//
// See http://goo.gl/2rOLFF for more details.
type ListImagesOptions struct {
All bool
Filters map[string][]string
}
var (
// ErrNoSuchImage is the error returned when the image does not exist.
ErrNoSuchImage = errors.New("no such image")
@ -77,18 +88,17 @@ var (
// ErrMissingOutputStream is the error returned when no output stream
// is provided to some calls, like BuildImage.
ErrMissingOutputStream = errors.New("missing output stream")
// ErrMultipleContexts is the error returned when both a ContextDir and
// InputStream are provided in BuildImageOptions
ErrMultipleContexts = errors.New("image build may not be provided BOTH context dir and input stream")
)
// ListImages returns the list of available images in the server.
//
// See http://goo.gl/VmcR6v for more details.
func (c *Client) ListImages(all bool) ([]APIImages, error) {
path := "/images/json?all="
if all {
path += "1"
} else {
path += "0"
}
// 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)
if err != nil {
return nil, err
@ -146,7 +156,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(apiVersion_1_12) {
if c.SkipServerVersionCheck || c.expectedAPIVersion.GreaterThanOrEqualTo(apiVersion1_12) {
err = json.Unmarshal(body, &image)
if err != nil {
return nil, err
@ -194,9 +204,16 @@ type PushImageOptions struct {
// 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"`
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.
@ -212,12 +229,7 @@ func (c *Client) PushImage(opts PushImageOptions, auth AuthConfiguration) error
name := opts.Name
opts.Name = ""
path := "/images/" + name + "/push?" + queryString(&opts)
var headers = make(map[string]string)
var buf bytes.Buffer
json.NewEncoder(&buf).Encode(auth)
headers["X-Registry-Auth"] = base64.URLEncoding.EncodeToString(buf.Bytes())
headers := headersWithAuth(auth)
return c.stream("POST", path, true, opts.RawJSONStream, headers, nil, opts.OutputStream, nil)
}
@ -241,11 +253,7 @@ func (c *Client) PullImage(opts PullImageOptions, auth AuthConfiguration) error
return ErrNoSuchImage
}
var headers = make(map[string]string)
var buf bytes.Buffer
json.NewEncoder(&buf).Encode(auth)
headers["X-Registry-Auth"] = base64.URLEncoding.EncodeToString(buf.Bytes())
headers := headersWithAuth(auth)
return c.createImage(queryString(&opts), headers, nil, opts.OutputStream, opts.RawJSONStream)
}
@ -324,15 +332,18 @@ func (c *Client) ImportImage(opts ImportImageOptions) error {
// For more details about the Docker building process, see
// http://goo.gl/tlPXPu.
type BuildImageOptions struct {
Name string `qs:"t"`
NoCache bool `qs:"nocache"`
SuppressOutput bool `qs:"q"`
RmTmpContainer bool `qs:"rm"`
ForceRmTmpContainer bool `qs:"forcerm"`
InputStream io.Reader `qs:"-"`
OutputStream io.Writer `qs:"-"`
RawJSONStream bool `qs:"-"`
Remote string `qs:"remote"`
Name string `qs:"t"`
NoCache bool `qs:"nocache"`
SuppressOutput bool `qs:"q"`
RmTmpContainer bool `qs:"rm"`
ForceRmTmpContainer bool `qs:"forcerm"`
InputStream io.Reader `qs:"-"`
OutputStream io.Writer `qs:"-"`
RawJSONStream bool `qs:"-"`
Remote string `qs:"remote"`
Auth AuthConfiguration `qs:"-"` // for older docker X-Registry-Auth header
AuthConfigs AuthConfigurations `qs:"-"` // for newer docker X-Registry-Config header
ContextDir string `qs:"-"`
}
// BuildImage builds an image from a tarball's url or a Dockerfile in the input
@ -343,15 +354,26 @@ func (c *Client) BuildImage(opts BuildImageOptions) error {
if opts.OutputStream == nil {
return ErrMissingOutputStream
}
var headers map[string]string
var headers = headersWithAuth(opts.Auth, opts.AuthConfigs)
if opts.Remote != "" && opts.Name == "" {
opts.Name = opts.Remote
}
if opts.InputStream != nil {
headers = map[string]string{"Content-Type": "application/tar"}
if opts.InputStream != nil || opts.ContextDir != "" {
headers["Content-Type"] = "application/tar"
} else if opts.Remote == "" {
return ErrMissingRepo
}
if opts.ContextDir != "" {
if opts.InputStream != nil {
return ErrMultipleContexts
}
var err error
if opts.InputStream, err = createTarStream(opts.ContextDir); err != nil {
return err
}
}
return c.stream("POST", fmt.Sprintf("/build?%s",
queryString(&opts)), true, opts.RawJSONStream, headers, opts.InputStream, opts.OutputStream, nil)
}
@ -389,6 +411,25 @@ func isURL(u string) bool {
return p.Scheme == "http" || p.Scheme == "https"
}
func headersWithAuth(auths ...interface{}) map[string]string {
var headers = make(map[string]string)
for _, auth := range auths {
switch auth.(type) {
case AuthConfiguration:
var buf bytes.Buffer
json.NewEncoder(&buf).Encode(auth)
headers["X-Registry-Auth"] = base64.URLEncoding.EncodeToString(buf.Bytes())
case AuthConfigurations:
var buf bytes.Buffer
json.NewEncoder(&buf).Encode(auth)
headers["X-Registry-Config"] = base64.URLEncoding.EncodeToString(buf.Bytes())
}
}
return headers
}
// APIImageSearch reflect the result of a search on the dockerHub
//
// See http://goo.gl/xI5lLZ for more details.

View File

@ -88,7 +88,7 @@ func TestListImages(t *testing.T) {
t.Fatal(err)
}
client := newTestClient(&FakeRoundTripper{message: body, status: http.StatusOK})
images, err := client.ListImages(false)
images, err := client.ListImages(ListImagesOptions{})
if err != nil {
t.Error(err)
}
@ -100,25 +100,42 @@ func TestListImages(t *testing.T) {
func TestListImagesParameters(t *testing.T) {
fakeRT := &FakeRoundTripper{message: "null", status: http.StatusOK}
client := newTestClient(fakeRT)
_, err := client.ListImages(false)
_, err := client.ListImages(ListImagesOptions{All: false})
if err != nil {
t.Fatal(err)
}
req := fakeRT.requests[0]
if req.Method != "GET" {
t.Errorf("ListImages(false: Wrong HTTP method. Want GET. Got %s.", req.Method)
t.Errorf("ListImages({All: false}: Wrong HTTP method. Want GET. Got %s.", req.Method)
}
if all := req.URL.Query().Get("all"); all != "0" {
t.Errorf("ListImages(false): Wrong parameter. Want all=0. Got all=%s", all)
if all := req.URL.Query().Get("all"); all != "0" && all != "" {
t.Errorf("ListImages({All: false}): Wrong parameter. Want all=0 or not present at all. Got all=%s", all)
}
fakeRT.Reset()
_, err = client.ListImages(true)
_, err = client.ListImages(ListImagesOptions{All: true})
if err != nil {
t.Fatal(err)
}
req = fakeRT.requests[0]
if all := req.URL.Query().Get("all"); all != "1" {
t.Errorf("ListImages(true): Wrong parameter. Want all=1. Got all=%s", all)
t.Errorf("ListImages({All: true}): Wrong parameter. Want all=1. Got all=%s", all)
}
fakeRT.Reset()
_, err = client.ListImages(ListImagesOptions{Filters: map[string][]string{
"dangling": {"true"},
}})
if err != nil {
t.Fatal(err)
}
req = fakeRT.requests[0]
body := req.URL.Query().Get("filters")
var filters map[string][]string
err = json.Unmarshal([]byte(body), &filters)
if err != nil {
t.Fatal(err)
}
if len(filters["dangling"]) != 1 || filters["dangling"][0] != "true" {
t.Errorf("ListImages(dangling=[true]): Wrong filter map. Want dangling=[true], got dangling=%v", filters["dangling"])
}
}

View File

@ -0,0 +1,99 @@
// Copyright 2014 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package docker
import (
"fmt"
"io"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/fileutils"
)
func createTarStream(srcPath string) (io.ReadCloser, error) {
excludes, err := parseDockerignore(srcPath)
if err != nil {
return nil, err
}
if err := validateContextDirectory(srcPath, excludes); err != nil {
return nil, err
}
tarOpts := &archive.TarOptions{
Excludes: excludes,
Compression: archive.Uncompressed,
NoLchown: true,
}
return archive.TarWithOptions(srcPath, tarOpts)
}
// validateContextDirectory checks if all the contents of the directory
// can be read and returns an error if some files can't be read.
// Symlinks which point to non-existing files don't trigger an error
func validateContextDirectory(srcPath string, excludes []string) error {
return filepath.Walk(filepath.Join(srcPath, "."), func(filePath string, f os.FileInfo, err error) error {
// skip this directory/file if it's not in the path, it won't get added to the context
if relFilePath, err := filepath.Rel(srcPath, filePath); err != nil {
return err
} else if skip, err := fileutils.Matches(relFilePath, excludes); err != nil {
return err
} else if skip {
if f.IsDir() {
return filepath.SkipDir
}
return nil
}
if err != nil {
if os.IsPermission(err) {
return fmt.Errorf("can't stat '%s'", filePath)
}
if os.IsNotExist(err) {
return nil
}
return err
}
// skip checking if symlinks point to non-existing files, such symlinks can be useful
// also skip named pipes, because they hanging on open
if f.Mode()&(os.ModeSymlink|os.ModeNamedPipe) != 0 {
return nil
}
if !f.IsDir() {
currentFile, err := os.Open(filePath)
if err != nil && os.IsPermission(err) {
return fmt.Errorf("no permission to read from '%s'", filePath)
}
currentFile.Close()
}
return nil
})
}
func parseDockerignore(root string) ([]string, error) {
var excludes []string
ignore, err := ioutil.ReadFile(path.Join(root, ".dockerignore"))
if err != nil && !os.IsNotExist(err) {
return excludes, fmt.Errorf("error reading .dockerignore: '%s'", err)
}
for _, pattern := range strings.Split(string(ignore), "\n") {
matches, err := filepath.Match(pattern, "Dockerfile")
if err != nil {
return excludes, fmt.Errorf("bad .dockerignore pattern: '%s', error: %s", pattern, err)
}
if matches {
return excludes, fmt.Errorf("dockerfile was excluded by .dockerignore pattern '%s'", pattern)
}
excludes = append(excludes, pattern)
}
return excludes, nil
}

View File

@ -0,0 +1,38 @@
#!/bin/bash
readonly GOPATH="${GOPATH%%:*}"
main() {
check_fmt
check_lint
}
check_fmt() {
eval "set -e"
for file in $(git ls-files '*.go') ; do
gofmt $file | diff -u $file -
done
eval "set +e"
}
check_lint() {
_install_linter
for file in $(git ls-files '*.go') ; do
if [[ ! "$(${GOPATH}/bin/golint $file)" =~ ^[[:blank:]]*$ ]] ; then
_lint_verbose && exit 1
fi
done
}
_lint_verbose() {
for file in $(git ls-files '*.go') ; do $GOPATH/bin/golint $file ; done
}
_install_linter() {
if [[ ! -x "${GOPATH}/bin/golint" ]] ; then
go get -u github.com/golang/lint/golint
fi
}
main "$@"

View File

@ -0,0 +1,3 @@
container.tar
dockerfile.tar
foofile

View File

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC1TCCAb+gAwIBAgIQJ9MsNxrUxumNbAytGi3GEDALBgkqhkiG9w0BAQswFjEU
MBIGA1UEChMLQm9vdDJEb2NrZXIwHhcNMTQxMDE2MjAyMTM4WhcNMTcwOTMwMjAy
MTM4WjAWMRQwEgYDVQQKEwtCb290MkRvY2tlcjCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBALpFCSARjG+5yXoqr7UMzuE0df7RRZfeRZI06lJ02ZqV4Iii
rgL7ML9yPxX50NbLnjiilSDTUhnyocYFItokzUzz8qpX/nlYhuN2Iqwh4d0aWS8z
f5y248F+H1z+HY2W8NPl/6DVlVwYaNW1/k+RPMlHS0INLR6j+3Ievew7RNE0NnM2
znELW6NetekDt3GUcz0Z95vDUDfdPnIk1eIFMmYvLxZh23xOca4Q37a3S8F3d+dN
+OOpwjdgY9Qme0NQUaXpgp58jWuQfB8q7mZrdnLlLqRa8gx1HeDSotX7UmWtWPkb
vd9EdlKLYw5PVpxMV1rkwf2t4TdgD5NfkpXlXkkCAwEAAaMjMCEwDgYDVR0PAQH/
BAQDAgCkMA8GA1UdEwEB/wQFMAMBAf8wCwYJKoZIhvcNAQELA4IBAQBxYjHVSKqE
MJw7CW0GddesULtXXVWGJuZdWJLQlPvPMfIfjIvlcZyS4cdVNiQ3sREFIZz8TpII
CT0/Pg3sgv/FcOQe1CN0xZYZcyiAZHK1z0fJQq2qVpdv7+tJcjI2vvU6NI24iQCo
W1wz25trJz9QbdB2MRLMjyz7TSWuafztIvcfEzaIdQ0Whqund/cSuPGQx5IwF83F
rvlkOyJSH2+VIEBTCIuykJeL0DLTt8cePBQR5L1ISXb4RUMK9ZtqRscBRv8sn7o2
ixG3wtL0gYF4xLtsQWVxI3iFVrU3WzOH/3c5shVRkWBd+AQRSwCJI4mKH7penJCF
i3/zzlkvOnjV
-----END CERTIFICATE-----

View File

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC6DCCAdKgAwIBAgIRANO6ymxQAjp66KmEka1G6b0wCwYJKoZIhvcNAQELMBYx
FDASBgNVBAoTC0Jvb3QyRG9ja2VyMB4XDTE0MTAxNjIwMjE1MloXDTE3MDkzMDIw
MjE1MlowFjEUMBIGA1UEChMLQm9vdDJEb2NrZXIwggEiMA0GCSqGSIb3DQEBAQUA
A4IBDwAwggEKAoIBAQDGA1mAhSOpZspD1dpZ7qVEQrIJw4Xo8252jHaORnEdDiFm
b6brEmr6jw8t4P3IGxbqBc/TqRV+SSXxwYEVvfpeQKH+SmqStoMNtD3Ura161az4
V0BcxMtSlsUGpoz+//QCAq8qiaxMwgiyc5253mkQm88anj2cNt7xbewiu/KFWuf7
BVpNK1+ltpJmlukfcj/G+I1bw7j1KxBjDrFqe5cyDuuZcDL2tmUXP/ZWDyXwSv+H
AOckqn44z6aXlBkVvOXDBZJqY76d/vWVDNCuZeXRnqlhP3t1kH4V0RQXo+JD2tgt
JgdU0unzyoFOSWNUBPm73tqmjUGGAmGHBmeegJr/AgMBAAGjNTAzMA4GA1UdDwEB
/wQEAwIAgDATBgNVHSUEDDAKBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMAsGCSqG
SIb3DQEBCwOCAQEABVTWl5SmBP+j5He5bQsgnIXjviSKqe40/10V4LJAOmilycRF
zLrzM+YMwfjg6PLIs8CldAMWHw9y9ktZY4MxkgCktaiaN/QmMTMwFWEcN4wy5IpM
U5l93eAg7xsnY430h3QBBADujX4wdF3fs8rSL8zAAQFL0ihurwU124K3yXKsrwpb
CiVUGfIN4sPwjy8Ws9oxHFDC9/P8lgjHZ1nBIf8KSHnMzlxDGj7isQfhtH+7mcCL
cM1qO2NirS2v7uaEPPY+MJstAz+W7EJCW9dfMSmHna2SDC37Xkin7uEY9z+qaKFL
8d/XxOB/L8Ucy8VZhdsv0dsBq5KfJntITM0ksQ==
-----END CERTIFICATE-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAxgNZgIUjqWbKQ9XaWe6lREKyCcOF6PNudox2jkZxHQ4hZm+m
6xJq+o8PLeD9yBsW6gXP06kVfkkl8cGBFb36XkCh/kpqkraDDbQ91K2tetWs+FdA
XMTLUpbFBqaM/v/0AgKvKomsTMIIsnOdud5pEJvPGp49nDbe8W3sIrvyhVrn+wVa
TStfpbaSZpbpH3I/xviNW8O49SsQYw6xanuXMg7rmXAy9rZlFz/2Vg8l8Er/hwDn
JKp+OM+ml5QZFbzlwwWSamO+nf71lQzQrmXl0Z6pYT97dZB+FdEUF6PiQ9rYLSYH
VNLp88qBTkljVAT5u97apo1BhgJhhwZnnoCa/wIDAQABAoIBAQCaGy9EC9pmU95l
DwGh7k5nIrUnTilg1FwLHWSDdCVCZKXv8ENrPelOWZqJrUo1u4eI2L8XTsewgkNq
tJu/DRzWz9yDaO0qg6rZNobMh+K076lvmZA44twOydJLS8H+D7ua+PXU2FLlZjmY
kMyXRJZmW6zCXZc7haTbJx6ZJccoquk/DkS4FcFurJP177u1YrWS9TTw9kensUtU
jQ63uf56UTN1i+0+Rxl7OW1TZlqwlri5I4njg5249+FxwwHzIq8+l7zD7K9pl8c/
nG1HuulvU2bVlDlRdyslMPAH34vw9Sku1BD8furrJLr1na5lRSLKJODEaIPEsLwv
CdEUwP9JAoGBAO76ZW80RyNB2fA+wbTq70Sr8CwrXxYemXrez5LKDC7SsohKFCPE
IedpO/n+nmymiiJvMm874EExoG6BVrbkWkeb+2vinEfOQNlDMsDx7WLjPekP3t6i
rXHO3CjFooVFq2z3mZa/Nc5NZqu8fNWNCKJxZDJphdoj6sORNJIUvZVjAoGBANQd
++J+ITcu3/+A6JrGcgLunBFQYPqkiItk0J4QKYKuX5ik9rWcQDN8TTtfW2mDuiQ4
NrCwuVPq1V1kB16JzH017SsYLo9g8I20YjnBZge9pKTeUaLVTb3C50LW8FBylop0
Bnm597dNbtSjphjoTMg0XyC19o3Esf2YeWG0QNS1AoGAWWDfFRNJU99qIldmXULM
0DM6NVrXSk+ReYnhunXEzrJQwXZrR+EwCPurydk36Uz0NuK9yypquhdUeF/5TZfk
SAoHo5byekyipl9imRUigqyY2BTudvgCxKDoaHtaSFwBPFTyZZYICquaLbrmOXxw
8UhVgCFFRYvPXuts7QHC0h8CgYBWEvy9gfU0kV7wLX02IUTuj6jhFb7ktpN6DSTi
nyhZES1VoctDEu6ydcRZTW6ouH12aSE4Pd5WgTqntQmQgVZrkNB25k8ue2Xh+srJ
KQOgLIJ9LIHwE6KCWG7DnrjRzE3uTPq7to0g4tkQjH/AJ7PQof/gJDayfJjFkXPg
A+cy6QKBgEPbKpiqscm03gT2QanBut5pg4dqPOxp0SlErA3kSFNTRK3oYBQPC+LH
qA5nD5brdkeNBB58Rll8Zpzxiff50bcvLP/7/Sb3NjaXFTEY0gVbdRof3n6N0YP3
Hu5XDNJ9RNkNzE5RIG1g86KE+aKlcrKMaigqAiuIy2PSnjkQeGk8
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC/DCCAeagAwIBAgIQMUILcXtvmSOK63zEBo0VXzALBgkqhkiG9w0BAQswFjEU
MBIGA1UEChMLQm9vdDJEb2NrZXIwHhcNMTQxMDE2MjAyMTQ2WhcNMTcwOTMwMjAy
MTQ2WjAWMRQwEgYDVQQKEwtCb290MkRvY2tlcjCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBANxUOUhNnqFnrTlLsBYzfFRZWQo268l+4K4lOJCVbfDonP3g
Mz0vGi9fcyFqEWSA8Y+ShXna625HTnReCwFdsu0861qCIq7v95hFFCyOe0iIxpd0
AKLnl90d+1vonE7andgFgoobbTiMly4UK4H6z8D148fFNIihoteOG3PIF89TFxP7
CJ/3wXnx/IKpdlO8PAnub3tBPJHvGDj7KORLy4IBxRX5VBAdfGNybE66fcrehEva
rLA4m9pgiaR/Nnr9FdKhPyqYdjflLNvzydxNvMIV4M0hFlhXmYvpMjA5/XsTnsyV
t9JHJa5Upwqsbne08t7rsm7liZNxZlko8xPOTQcCAwEAAaNKMEgwDgYDVR0PAQH/
BAQDAgCgMAwGA1UdEwEB/wQCMAAwKAYDVR0RBCEwH4ILYm9vdDJkb2NrZXKHBH8A
AAGHBAoAAg+HBMCoO2cwCwYJKoZIhvcNAQELA4IBAQAYoYcDkDWkl73FZ0WnPmAj
LiF7HU95Qg3KyEpFsAJeShSLPPbQntmwhdekEzY4tQ3eKQB/+zHFjzsCr/lmDUmH
Ea/ryQ17C+jyH+Ykg0IWW6L6veZhvRDg6Z9focVtPVBRxPTqC/Qhb54blWRASV+W
UreMuXQ5+1dQptAM7ixOeLVHjBi/bd9TL3jvwBVCr9QedteMjjK4TCF9Tbcou+MF
2w3OJJZMDhcD+YwoK9uJDqlKmcTm/vVMbSsp/pTMcnQ7jxCeR8/XyX+VwTZwaHAa
o92Q/eg3THAiWhvyT/SzyH9dHHBAyXynUwGCggKawHktfvW4QXRPuLxLrJ7iB5cy
-----END CERTIFICATE-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEoAIBAAKCAQEA3FQ5SE2eoWetOUuwFjN8VFlZCjbryX7griU4kJVt8Oic/eAz
PS8aL19zIWoRZIDxj5KFedrrbkdOdF4LAV2y7TzrWoIiru/3mEUULI57SIjGl3QA
oueX3R37W+icTtqd2AWCihttOIyXLhQrgfrPwPXjx8U0iKGi144bc8gXz1MXE/sI
n/fBefH8gql2U7w8Ce5ve0E8ke8YOPso5EvLggHFFflUEB18Y3JsTrp9yt6ES9qs
sDib2mCJpH82ev0V0qE/Kph2N+Us2/PJ3E28whXgzSEWWFeZi+kyMDn9exOezJW3
0kclrlSnCqxud7Ty3uuybuWJk3FmWSjzE85NBwIDAQABAoIBAG0ak+cW8LeShHf7
3+2Of0GxoOLrAWWdG5uAuPr31CJYve0FybnBimDtDjD8ujIfm/7xmoEWBEFutA3x
x9dcU88gvJbsHEqub9gKVQwfXjMz78tt2SbSMiR/xUnk7QorPcCMMfE71aEMFYzu
1gCed6Rg3vO81t/V0rKVH0j9S7UQz5v/oX15eVDV5LOqyCHwAi6K0eXXbqnbI0TH
SOQ/nexM2msVXWbO9t6ra6f5V7FXziDK5Xi+rPxRbX9mkrDzxDAevfuRqYBx5vtL
W2Q2hKjUAHFgXFniNSZBS7dCdAtz0el/3ct+cNmpuTMhhs7M6wC1CuYiZ/DxLiFh
Si73VckCgYEA+/ceh3+VjtQ0rgEw8sD9bqYEA8IaBiObjneIoFnKBYRG7yZd8JMm
HD4M/aQ1qhcRLPN7GR03YQULgQJURbKSjJHnhfTXHyeHC3NN4gMVHQXewu2MHCh6
7FCQ9CfK0KcYLgegVVvL3PrF3hyWGnmTu+G0UkDQRYVnaNrB7snrW6UCgYEA39tq
+MCQdu0moJ5szSZf02undg9EeW6isk9qzi7TId3/MLci2eH7PEnipipPUK3+DERq
aba0y0TKgBR2EXvXLFJA/+kfdo2loIEHOfox85HVfxgUaFRti63ZI0uF8D0QT2Yy
oJal+RFghVoSnv4LjhRKEPbIkScTXGjdK+7wFjsCfz79iKRXQQx0ALd/lL0bgkAn
QNmvrNHcFQeI2p8700WNzC39aX67SsvEt3qxkrjzC1gxhpTAuReIK1gVPPwvqHN8
BmV20FD5kMlMCix2mNCopwgUWvKvLAvoGFTxncKMA39+aJbuXAjiqJTekKgNvOE7
i9kEWw0GTNPp3JHV6QECgYAPwb0M11kT1euDIMOdyRazpf86kyaJuZzgGjD1ZFxe
JOcigbGFTp/FhZnbglzk2+pm6KXo3QBq0mPCki4hWusxZnTGzpz1VlETNCHTFeZQ
M7KoaIR/N3oie9Et59H8r/+m5xWnMhNqratyl316DX24uXrhKM3DUdHODl+LCR2D
IwKBgE1MbHuwolUPEw3HeO4R7NMFVTFei7E/fpUsimPfArGg8UydwvloNT1myJos
N2JzfGGjN2KPVcBk9fOs71mJ6VcK3C3g5JIccplk6h9VNaw55+zdQvKPTzoBoTvy
A+Fwx2AlF61KeRF87DL2YTRJ6B9MHmWgf7+GVZOxomLgEAcZ
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1 @@
doesnotexist

View File

@ -34,6 +34,7 @@ import (
// For more details on the remote API, check http://goo.gl/G3plxW.
type DockerServer struct {
containers []*docker.Container
execs []*docker.Exec
cMut sync.RWMutex
images []docker.Image
iMut sync.RWMutex
@ -90,18 +91,22 @@ func (s *DockerServer) buildMuxer() {
s.mux.Path("/containers/{id:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectContainer))
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))
s.mux.Path("/containers/{id:.*}/stop").Methods("POST").HandlerFunc(s.handlerWrapper(s.stopContainer))
s.mux.Path("/containers/{id:.*}/pause").Methods("POST").HandlerFunc(s.handlerWrapper(s.pauseContainer))
s.mux.Path("/containers/{id:.*}/unpause").Methods("POST").HandlerFunc(s.handlerWrapper(s.unpauseContainer))
s.mux.Path("/containers/{id:.*}/wait").Methods("POST").HandlerFunc(s.handlerWrapper(s.waitContainer))
s.mux.Path("/containers/{id:.*}/attach").Methods("POST").HandlerFunc(s.handlerWrapper(s.attachContainer))
s.mux.Path("/containers/{id:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeContainer))
s.mux.Path("/containers/{id:.*}/exec").Methods("POST").HandlerFunc(s.handlerWrapper(s.createExecContainer))
s.mux.Path("/exec/{id:.*}/start").Methods("POST").HandlerFunc(s.handlerWrapper(s.startExecContainer))
s.mux.Path("/images/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.pullImage))
s.mux.Path("/build").Methods("POST").HandlerFunc(s.handlerWrapper(s.buildImage))
s.mux.Path("/images/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.listImages))
s.mux.Path("/images/{id:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeImage))
s.mux.Path("/images/{name:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectImage))
s.mux.Path("/images/{name:.*}/push").Methods("POST").HandlerFunc(s.handlerWrapper(s.pushImage))
s.mux.Path("/images/{name:.*}/tag").Methods("POST").HandlerFunc(s.handlerWrapper(s.tagImage))
s.mux.Path("/events").Methods("GET").HandlerFunc(s.listEvents)
s.mux.Path("/_ping").Methods("GET").HandlerFunc(s.handlerWrapper(s.pingDocker))
s.mux.Path("/images/load").Methods("POST").HandlerFunc(s.handlerWrapper(s.loadImage))
@ -173,8 +178,8 @@ func (s *DockerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
// Returns default http.Handler mux, it allows customHandlers to call the
// default behavior if wanted.
// DefaultHandler returns default http.Handler mux, it allows customHandlers to
// call the default behavior if wanted.
func (s *DockerServer) DefaultHandler() http.Handler {
return s.mux
}
@ -210,6 +215,7 @@ func (s *DockerServer) listContainers(w http.ResponseWriter, r *http.Request) {
Created: container.Created.Unix(),
Status: container.State.String(),
Ports: container.NetworkSettings.PortMappingAPI(),
Names: []string{fmt.Sprintf("/%s", container.Name)},
}
}
}
@ -269,8 +275,7 @@ func (s *DockerServer) createContainer(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
image, err := s.findImage(config.Image)
if err != nil {
if _, err := s.findImage(config.Image); err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
@ -278,7 +283,7 @@ func (s *DockerServer) createContainer(w http.ResponseWriter, r *http.Request) {
ports := map[docker.Port][]docker.PortBinding{}
for port := range config.ExposedPorts {
ports[port] = []docker.PortBinding{{
HostIp: "0.0.0.0",
HostIP: "0.0.0.0",
HostPort: strconv.Itoa(mathrand.Int() % 65536),
}}
}
@ -294,6 +299,7 @@ func (s *DockerServer) createContainer(w http.ResponseWriter, r *http.Request) {
}
container := docker.Container{
Name: r.URL.Query().Get("name"),
ID: s.generateID(),
Created: time.Now(),
Path: path,
@ -305,7 +311,7 @@ func (s *DockerServer) createContainer(w http.ResponseWriter, r *http.Request) {
ExitCode: 0,
StartedAt: time.Now(),
},
Image: image,
Image: config.Image,
NetworkSettings: &docker.NetworkSettings{
IPAddress: fmt.Sprintf("172.16.42.%d", mathrand.Int()%250+2),
IPPrefixLen: 24,
@ -557,8 +563,10 @@ func (s *DockerServer) buildImage(w http.ResponseWriter, r *http.Request) {
}
//we did not use that Dockerfile to build image cause we are a fake Docker daemon
image := docker.Image{
ID: s.generateID(),
ID: s.generateID(),
Created: time.Now(),
}
query := r.URL.Query()
repository := image.ID
if t := query.Get("t"); t != "" {
@ -597,6 +605,22 @@ func (s *DockerServer) pushImage(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Pushed")
}
func (s *DockerServer) tagImage(w http.ResponseWriter, r *http.Request) {
name := mux.Vars(r)["name"]
s.iMut.RLock()
if _, ok := s.imgIDs[name]; !ok {
s.iMut.RUnlock()
http.Error(w, "No such image", http.StatusNotFound)
return
}
s.iMut.RUnlock()
s.iMut.Lock()
defer s.iMut.Unlock()
newRepo := r.URL.Query().Get("repo")
s.imgIDs[newRepo] = s.imgIDs[name]
w.WriteHeader(http.StatusCreated)
}
func (s *DockerServer) removeImage(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
s.iMut.RLock()
@ -604,6 +628,12 @@ func (s *DockerServer) removeImage(w http.ResponseWriter, r *http.Request) {
if img, ok := s.imgIDs[id]; ok {
id, tag = img, id
}
var tags []string
for tag, taggedID := range s.imgIDs {
if taggedID == id {
tags = append(tags, tag)
}
}
s.iMut.RUnlock()
_, index, err := s.findImageByID(id)
if err != nil {
@ -613,8 +643,10 @@ func (s *DockerServer) removeImage(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
s.iMut.Lock()
defer s.iMut.Unlock()
s.images[index] = s.images[len(s.images)-1]
s.images = s.images[:len(s.images)-1]
if len(tags) < 2 {
s.images[index] = s.images[len(s.images)-1]
s.images = s.images[:len(s.images)-1]
}
if tag != "" {
delete(s.imgIDs, tag)
}
@ -690,3 +722,23 @@ func (s *DockerServer) getImage(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/tar")
}
func (s *DockerServer) createExecContainer(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
exec := docker.Exec{ID: "id-exec-created-by-test"}
s.execs = append(s.execs, &exec)
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 {
w.WriteHeader(http.StatusOK)
return
}
}
w.WriteHeader(http.StatusNotFound)
}

View File

@ -120,6 +120,7 @@ func TestListContainers(t *testing.T) {
Created: container.Created.Unix(),
Status: container.State.String(),
Ports: container.NetworkSettings.PortMappingAPI(),
Names: []string{"/" + container.Name},
}
}
var got []docker.APIContainers
@ -473,6 +474,23 @@ func TestStopContainer(t *testing.T) {
}
}
func TestKillContainer(t *testing.T) {
server := DockerServer{}
addContainers(&server, 1)
server.containers[0].State.Running = true
server.buildMuxer()
recorder := httptest.NewRecorder()
path := fmt.Sprintf("/containers/%s/kill", server.containers[0].ID)
request, _ := http.NewRequest("POST", path, nil)
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusNoContent {
t.Errorf("KillContainer: wrong status code. Want %d. Got %d.", http.StatusNoContent, recorder.Code)
}
if server.containers[0].State.Running {
t.Error("KillContainer: did not stop the container")
}
}
func TestStopContainerWithNotifyChannel(t *testing.T) {
ch := make(chan *docker.Container, 1)
server := DockerServer{}
@ -771,12 +789,38 @@ func TestPushImageNotFound(t *testing.T) {
}
}
func TestTagImage(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", 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"] {
t.Errorf("TagImage: did not tag the image")
}
}
func TestTagImageNotFound(t *testing.T) {
server := DockerServer{}
server.buildMuxer()
recorder := httptest.NewRecorder()
request, _ := http.NewRequest("POST", "/images/tsuru/python/tag", nil)
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusNotFound {
t.Errorf("TagImage: wrong status. Want %d. Got %d.", http.StatusNotFound, recorder.Code)
}
}
func addContainers(server *DockerServer, n int) {
server.cMut.Lock()
defer server.cMut.Unlock()
for i := 0; i < n; i++ {
date := time.Now().Add(time.Duration((rand.Int() % (i + 1))) * time.Hour)
container := docker.Container{
Name: fmt.Sprintf("%x", rand.Int()%10000),
ID: fmt.Sprintf("%x", rand.Int()%10000),
Created: date,
Path: "ls",
@ -896,6 +940,36 @@ func TestRemoveImageByName(t *testing.T) {
}
}
func TestRemoveImageWithMultipleTags(t *testing.T) {
server := DockerServer{}
addImages(&server, 1, true)
server.buildMuxer()
imgID := server.images[0].ID
imgName := "docker/python-" + imgID
server.imgIDs["docker/python-wat"] = imgID
recorder := httptest.NewRecorder()
path := fmt.Sprintf("/images/%s", imgName)
request, _ := http.NewRequest("DELETE", path, nil)
server.ServeHTTP(recorder, request)
_, ok := server.imgIDs[imgName]
if ok {
t.Error("RemoveImage: did not remove image tag name.")
}
id, ok := server.imgIDs["docker/python-wat"]
if !ok {
t.Error("RemoveImage: removed the wrong tag name.")
}
if id != imgID {
t.Error("RemoveImage: disassociated the wrong ID from the tag")
}
if len(server.images) < 1 {
t.Fatal("RemoveImage: removed the image, but should keep it")
}
if server.images[0].ID != imgID {
t.Error("RemoveImage: changed the ID of the image!")
}
}
func TestPrepareFailure(t *testing.T) {
server := DockerServer{failures: make(map[string]string)}
server.buildMuxer()

View File

@ -13,9 +13,9 @@ import (
type stdType [8]byte
var (
stdin stdType = stdType{0: 0}
stdout stdType = stdType{0: 1}
stderr stdType = stdType{0: 2}
stdin = stdType{0: 0}
stdout = stdType{0: 1}
stderr = stdType{0: 2}
)
type stdWriter struct {