Merge pull request #589 from monnand/update-docker-client

update github.com/fsouza/go-dockerclient
This commit is contained in:
Daniel Smith 2014-07-23 18:00:24 -07:00
commit 65575953c0
13 changed files with 1003 additions and 146 deletions

View File

@ -239,7 +239,7 @@ func (kl *Kubelet) runContainer(pod *Pod, container *api.Container, podVolumes v
ExposedPorts: exposedPorts, ExposedPorts: exposedPorts,
Hostname: container.Name, Hostname: container.Name,
Image: container.Image, Image: container.Image,
Memory: int64(container.Memory), Memory: uint64(container.Memory),
CpuShares: int64(milliCPUToShares(container.CPU)), CpuShares: int64(milliCPUToShares(container.CPU)),
Volumes: volumes, Volumes: volumes,
WorkingDir: container.WorkingDir, WorkingDir: container.WorkingDir,

View File

@ -2,6 +2,7 @@ language: go
go: go:
- 1.1.2 - 1.1.2
- 1.2 - 1.2
- 1.3
- tip - tip
env: env:
- GOARCH=amd64 - GOARCH=amd64

View File

@ -10,6 +10,7 @@ Ed <edrocksit@gmail.com>
Eric Anderson <anderson@copperegg.com> Eric Anderson <anderson@copperegg.com>
Flavia Missi <flaviamissi@gmail.com> Flavia Missi <flaviamissi@gmail.com>
Francisco Souza <f@souza.cc> Francisco Souza <f@souza.cc>
Jari Kolehmainen <jari.kolehmainen@digia.com>
Jason Wilder <jwilder@litl.com> Jason Wilder <jwilder@litl.com>
Jean-Baptiste Dalido <jeanbaptiste@appgratis.com> Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
Jeff Mitchell <jeffrey.mitchell@gmail.com> Jeff Mitchell <jeffrey.mitchell@gmail.com>
@ -19,6 +20,7 @@ Omeid Matten <public@omeid.me>
Paul Morie <pmorie@gmail.com> Paul Morie <pmorie@gmail.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 <r.colton@modcloth.com>
Salvador Gironès <salvadorgirones@gmail.com> Salvador Gironès <salvadorgirones@gmail.com>
Simon Eskildsen <sirup@sirupsen.com> Simon Eskildsen <sirup@sirupsen.com>
Simon Menke <simon.menke@gmail.com> Simon Menke <simon.menke@gmail.com>

View File

@ -9,12 +9,6 @@ This package presents a client for the Docker remote 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.io/en/latest/reference/api/docker_remote_api/).
##Versioning
* Version 0.1 is compatible with Docker v0.7.1
* The master is compatible with Docker's master
## Example ## Example
package main package main

View File

@ -47,20 +47,17 @@ type ApiVersion []int
// <minor> and <patch> are integer numbers. // <minor> and <patch> are integer numbers.
func NewApiVersion(input string) (ApiVersion, error) { func NewApiVersion(input string) (ApiVersion, error) {
if !strings.Contains(input, ".") { if !strings.Contains(input, ".") {
return nil, fmt.Errorf("Unable to parse version '%s'", input) return nil, fmt.Errorf("Unable to parse version %q", input)
} }
arr := strings.Split(input, ".") arr := strings.Split(input, ".")
ret := make(ApiVersion, len(arr)) ret := make(ApiVersion, len(arr))
var err error var err error
for i, val := range arr { for i, val := range arr {
ret[i], err = strconv.Atoi(val) ret[i], err = strconv.Atoi(val)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("Unable to parse version %q: %q is not an integer", input, val)
} }
} }
return ret, nil return ret, nil
} }
@ -203,6 +200,21 @@ func parseApiVersionString(input string) (version uint16, err error) {
return version, nil return version, nil
} }
// Ping pings the docker server
//
// See http://goo.gl/stJENm for more details.
func (c *Client) Ping() error {
path := "/_ping"
body, status, err := c.do("GET", path, nil)
if err != nil {
return err
}
if status != http.StatusOK {
return newError(status, body)
}
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) body, status, err := c.do("GET", "/version", nil)
if err != nil { if err != nil {
@ -257,6 +269,7 @@ func (c *Client) do(method, path string, data interface{}) ([]byte, int, error)
if err != nil { if err != nil {
return nil, -1, err return nil, -1, err
} }
defer dial.Close()
clientconn := httputil.NewClientConn(dial, nil) clientconn := httputil.NewClientConn(dial, nil)
resp, err = clientconn.Do(req) resp, err = clientconn.Do(req)
if err != nil { if err != nil {
@ -283,11 +296,10 @@ func (c *Client) do(method, path string, data interface{}) ([]byte, int, error)
return body, resp.StatusCode, nil return body, resp.StatusCode, nil
} }
func (c *Client) stream(method, path string, headers map[string]string, in io.Reader, out io.Writer) error { func (c *Client) stream(method, path string, setRawTerminal bool, headers map[string]string, in io.Reader, stdout, stderr io.Writer) error {
if (method == "POST" || method == "PUT") && in == nil { if (method == "POST" || method == "PUT") && in == nil {
in = bytes.NewReader(nil) in = bytes.NewReader(nil)
} }
if path != "/version" && !c.SkipServerVersionCheck && c.expectedApiVersion == nil { if path != "/version" && !c.SkipServerVersionCheck && c.expectedApiVersion == nil {
err := c.checkApiVersion() err := c.checkApiVersion()
if err != nil { if err != nil {
@ -308,8 +320,11 @@ func (c *Client) stream(method, path string, headers map[string]string, in io.Re
var resp *http.Response var resp *http.Response
protocol := c.endpointURL.Scheme protocol := c.endpointURL.Scheme
address := c.endpointURL.Path address := c.endpointURL.Path
if out == nil { if stdout == nil {
out = ioutil.Discard stdout = ioutil.Discard
}
if stderr == nil {
stderr = ioutil.Discard
} }
if protocol == "unix" { if protocol == "unix" {
dial, err := net.Dial(protocol, address) dial, err := net.Dial(protocol, address)
@ -346,20 +361,23 @@ func (c *Client) stream(method, path string, headers map[string]string, in io.Re
return err return err
} }
if m.Stream != "" { if m.Stream != "" {
fmt.Fprint(out, m.Stream) fmt.Fprint(stdout, m.Stream)
} else if m.Progress != "" { } else if m.Progress != "" {
fmt.Fprintf(out, "%s %s\r", m.Status, m.Progress) fmt.Fprintf(stdout, "%s %s\r", m.Status, m.Progress)
} else if m.Error != "" { } else if m.Error != "" {
return errors.New(m.Error) return errors.New(m.Error)
} }
if m.Status != "" { if m.Status != "" {
fmt.Fprintln(out, m.Status) fmt.Fprintln(stdout, m.Status)
} }
} }
} else { } else {
if _, err := io.Copy(out, resp.Body); err != nil { if setRawTerminal {
return err _, err = io.Copy(stdout, resp.Body)
} else {
_, err = utils.StdCopy(stdout, stderr, resp.Body)
} }
return err
} }
return nil return nil
} }
@ -371,6 +389,12 @@ func (c *Client) hijack(method, path string, success chan struct{}, setRawTermin
return err return err
} }
} }
if stdout == nil {
stdout = ioutil.Discard
}
if stderr == nil {
stderr = ioutil.Discard
}
req, err := http.NewRequest(method, c.getURL(path), nil) req, err := http.NewRequest(method, c.getURL(path), nil)
if err != nil { if err != nil {
return err return err

View File

@ -148,6 +148,25 @@ func TestQueryString(t *testing.T) {
} }
} }
func TestNewApiVersionFailures(t *testing.T) {
var tests = []struct {
input string
expectedError string
}{
{"1-0", `Unable to parse version "1-0"`},
{"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)
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())
}
}
}
func TestApiVersions(t *testing.T) { func TestApiVersions(t *testing.T) {
var tests = []struct { var tests = []struct {
a string a string
@ -192,6 +211,41 @@ func TestApiVersions(t *testing.T) {
} }
} }
func TestPing(t *testing.T) {
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
client := newTestClient(fakeRT)
err := client.Ping()
if err != nil {
t.Fatal(err)
}
}
func TestPingFailing(t *testing.T) {
fakeRT := &FakeRoundTripper{message: "", status: http.StatusInternalServerError}
client := newTestClient(fakeRT)
err := client.Ping()
if err == nil {
t.Fatal("Expected non nil error, got nil")
}
expectedErrMsg := "API error (500): "
if err.Error() != expectedErrMsg {
t.Fatalf("Expected error to be %q, got: %q", expectedErrMsg, err.Error())
}
}
func TestPingFailingWrongStatus(t *testing.T) {
fakeRT := &FakeRoundTripper{message: "", status: http.StatusAccepted}
client := newTestClient(fakeRT)
err := client.Ping()
if err == nil {
t.Fatal("Expected non nil error, got nil")
}
expectedErrMsg := "API error (202): "
if err.Error() != expectedErrMsg {
t.Fatalf("Expected error to be %q, got: %q", expectedErrMsg, err.Error())
}
}
type FakeRoundTripper struct { type FakeRoundTripper struct {
message string message string
status int status int

View File

@ -13,7 +13,6 @@ import (
"net/url" "net/url"
"strconv" "strconv"
"strings" "strings"
"sync"
"time" "time"
) )
@ -87,22 +86,19 @@ func (p Port) Proto() string {
// State represents the state of a container. // State represents the state of a container.
type State struct { type State struct {
sync.RWMutex
Running bool Running bool
Paused bool
Pid int Pid int
ExitCode int ExitCode int
StartedAt time.Time StartedAt time.Time
FinishedAt time.Time FinishedAt time.Time
Ghost bool
} }
// String returns the string representation of a state. // String returns the string representation of a state.
func (s *State) String() string { func (s *State) String() string {
s.RLock()
defer s.RUnlock()
if s.Running { if s.Running {
if s.Ghost { if s.Paused {
return "Ghost" return "paused"
} }
return fmt.Sprintf("Up %s", time.Now().UTC().Sub(s.StartedAt)) return fmt.Sprintf("Up %s", time.Now().UTC().Sub(s.StartedAt))
} }
@ -162,8 +158,8 @@ type Config struct {
Hostname string Hostname string
Domainname string Domainname string
User string User string
Memory int64 Memory uint64
MemorySwap int64 MemorySwap uint64
CpuShares int64 CpuShares int64
AttachStdin bool AttachStdin bool
AttachStdout bool AttachStdout bool
@ -351,6 +347,36 @@ func (c *Client) RestartContainer(id string, timeout uint) error {
return nil return nil
} }
// PauseContainer pauses the given container.
//
// See http://goo.gl/AM5t42 for more details.
func (c *Client) PauseContainer(id string) error {
path := fmt.Sprintf("/containers/%s/pause", id)
_, status, err := c.do("POST", path, nil)
if status == http.StatusNotFound {
return &NoSuchContainer{ID: id}
}
if err != nil {
return err
}
return nil
}
// UnpauseContainer pauses the given container.
//
// See http://goo.gl/eBrNSL for more details.
func (c *Client) UnpauseContainer(id string) error {
path := fmt.Sprintf("/containers/%s/unpause", id)
_, status, err := c.do("POST", path, nil)
if status == http.StatusNotFound {
return &NoSuchContainer{ID: id}
}
if err != nil {
return err
}
return nil
}
// KillContainerOptions represents the set of options that can be used in a // KillContainerOptions represents the set of options that can be used in a
// call to KillContainer. // call to KillContainer.
type KillContainerOptions struct { type KillContainerOptions struct {
@ -542,10 +568,15 @@ func (c *Client) AttachToContainer(opts AttachToContainerOptions) error {
type LogsOptions struct { type LogsOptions struct {
Container string `qs:"-"` Container string `qs:"-"`
OutputStream io.Writer `qs:"-"` OutputStream io.Writer `qs:"-"`
ErrorStream io.Writer `qs:"-"`
Follow bool Follow bool
Stdout bool Stdout bool
Stderr bool Stderr bool
Timestamps bool Timestamps bool
Tail string
// Use raw terminal? Usually true when the container contains a TTY.
RawTerminal bool `qs:"-"`
} }
// Logs gets stdout and stderr logs from the specified container. // Logs gets stdout and stderr logs from the specified container.
@ -555,8 +586,11 @@ func (c *Client) Logs(opts LogsOptions) error {
if opts.Container == "" { if opts.Container == "" {
return &NoSuchContainer{ID: opts.Container} return &NoSuchContainer{ID: opts.Container}
} }
if opts.Tail == "" {
opts.Tail = "all"
}
path := "/containers/" + opts.Container + "/logs?" + queryString(opts) path := "/containers/" + opts.Container + "/logs?" + queryString(opts)
return c.stream("GET", path, nil, nil, opts.OutputStream) return c.stream("GET", path, opts.RawTerminal, nil, nil, opts.OutputStream, opts.ErrorStream)
} }
// ResizeContainerTTY resizes the terminal to the given height and width. // ResizeContainerTTY resizes the terminal to the given height and width.
@ -586,7 +620,7 @@ func (c *Client) ExportContainer(opts ExportContainerOptions) error {
return NoSuchContainer{ID: opts.ID} return NoSuchContainer{ID: opts.ID}
} }
url := fmt.Sprintf("/containers/%s/export", opts.ID) url := fmt.Sprintf("/containers/%s/export", opts.ID)
return c.stream("GET", url, nil, nil, opts.OutputStream) return c.stream("GET", url, true, nil, nil, opts.OutputStream, nil)
} }
// NoSuchContainer is the error returned when a given container does not exist. // NoSuchContainer is the error returned when a given container does not exist.

View File

@ -14,12 +14,32 @@ import (
"net/url" "net/url"
"os" "os"
"reflect" "reflect"
"regexp"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"testing" "testing"
"time"
) )
func TestStateString(t *testing.T) {
started := time.Now().Add(-3 * time.Hour)
var tests = []struct {
input State
expected string
}{
{State{Running: true, Paused: true}, "^paused$"},
{State{Running: true, StartedAt: started}, "^Up 3h.*$"},
{State{Running: false, ExitCode: 7}, "^Exit 7$"},
}
for _, tt := range tests {
re := regexp.MustCompile(tt.expected)
if got := tt.input.String(); !re.MatchString(got) {
t.Errorf("State.String(): wrong result. Want %q. Got %q.", tt.expected, got)
}
}
}
func TestListContainers(t *testing.T) { func TestListContainers(t *testing.T) {
jsonContainers := `[ jsonContainers := `[
{ {
@ -132,8 +152,8 @@ func TestInspectContainer(t *testing.T) {
"Config": { "Config": {
"Hostname": "4fa6e0f0c678", "Hostname": "4fa6e0f0c678",
"User": "", "User": "",
"Memory": 0, "Memory": 17179869184,
"MemorySwap": 0, "MemorySwap": 34359738368,
"AttachStdin": false, "AttachStdin": false,
"AttachStdout": true, "AttachStdout": true,
"AttachStderr": true, "AttachStderr": true,
@ -445,6 +465,60 @@ func TestRestartContainerNotFound(t *testing.T) {
} }
} }
func TestPauseContainer(t *testing.T) {
fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent}
client := newTestClient(fakeRT)
id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
err := client.PauseContainer(id)
if err != nil {
t.Fatal(err)
}
req := fakeRT.requests[0]
if req.Method != "POST" {
t.Errorf("PauseContainer(%q): wrong HTTP method. Want %q. Got %q.", id, "POST", req.Method)
}
expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/pause"))
if gotPath := req.URL.Path; gotPath != expectedURL.Path {
t.Errorf("PauseContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath)
}
}
func TestPauseContainerNotFound(t *testing.T) {
client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound})
err := client.PauseContainer("a2334")
expected := &NoSuchContainer{ID: "a2334"}
if !reflect.DeepEqual(err, expected) {
t.Errorf("PauseContainer: Wrong error returned. Want %#v. Got %#v.", expected, err)
}
}
func TestUnpauseContainer(t *testing.T) {
fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent}
client := newTestClient(fakeRT)
id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
err := client.UnpauseContainer(id)
if err != nil {
t.Fatal(err)
}
req := fakeRT.requests[0]
if req.Method != "POST" {
t.Errorf("PauseContainer(%q): wrong HTTP method. Want %q. Got %q.", id, "POST", req.Method)
}
expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/unpause"))
if gotPath := req.URL.Path; gotPath != expectedURL.Path {
t.Errorf("PauseContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath)
}
}
func TestUnpauseContainerNotFound(t *testing.T) {
client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound})
err := client.UnpauseContainer("a2334")
expected := &NoSuchContainer{ID: "a2334"}
if !reflect.DeepEqual(err, expected) {
t.Errorf("PauseContainer: Wrong error returned. Want %#v. Got %#v.", expected, err)
}
}
func TestKillContainer(t *testing.T) { func TestKillContainer(t *testing.T) {
fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent} fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent}
client := newTestClient(fakeRT) client := newTestClient(fakeRT)
@ -736,7 +810,7 @@ func TestAttachToContainer(t *testing.T) {
Stream: true, Stream: true,
RawTerminal: true, RawTerminal: true,
} }
var err = client.AttachToContainer(opts) err := client.AttachToContainer(opts)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -781,6 +855,63 @@ func TestAttachToContainerSentinel(t *testing.T) {
success <- <-success success <- <-success
} }
func TestAttachToContainerNilStdout(t *testing.T) {
var reader = strings.NewReader("send value")
var req http.Request
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte{1, 0, 0, 0, 0, 0, 0, 5})
w.Write([]byte("hello"))
req = *r
}))
defer server.Close()
client, _ := NewClient(server.URL)
client.SkipServerVersionCheck = true
var stderr bytes.Buffer
opts := AttachToContainerOptions{
Container: "a123456",
OutputStream: nil,
ErrorStream: &stderr,
InputStream: reader,
Stdin: true,
Stdout: true,
Stderr: true,
Stream: true,
RawTerminal: true,
}
err := client.AttachToContainer(opts)
if err != nil {
t.Fatal(err)
}
}
func TestAttachToContainerNilStderr(t *testing.T) {
var reader = strings.NewReader("send value")
var req http.Request
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte{1, 0, 0, 0, 0, 0, 0, 5})
w.Write([]byte("hello"))
req = *r
}))
defer server.Close()
client, _ := NewClient(server.URL)
client.SkipServerVersionCheck = true
var stdout bytes.Buffer
opts := AttachToContainerOptions{
Container: "a123456",
OutputStream: &stdout,
InputStream: reader,
Stdin: true,
Stdout: true,
Stderr: true,
Stream: true,
RawTerminal: true,
}
err := client.AttachToContainer(opts)
if err != nil {
t.Fatal(err)
}
}
func TestAttachToContainerRawTerminalFalse(t *testing.T) { func TestAttachToContainerRawTerminalFalse(t *testing.T) {
input := strings.NewReader("send value") input := strings.NewReader("send value")
var req http.Request var req http.Request
@ -838,6 +969,8 @@ func TestAttachToContainerWithoutContainer(t *testing.T) {
func TestLogs(t *testing.T) { func TestLogs(t *testing.T) {
var req http.Request var req http.Request
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
prefix := []byte{1, 0, 0, 0, 0, 0, 0, 19}
w.Write(prefix)
w.Write([]byte("something happened!")) w.Write([]byte("something happened!"))
req = *r req = *r
})) }))
@ -873,6 +1006,7 @@ func TestLogs(t *testing.T) {
"stdout": {"1"}, "stdout": {"1"},
"stderr": {"1"}, "stderr": {"1"},
"timestamps": {"1"}, "timestamps": {"1"},
"tail": {"all"},
} }
got := map[string][]string(req.URL.Query()) got := map[string][]string(req.URL.Query())
if !reflect.DeepEqual(got, expectedQs) { if !reflect.DeepEqual(got, expectedQs) {
@ -880,6 +1014,133 @@ func TestLogs(t *testing.T) {
} }
} }
func TestLogsNilStdoutDoesntFail(t *testing.T) {
var req http.Request
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
prefix := []byte{1, 0, 0, 0, 0, 0, 0, 19}
w.Write(prefix)
w.Write([]byte("something happened!"))
req = *r
}))
defer server.Close()
client, _ := NewClient(server.URL)
client.SkipServerVersionCheck = true
opts := LogsOptions{
Container: "a123456",
Follow: true,
Stdout: true,
Stderr: true,
Timestamps: true,
}
err := client.Logs(opts)
if err != nil {
t.Fatal(err)
}
}
func TestLogsNilStderrDoesntFail(t *testing.T) {
var req http.Request
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
prefix := []byte{2, 0, 0, 0, 0, 0, 0, 19}
w.Write(prefix)
w.Write([]byte("something happened!"))
req = *r
}))
defer server.Close()
client, _ := NewClient(server.URL)
client.SkipServerVersionCheck = true
opts := LogsOptions{
Container: "a123456",
Follow: true,
Stdout: true,
Stderr: true,
Timestamps: true,
}
err := client.Logs(opts)
if err != nil {
t.Fatal(err)
}
}
func TestLogsSpecifyingTail(t *testing.T) {
var req http.Request
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
prefix := []byte{1, 0, 0, 0, 0, 0, 0, 19}
w.Write(prefix)
w.Write([]byte("something happened!"))
req = *r
}))
defer server.Close()
client, _ := NewClient(server.URL)
client.SkipServerVersionCheck = true
var buf bytes.Buffer
opts := LogsOptions{
Container: "a123456",
OutputStream: &buf,
Follow: true,
Stdout: true,
Stderr: true,
Timestamps: true,
Tail: "100",
}
err := client.Logs(opts)
if err != nil {
t.Fatal(err)
}
expected := "something happened!"
if buf.String() != expected {
t.Errorf("Logs: wrong output. Want %q. Got %q.", expected, buf.String())
}
if req.Method != "GET" {
t.Errorf("Logs: wrong HTTP method. Want GET. Got %s.", req.Method)
}
u, _ := url.Parse(client.getURL("/containers/a123456/logs"))
if req.URL.Path != u.Path {
t.Errorf("AttachToContainer for logs: wrong HTTP path. Want %q. Got %q.", u.Path, req.URL.Path)
}
expectedQs := map[string][]string{
"follow": {"1"},
"stdout": {"1"},
"stderr": {"1"},
"timestamps": {"1"},
"tail": {"100"},
}
got := map[string][]string(req.URL.Query())
if !reflect.DeepEqual(got, expectedQs) {
t.Errorf("Logs: wrong query string. Want %#v. Got %#v.", expectedQs, got)
}
}
func TestLogsRawTerminal(t *testing.T) {
var req http.Request
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("something happened!"))
req = *r
}))
defer server.Close()
client, _ := NewClient(server.URL)
client.SkipServerVersionCheck = true
var buf bytes.Buffer
opts := LogsOptions{
Container: "a123456",
OutputStream: &buf,
Follow: true,
RawTerminal: true,
Stdout: true,
Stderr: true,
Timestamps: true,
Tail: "100",
}
err := client.Logs(opts)
if err != nil {
t.Fatal(err)
}
expected := "something happened!"
if buf.String() != expected {
t.Errorf("Logs: wrong output. Want %q. Got %q.", expected, buf.String())
}
}
func TestLogsNoContainer(t *testing.T) { func TestLogsNoContainer(t *testing.T) {
var client Client var client Client
err := client.Logs(LogsOptions{}) err := client.Logs(LogsOptions{})

View File

@ -154,6 +154,9 @@ type PushImageOptions struct {
// Name of the image // Name of the image
Name string Name string
// Tag of the image
Tag string
// Registry server to push the image // Registry server to push the image
Registry string Registry string
@ -187,7 +190,7 @@ func (c *Client) PushImage(opts PushImageOptions, auth AuthConfiguration) error
headers["X-Registry-Auth"] = base64.URLEncoding.EncodeToString(buf.Bytes()) headers["X-Registry-Auth"] = base64.URLEncoding.EncodeToString(buf.Bytes())
return c.stream("POST", path, headers, nil, opts.OutputStream) return c.stream("POST", path, true, headers, nil, opts.OutputStream, nil)
} }
// PullImageOptions present the set of options available for pulling an image // PullImageOptions present the set of options available for pulling an image
@ -219,7 +222,7 @@ func (c *Client) PullImage(opts PullImageOptions, auth AuthConfiguration) error
func (c *Client) createImage(qs string, headers map[string]string, in io.Reader, w io.Writer) error { func (c *Client) createImage(qs string, headers map[string]string, in io.Reader, w io.Writer) error {
path := "/images/create?" + qs path := "/images/create?" + qs
return c.stream("POST", path, headers, in, w) return c.stream("POST", path, true, headers, in, w, nil)
} }
// ImportImageOptions present the set of informations available for importing // ImportImageOptions present the set of informations available for importing
@ -286,13 +289,14 @@ func (c *Client) BuildImage(opts BuildImageOptions) error {
return ErrMissingRepo return ErrMissingRepo
} }
return c.stream("POST", fmt.Sprintf("/build?%s", return c.stream("POST", fmt.Sprintf("/build?%s",
queryString(&opts)), headers, opts.InputStream, opts.OutputStream) queryString(&opts)), true, headers, opts.InputStream, opts.OutputStream, nil)
} }
// TagImageOptions present the set of options to tag an image // TagImageOptions present the set of options to tag an image
type TagImageOptions struct { type TagImageOptions struct {
Repo string `qs:"repo"` Repo string
Force bool `qs:"force"` Tag string
Force bool
} }
// TagImage adds a tag to the image 'name' // TagImage adds a tag to the image 'name'

View File

@ -18,7 +18,6 @@ import (
mathrand "math/rand" mathrand "math/rand"
"net" "net"
"net/http" "net/http"
"reflect"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
@ -34,43 +33,55 @@ import (
// //
// For more details on the remote API, check http://goo.gl/yMI1S. // For more details on the remote API, check http://goo.gl/yMI1S.
type DockerServer struct { type DockerServer struct {
containers []*docker.Container containers []*docker.Container
cMut sync.RWMutex cMut sync.RWMutex
images []docker.Image images []docker.Image
iMut sync.RWMutex iMut sync.RWMutex
imgIDs map[string]string imgIDs map[string]string
listener net.Listener listener net.Listener
mux *mux.Router mux *mux.Router
hook func(*http.Request) hook func(*http.Request)
failures map[string]FailureSpec failures map[string]string
} customHandlers map[string]http.Handler
handlerMutex sync.RWMutex
// FailureSpec is used with PrepareFailure and describes in which situations cChan chan<- *docker.Container
// the request should fail. UrlRegex is mandatory, if a container id is sent
// on the request you can also specify the other properties.
type FailureSpec struct {
UrlRegex string
ContainerPath string
ContainerArgs []string
} }
// NewServer returns a new instance of the fake server, in standalone mode. Use // NewServer returns a new instance of the fake server, in standalone mode. Use
// the method URL to get the URL of the server. // the method URL to get the URL of the server.
// //
// It receives the bind address (use 127.0.0.1:0 for getting an available port // It receives the bind address (use 127.0.0.1:0 for getting an available port
// on the host) and a hook function, that will be called on every request. // on the host), a channel of containers and a hook function, that will be
func NewServer(bind string, hook func(*http.Request)) (*DockerServer, error) { // called on every request.
//
// The fake server will send containers in the channel whenever the container
// changes its state, via the HTTP API (i.e.: create, start and stop). This
// channel may be nil, which means that the server won't notify on state
// changes.
func NewServer(bind string, containerChan chan<- *docker.Container, hook func(*http.Request)) (*DockerServer, error) {
listener, err := net.Listen("tcp", bind) listener, err := net.Listen("tcp", bind)
if err != nil { if err != nil {
return nil, err return nil, err
} }
server := DockerServer{listener: listener, imgIDs: make(map[string]string), hook: hook, server := DockerServer{
failures: make(map[string]FailureSpec)} listener: listener,
imgIDs: make(map[string]string),
hook: hook,
failures: make(map[string]string),
customHandlers: make(map[string]http.Handler),
cChan: containerChan,
}
server.buildMuxer() server.buildMuxer()
go http.Serve(listener, &server) go http.Serve(listener, &server)
return &server, nil return &server, nil
} }
func (s *DockerServer) notify(container *docker.Container) {
if s.cChan != nil {
s.cChan <- container
}
}
func (s *DockerServer) buildMuxer() { func (s *DockerServer) buildMuxer() {
s.mux = mux.NewRouter() s.mux = mux.NewRouter()
s.mux.Path("/commit").Methods("POST").HandlerFunc(s.handlerWrapper(s.commitContainer)) s.mux.Path("/commit").Methods("POST").HandlerFunc(s.handlerWrapper(s.commitContainer))
@ -79,6 +90,8 @@ func (s *DockerServer) buildMuxer() {
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:.*}/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:.*}/stop").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:.*}/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:.*}/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:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeContainer))
@ -89,19 +102,45 @@ func (s *DockerServer) buildMuxer() {
s.mux.Path("/images/{name:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectImage)) 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:.*}/push").Methods("POST").HandlerFunc(s.handlerWrapper(s.pushImage))
s.mux.Path("/events").Methods("GET").HandlerFunc(s.listEvents) s.mux.Path("/events").Methods("GET").HandlerFunc(s.listEvents)
s.mux.Path("/_ping").Methods("GET").HandlerFunc(s.handlerWrapper(s.pingDocker))
} }
// PrepareFailure adds a new expected failure based on a FailureSpec // PrepareFailure adds a new expected failure based on a URL regexp it receives
// it receives an id for the failure and the spec. // an id for the failure.
func (s *DockerServer) PrepareFailure(id string, spec FailureSpec) { func (s *DockerServer) PrepareFailure(id string, urlRegexp string) {
s.failures[id] = spec s.failures[id] = urlRegexp
} }
// ResetFailure removes an expected failure identified by the 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)
} }
// CustomHandler registers a custom handler for a specific path.
//
// For example:
//
// server.CustomHandler("/containers/json", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// http.Error(w, "Something wrong is not right", http.StatusInternalServerError)
// }))
func (s *DockerServer) CustomHandler(path string, handler http.Handler) {
s.handlerMutex.Lock()
s.customHandlers[path] = handler
s.handlerMutex.Unlock()
}
// MutateContainer changes the state of a container, returning an error if the
// given id does not match to any container "running" in the server.
func (s *DockerServer) MutateContainer(id string, state docker.State) error {
for _, container := range s.containers {
if container.ID == id {
container.State = state
return nil
}
}
return errors.New("container not found")
}
// Stop stops the server. // Stop stops the server.
func (s *DockerServer) Stop() { func (s *DockerServer) Stop() {
if s.listener != nil { if s.listener != nil {
@ -119,6 +158,12 @@ func (s *DockerServer) URL() string {
// ServeHTTP handles HTTP requests sent to the server. // ServeHTTP handles HTTP requests sent to the server.
func (s *DockerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (s *DockerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.handlerMutex.RLock()
defer s.handlerMutex.RUnlock()
if handler, ok := s.customHandlers[r.URL.Path]; ok {
handler.ServeHTTP(w, r)
return
}
s.mux.ServeHTTP(w, r) s.mux.ServeHTTP(w, r)
if s.hook != nil { if s.hook != nil {
s.hook(r) s.hook(r)
@ -127,8 +172,8 @@ func (s *DockerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (s *DockerServer) handlerWrapper(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { func (s *DockerServer) handlerWrapper(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
for errorId, spec := range s.failures { for errorID, urlRegexp := range s.failures {
matched, err := regexp.MatchString(spec.UrlRegex, r.URL.Path) matched, err := regexp.MatchString(urlRegexp, r.URL.Path)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return return
@ -136,21 +181,7 @@ func (s *DockerServer) handlerWrapper(f func(http.ResponseWriter, *http.Request)
if !matched { if !matched {
continue continue
} }
id := mux.Vars(r)["id"] http.Error(w, errorID, http.StatusBadRequest)
if id != "" {
container, _, err := s.findContainer(id)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if spec.ContainerPath != "" && container.Path != spec.ContainerPath {
continue
}
if spec.ContainerArgs != nil && reflect.DeepEqual(container.Args, spec.ContainerArgs) {
continue
}
}
http.Error(w, errorId, http.StatusBadRequest)
return return
} }
f(w, r) f(w, r)
@ -272,6 +303,7 @@ func (s *DockerServer) createContainer(w http.ResponseWriter, r *http.Request) {
s.cMut.Lock() s.cMut.Lock()
s.containers = append(s.containers, &container) s.containers = append(s.containers, &container)
s.cMut.Unlock() s.cMut.Unlock()
s.notify(&container)
var c = struct{ ID string }{ID: container.ID} var c = struct{ ID string }{ID: container.ID}
json.NewEncoder(w).Encode(c) json.NewEncoder(w).Encode(c)
} }
@ -308,6 +340,7 @@ func (s *DockerServer) startContainer(w http.ResponseWriter, r *http.Request) {
return return
} }
container.State.Running = true container.State.Running = true
s.notify(container)
} }
func (s *DockerServer) stopContainer(w http.ResponseWriter, r *http.Request) { func (s *DockerServer) stopContainer(w http.ResponseWriter, r *http.Request) {
@ -325,6 +358,41 @@ func (s *DockerServer) stopContainer(w http.ResponseWriter, r *http.Request) {
} }
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)
container.State.Running = false container.State.Running = false
s.notify(container)
}
func (s *DockerServer) pauseContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
container, _, err := s.findContainer(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
s.cMut.Lock()
defer s.cMut.Unlock()
if container.State.Paused {
http.Error(w, "Container already paused", http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusNoContent)
container.State.Paused = true
}
func (s *DockerServer) unpauseContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
container, _, err := s.findContainer(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
s.cMut.Lock()
defer s.cMut.Unlock()
if !container.State.Paused {
http.Error(w, "Container not paused", http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusNoContent)
container.State.Paused = false
} }
func (s *DockerServer) attachContainer(w http.ResponseWriter, r *http.Request) { func (s *DockerServer) attachContainer(w http.ResponseWriter, r *http.Request) {
@ -361,7 +429,8 @@ func (s *DockerServer) waitContainer(w http.ResponseWriter, r *http.Request) {
} }
s.cMut.RUnlock() s.cMut.RUnlock()
} }
w.Write([]byte(`{"StatusCode":0}`)) result := map[string]int{"StatusCode": container.State.ExitCode}
json.NewEncoder(w).Encode(result)
} }
func (s *DockerServer) removeContainer(w http.ResponseWriter, r *http.Request) { func (s *DockerServer) removeContainer(w http.ResponseWriter, r *http.Request) {
@ -547,6 +616,10 @@ func (s *DockerServer) listEvents(w http.ResponseWriter, r *http.Request) {
} }
} }
func (s *DockerServer) pingDocker(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}
func (s *DockerServer) generateEvent() *docker.APIEvents { func (s *DockerServer) generateEvent() *docker.APIEvents {
var eventType string var eventType string
switch mathrand.Intn(4) { switch mathrand.Intn(4) {

View File

@ -20,7 +20,7 @@ import (
) )
func TestNewServer(t *testing.T) { func TestNewServer(t *testing.T) {
server, err := NewServer("127.0.0.1:0", nil) server, err := NewServer("127.0.0.1:0", nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -33,7 +33,7 @@ func TestNewServer(t *testing.T) {
} }
func TestServerStop(t *testing.T) { func TestServerStop(t *testing.T) {
server, err := NewServer("127.0.0.1:0", nil) server, err := NewServer("127.0.0.1:0", nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -50,7 +50,7 @@ func TestServerStopNoListener(t *testing.T) {
} }
func TestServerURL(t *testing.T) { func TestServerURL(t *testing.T) {
server, err := NewServer("127.0.0.1:0", nil) server, err := NewServer("127.0.0.1:0", nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -71,7 +71,7 @@ func TestServerURLNoListener(t *testing.T) {
func TestHandleWithHook(t *testing.T) { func TestHandleWithHook(t *testing.T) {
var called bool var called bool
server, _ := NewServer("127.0.0.1:0", func(*http.Request) { called = true }) server, _ := NewServer("127.0.0.1:0", nil, func(*http.Request) { called = true })
defer server.Stop() defer server.Stop()
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
request, _ := http.NewRequest("GET", "/containers/json?all=1", nil) request, _ := http.NewRequest("GET", "/containers/json?all=1", nil)
@ -81,6 +81,25 @@ func TestHandleWithHook(t *testing.T) {
} }
} }
func TestCustomHandler(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)
@ -158,6 +177,25 @@ func TestCreateContainer(t *testing.T) {
} }
} }
func TestCreateContainerWithNotifyChannel(t *testing.T) {
ch := make(chan *docker.Container, 1)
server := DockerServer{}
server.imgIDs = map[string]string{"base": "a1234"}
server.cChan = ch
server.buildMuxer()
recorder := httptest.NewRecorder()
body := `{"Hostname":"", "User":"", "Memory":0, "MemorySwap":0, "AttachStdin":false, "AttachStdout":true, "AttachStderr":true,
"PortSpecs":null, "Tty":false, "OpenStdin":false, "StdinOnce":false, "Env":null, "Cmd":["date"], "Image":"base", "Volumes":{}, "VolumesFrom":""}`
request, _ := http.NewRequest("POST", "/containers/create", strings.NewReader(body))
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusCreated {
t.Errorf("CreateContainer: wrong status. Want %d. Got %d.", http.StatusCreated, recorder.Code)
}
if notified := <-ch; notified != server.containers[0] {
t.Errorf("CreateContainer: did not notify the proper container. Want %q. Got %q.", server.containers[0].ID, notified.ID)
}
}
func TestCreateContainerInvalidBody(t *testing.T) { func TestCreateContainerInvalidBody(t *testing.T) {
server := DockerServer{} server := DockerServer{}
server.buildMuxer() server.buildMuxer()
@ -320,6 +358,25 @@ func TestStartContainer(t *testing.T) {
} }
} }
func TestStartContainerWithNotifyChannel(t *testing.T) {
ch := make(chan *docker.Container, 1)
server := DockerServer{}
server.cChan = ch
addContainers(&server, 1)
addContainers(&server, 1)
server.buildMuxer()
recorder := httptest.NewRecorder()
path := fmt.Sprintf("/containers/%s/start", server.containers[1].ID)
request, _ := http.NewRequest("POST", path, nil)
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Errorf("StartContainer: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code)
}
if notified := <-ch; notified != server.containers[1] {
t.Errorf("StartContainer: did not notify the proper container. Want %q. Got %q.", server.containers[1].ID, notified.ID)
}
}
func TestStartContainerNotFound(t *testing.T) { func TestStartContainerNotFound(t *testing.T) {
server := DockerServer{} server := DockerServer{}
server.buildMuxer() server.buildMuxer()
@ -363,6 +420,26 @@ func TestStopContainer(t *testing.T) {
} }
} }
func TestStopContainerWithNotifyChannel(t *testing.T) {
ch := make(chan *docker.Container, 1)
server := DockerServer{}
server.cChan = ch
addContainers(&server, 1)
addContainers(&server, 1)
server.containers[1].State.Running = true
server.buildMuxer()
recorder := httptest.NewRecorder()
path := fmt.Sprintf("/containers/%s/stop", server.containers[1].ID)
request, _ := http.NewRequest("POST", path, nil)
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusNoContent {
t.Errorf("StopContainer: wrong status code. Want %d. Got %d.", http.StatusNoContent, recorder.Code)
}
if notified := <-ch; notified != server.containers[1] {
t.Errorf("StopContainer: did not notify the proper container. Want %q. Got %q.", server.containers[1].ID, notified.ID)
}
}
func TestStopContainerNotFound(t *testing.T) { func TestStopContainerNotFound(t *testing.T) {
server := DockerServer{} server := DockerServer{}
server.buildMuxer() server.buildMuxer()
@ -388,6 +465,90 @@ func TestStopContainerNotRunning(t *testing.T) {
} }
} }
func TestPauseContainer(t *testing.T) {
server := DockerServer{}
addContainers(&server, 1)
server.buildMuxer()
recorder := httptest.NewRecorder()
path := fmt.Sprintf("/containers/%s/pause", server.containers[0].ID)
request, _ := http.NewRequest("POST", path, nil)
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusNoContent {
t.Errorf("PauseContainer: wrong status code. Want %d. Got %d.", http.StatusNoContent, recorder.Code)
}
if !server.containers[0].State.Paused {
t.Error("PauseContainer: did not pause the container")
}
}
func TestPauseContainerAlreadyPaused(t *testing.T) {
server := DockerServer{}
addContainers(&server, 1)
server.containers[0].State.Paused = true
server.buildMuxer()
recorder := httptest.NewRecorder()
path := fmt.Sprintf("/containers/%s/pause", server.containers[0].ID)
request, _ := http.NewRequest("POST", path, nil)
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusBadRequest {
t.Errorf("PauseContainer: wrong status code. Want %d. Got %d.", http.StatusBadRequest, recorder.Code)
}
}
func TestPauseContainerNotFound(t *testing.T) {
server := DockerServer{}
server.buildMuxer()
recorder := httptest.NewRecorder()
path := "/containers/abc123/pause"
request, _ := http.NewRequest("POST", path, nil)
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusNotFound {
t.Errorf("PauseContainer: wrong status code. Want %d. Got %d.", http.StatusNotFound, recorder.Code)
}
}
func TestUnpauseContainer(t *testing.T) {
server := DockerServer{}
addContainers(&server, 1)
server.containers[0].State.Paused = true
server.buildMuxer()
recorder := httptest.NewRecorder()
path := fmt.Sprintf("/containers/%s/unpause", server.containers[0].ID)
request, _ := http.NewRequest("POST", path, nil)
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusNoContent {
t.Errorf("UnpauseContainer: wrong status code. Want %d. Got %d.", http.StatusNoContent, recorder.Code)
}
if server.containers[0].State.Paused {
t.Error("UnpauseContainer: did not unpause the container")
}
}
func TestUnpauseContainerNotPaused(t *testing.T) {
server := DockerServer{}
addContainers(&server, 1)
server.buildMuxer()
recorder := httptest.NewRecorder()
path := fmt.Sprintf("/containers/%s/unpause", server.containers[0].ID)
request, _ := http.NewRequest("POST", path, nil)
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusBadRequest {
t.Errorf("UnpauseContainer: wrong status code. Want %d. Got %d.", http.StatusBadRequest, recorder.Code)
}
}
func TestUnpauseContainerNotFound(t *testing.T) {
server := DockerServer{}
server.buildMuxer()
recorder := httptest.NewRecorder()
path := "/containers/abc123/unpause"
request, _ := http.NewRequest("POST", path, nil)
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusNotFound {
t.Errorf("UnpauseContainer: wrong status code. Want %d. Got %d.", http.StatusNotFound, recorder.Code)
}
}
func TestWaitContainer(t *testing.T) { func TestWaitContainer(t *testing.T) {
server := DockerServer{} server := DockerServer{}
addContainers(&server, 1) addContainers(&server, 1)
@ -405,7 +566,25 @@ func TestWaitContainer(t *testing.T) {
if recorder.Code != http.StatusOK { if recorder.Code != http.StatusOK {
t.Errorf("WaitContainer: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) t.Errorf("WaitContainer: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
} }
expected := `{"StatusCode":0}` expected := `{"StatusCode":0}` + "\n"
if body := recorder.Body.String(); body != expected {
t.Errorf("WaitContainer: wrong body. Want %q. Got %q.", expected, body)
}
}
func TestWaitContainerStatus(t *testing.T) {
server := DockerServer{}
addContainers(&server, 1)
server.buildMuxer()
server.containers[0].State.ExitCode = 63
recorder := httptest.NewRecorder()
path := fmt.Sprintf("/containers/%s/wait", server.containers[0].ID)
request, _ := http.NewRequest("POST", path, nil)
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Errorf("WaitContainer: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
}
expected := `{"StatusCode":63}` + "\n"
if body := recorder.Body.String(); body != expected { if body := recorder.Body.String(); body != expected {
t.Errorf("WaitContainer: wrong body. Want %q. Got %q.", expected, body) t.Errorf("WaitContainer: wrong body. Want %q. Got %q.", expected, body)
} }
@ -659,70 +838,33 @@ func TestRemoveImageByName(t *testing.T) {
} }
func TestPrepareFailure(t *testing.T) { func TestPrepareFailure(t *testing.T) {
server := DockerServer{failures: make(map[string]FailureSpec)} server := DockerServer{failures: make(map[string]string)}
server.buildMuxer() server.buildMuxer()
errorId := "my_error" errorID := "my_error"
failure := FailureSpec{UrlRegex: "containers/json"} server.PrepareFailure(errorID, "containers/json")
server.PrepareFailure(errorId, failure)
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
request, _ := http.NewRequest("GET", "/containers/json?all=1", nil) request, _ := http.NewRequest("GET", "/containers/json?all=1", nil)
server.ServeHTTP(recorder, request) server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusBadRequest { if recorder.Code != http.StatusBadRequest {
t.Errorf("PrepareFailure: wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code) t.Errorf("PrepareFailure: wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code)
} }
if recorder.Body.String() != errorId+"\n" { if recorder.Body.String() != errorID+"\n" {
t.Errorf("PrepareFailure: wrong message. Want %s. Got %s.", errorId, recorder.Body.String()) t.Errorf("PrepareFailure: wrong message. Want %s. Got %s.", errorID, recorder.Body.String())
}
}
func TestPrepareFailureUsingContainerPath(t *testing.T) {
server := DockerServer{failures: make(map[string]FailureSpec)}
addContainers(&server, 1)
server.buildMuxer()
errorId := "my_path_error"
failure := FailureSpec{UrlRegex: "containers/.*?/start", ContainerPath: "ls"}
server.PrepareFailure(errorId, failure)
recorder := httptest.NewRecorder()
path := fmt.Sprintf("/containers/%s/start", server.containers[0].ID)
request, _ := http.NewRequest("POST", path, nil)
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusBadRequest {
t.Errorf("TestPrepareFailureUsingContainerPath: wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code)
}
if recorder.Body.String() != errorId+"\n" {
t.Errorf("TestPrepareFailureUsingContainerPath: wrong message. Want %s. Got %s.", errorId, recorder.Body.String())
}
}
func TestPrepareFailureUsingContainerPathWithWrongPath(t *testing.T) {
server := DockerServer{failures: make(map[string]FailureSpec)}
addContainers(&server, 1)
server.buildMuxer()
errorId := "my_path_error"
failure := FailureSpec{UrlRegex: "containers/.*?/start", ContainerPath: "xxx"}
server.PrepareFailure(errorId, failure)
recorder := httptest.NewRecorder()
path := fmt.Sprintf("/containers/%s/start", server.containers[0].ID)
request, _ := http.NewRequest("POST", path, nil)
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Errorf("StartContainer: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code)
} }
} }
func TestRemoveFailure(t *testing.T) { func TestRemoveFailure(t *testing.T) {
server := DockerServer{failures: make(map[string]FailureSpec)} server := DockerServer{failures: make(map[string]string)}
server.buildMuxer() server.buildMuxer()
errorId := "my_error" errorID := "my_error"
failure := FailureSpec{UrlRegex: "containers/json"} server.PrepareFailure(errorID, "containers/json")
server.PrepareFailure(errorId, failure)
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
request, _ := http.NewRequest("GET", "/containers/json?all=1", nil) request, _ := http.NewRequest("GET", "/containers/json?all=1", nil)
server.ServeHTTP(recorder, request) server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusBadRequest { if recorder.Code != http.StatusBadRequest {
t.Errorf("PrepareFailure: wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code) t.Errorf("PrepareFailure: wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code)
} }
server.ResetFailure(errorId) server.ResetFailure(errorID)
recorder = httptest.NewRecorder() recorder = httptest.NewRecorder()
request, _ = http.NewRequest("GET", "/containers/json?all=1", nil) request, _ = http.NewRequest("GET", "/containers/json?all=1", nil)
server.ServeHTTP(recorder, request) server.ServeHTTP(recorder, request)
@ -731,6 +873,34 @@ func TestRemoveFailure(t *testing.T) {
} }
} }
func TestMutateContainer(t *testing.T) {
server := DockerServer{failures: make(map[string]string)}
server.buildMuxer()
server.containers = append(server.containers, &docker.Container{ID: "id123"})
state := docker.State{Running: false, ExitCode: 1}
err := server.MutateContainer("id123", state)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(server.containers[0].State, state) {
t.Errorf("Wrong state after mutation.\nWant %#v.\nGot %#v.",
state, server.containers[0].State)
}
}
func TestMutateContainerNotFound(t *testing.T) {
server := DockerServer{failures: make(map[string]string)}
server.buildMuxer()
state := docker.State{Running: false, ExitCode: 1}
err := server.MutateContainer("id123", state)
if err == nil {
t.Error("Unexpected <nil> error")
}
if err.Error() != "container not found" {
t.Errorf("wrong error message. Want %q. Got %q.", "container not found", err)
}
}
func TestBuildImageWithContentTypeTar(t *testing.T) { func TestBuildImageWithContentTypeTar(t *testing.T) {
server := DockerServer{imgIDs: make(map[string]string)} server := DockerServer{imgIDs: make(map[string]string)}
imageName := "teste" imageName := "teste"
@ -762,3 +932,16 @@ func TestBuildImageWithRemoteDockerfile(t *testing.T) {
t.Errorf("BuildImage: image %s not builded", imageName) t.Errorf("BuildImage: image %s not builded", imageName)
} }
} }
func TestPing(t *testing.T) {
server := DockerServer{}
recorder := httptest.NewRecorder()
request, _ := http.NewRequest("GET", "/_ping", nil)
server.pingDocker(recorder, request)
if recorder.Body.String() != "" {
t.Errorf("Ping: Unexpected body: %s", recorder.Body.String())
}
if recorder.Code != http.StatusOK {
t.Errorf("Ping: Expected code %d, got: %d", http.StatusOK, recorder.Code)
}
}

View File

@ -87,9 +87,12 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error)
var nr2 int var nr2 int
nr2, er = src.Read(buf[nr:]) nr2, er = src.Read(buf[nr:])
if er == io.EOF { if er == io.EOF {
return written, nil if nr < StdWriterPrefixLen && nr2 < StdWriterPrefixLen {
} return written, nil
if er != nil { }
nr += nr2
break
} else if er != nil {
return 0, er return 0, er
} }
nr += nr2 nr += nr2
@ -117,7 +120,7 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error)
// Extend it if necessary. // Extend it if necessary.
if frameSize+StdWriterPrefixLen > bufLen { if frameSize+StdWriterPrefixLen > bufLen {
Debugf("Extending buffer cap.") Debugf("Extending buffer cap.")
buf = append(buf, make([]byte, frameSize-len(buf)+1)...) buf = append(buf, make([]byte, frameSize+StdWriterPrefixLen-len(buf)+1)...)
bufLen = len(buf) bufLen = len(buf)
} }
@ -126,9 +129,12 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error)
var nr2 int var nr2 int
nr2, er = src.Read(buf[nr:]) nr2, er = src.Read(buf[nr:])
if er == io.EOF { if er == io.EOF {
return written, nil if nr == 0 {
} return written, nil
if er != nil { }
nr += nr2
break
} else if er != nil {
Debugf("Error reading frame: %s", er) Debugf("Error reading frame: %s", er)
return 0, er return 0, er
} }
@ -136,7 +142,11 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error)
} }
// Write the retrieved frame (without header) // Write the retrieved frame (without header)
nw, ew = out.Write(buf[StdWriterPrefixLen : frameSize+StdWriterPrefixLen]) bound := frameSize + StdWriterPrefixLen
if bound > nr {
bound = nr
}
nw, ew = out.Write(buf[StdWriterPrefixLen:bound])
if nw > 0 { if nw > 0 {
written += int64(nw) written += int64(nw)
} }
@ -147,7 +157,7 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error)
// If the frame has not been fully written: error // If the frame has not been fully written: error
if nw != frameSize { if nw != frameSize {
Debugf("Error Short Write: (%d on %d)", nw, frameSize) Debugf("Error Short Write: (%d on %d)", nw, frameSize)
return 0, io.ErrShortWrite return written, io.ErrShortWrite
} }
// Move the rest of the buffer to the beginning // Move the rest of the buffer to the beginning

View File

@ -0,0 +1,217 @@
// 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 DOCKER-LICENSE file.
package utils
import (
"bytes"
"errors"
"io"
"strings"
"testing"
"testing/iotest"
)
type errorWriter struct {
}
func (errorWriter) Write([]byte) (int, error) {
return 0, errors.New("something went wrong")
}
func TestStdCopy(t *testing.T) {
var input, stdout, stderr bytes.Buffer
input.Write([]byte{2, 0, 0, 0, 0, 0, 0, 19})
input.Write([]byte("something happened!"))
input.Write([]byte{1, 0, 0, 0, 0, 0, 0, 12})
input.Write([]byte("just kidding"))
input.Write([]byte{0, 0, 0, 0, 0, 0, 0, 6})
input.Write([]byte("\nyeah!"))
n, err := StdCopy(&stdout, &stderr, &input)
if err != nil {
t.Fatal(err)
}
if expected := int64(19 + 12 + 6); n != expected {
t.Errorf("Wrong number of bytes. Want %d. Got %d.", expected, n)
}
if got := stderr.String(); got != "something happened!" {
t.Errorf("StdCopy: wrong stderr. Want %q. Got %q.", "something happened!", got)
}
if got := stdout.String(); got != "just kidding\nyeah!" {
t.Errorf("StdCopy: wrong stdout. Want %q. Got %q.", "just kidding\nyeah!", got)
}
}
func TestStdCopyStress(t *testing.T) {
var input, stdout, stderr bytes.Buffer
value := strings.Repeat("something ", 4096)
writer := NewStdWriter(&input, Stdout)
writer.Write([]byte(value))
n, err := StdCopy(&stdout, &stderr, &input)
if err != nil {
t.Fatal(err)
}
if n != 40960 {
t.Errorf("Wrong number of bytes. Want 40960. Got %d.", n)
}
if got := stderr.String(); got != "" {
t.Errorf("StdCopy: wrong stderr. Want empty string. Got %q", got)
}
if got := stdout.String(); got != value {
t.Errorf("StdCopy: wrong stdout. Want %q. Got %q", value, got)
}
}
func TestStdCopyInvalidStdHeader(t *testing.T) {
var input, stdout, stderr bytes.Buffer
input.Write([]byte{3, 0, 0, 0, 0, 0, 0, 19})
n, err := StdCopy(&stdout, &stderr, &input)
if n != 0 {
t.Errorf("StdCopy: wrong number of bytes. Want 0. Got %d", n)
}
if err != ErrInvalidStdHeader {
t.Errorf("StdCopy: wrong error. Want ErrInvalidStdHeader. Got %#v", err)
}
}
func TestStdCopyBigFrame(t *testing.T) {
var input, stdout, stderr bytes.Buffer
input.Write([]byte{2, 0, 0, 0, 0, 0, 0, 18})
input.Write([]byte("something happened!"))
n, err := StdCopy(&stdout, &stderr, &input)
if err != nil {
t.Fatal(err)
}
if expected := int64(18); n != expected {
t.Errorf("Wrong number of bytes. Want %d. Got %d.", expected, n)
}
if got := stderr.String(); got != "something happened" {
t.Errorf("StdCopy: wrong stderr. Want %q. Got %q.", "something happened", got)
}
if got := stdout.String(); got != "" {
t.Errorf("StdCopy: wrong stdout. Want %q. Got %q.", "", got)
}
}
func TestStdCopySmallFrame(t *testing.T) {
var input, stdout, stderr bytes.Buffer
input.Write([]byte{2, 0, 0, 0, 0, 0, 0, 20})
input.Write([]byte("something happened!"))
n, err := StdCopy(&stdout, &stderr, &input)
if err != io.ErrShortWrite {
t.Errorf("StdCopy: wrong error. Want ShortWrite. Got %#v", err)
}
if expected := int64(19); n != expected {
t.Errorf("Wrong number of bytes. Want %d. Got %d.", expected, n)
}
if got := stderr.String(); got != "something happened!" {
t.Errorf("StdCopy: wrong stderr. Want %q. Got %q.", "something happened", got)
}
if got := stdout.String(); got != "" {
t.Errorf("StdCopy: wrong stdout. Want %q. Got %q.", "", got)
}
}
func TestStdCopyEmpty(t *testing.T) {
var input, stdout, stderr bytes.Buffer
n, err := StdCopy(&stdout, &stderr, &input)
if err != nil {
t.Fatal(err)
}
if n != 0 {
t.Errorf("StdCopy: wrong number of bytes. Want 0. Got %d.", n)
}
}
func TestStdCopyCorruptedHeader(t *testing.T) {
var input, stdout, stderr bytes.Buffer
input.Write([]byte{2, 0, 0, 0, 0})
n, err := StdCopy(&stdout, &stderr, &input)
if err != nil {
t.Fatal(err)
}
if n != 0 {
t.Errorf("StdCopy: wrong number of bytes. Want 0. Got %d.", n)
}
}
func TestStdCopyTruncateWriter(t *testing.T) {
var input, stdout, stderr bytes.Buffer
input.Write([]byte{2, 0, 0, 0, 0, 0, 0, 19})
input.Write([]byte("something happened!"))
n, err := StdCopy(&stdout, iotest.TruncateWriter(&stderr, 7), &input)
if err != nil {
t.Fatal(err)
}
if expected := int64(19); n != expected {
t.Errorf("Wrong number of bytes. Want %d. Got %d.", expected, n)
}
if got := stderr.String(); got != "somethi" {
t.Errorf("StdCopy: wrong stderr. Want %q. Got %q.", "somethi", got)
}
if got := stdout.String(); got != "" {
t.Errorf("StdCopy: wrong stdout. Want %q. Got %q.", "", got)
}
}
func TestStdCopyHeaderOnly(t *testing.T) {
var input, stdout, stderr bytes.Buffer
input.Write([]byte{2, 0, 0, 0, 0, 0, 0, 19})
n, err := StdCopy(&stdout, iotest.TruncateWriter(&stderr, 7), &input)
if err != io.ErrShortWrite {
t.Errorf("StdCopy: wrong error. Want ShortWrite. Got %#v", err)
}
if n != 0 {
t.Errorf("Wrong number of bytes. Want 0. Got %d.", n)
}
if got := stderr.String(); got != "" {
t.Errorf("StdCopy: wrong stderr. Want %q. Got %q.", "", got)
}
if got := stdout.String(); got != "" {
t.Errorf("StdCopy: wrong stdout. Want %q. Got %q.", "", got)
}
}
func TestStdCopyDataErrReader(t *testing.T) {
var input, stdout, stderr bytes.Buffer
input.Write([]byte{2, 0, 0, 0, 0, 0, 0, 19})
input.Write([]byte("something happened!"))
n, err := StdCopy(&stdout, &stderr, iotest.DataErrReader(&input))
if err != nil {
t.Fatal(err)
}
if expected := int64(19); n != expected {
t.Errorf("Wrong number of bytes. Want %d. Got %d.", expected, n)
}
if got := stderr.String(); got != "something happened!" {
t.Errorf("StdCopy: wrong stderr. Want %q. Got %q.", "something happened!", got)
}
if got := stdout.String(); got != "" {
t.Errorf("StdCopy: wrong stdout. Want %q. Got %q.", "", got)
}
}
func TestStdCopyTimeoutReader(t *testing.T) {
var input, stdout, stderr bytes.Buffer
input.Write([]byte{2, 0, 0, 0, 0, 0, 0, 19})
input.Write([]byte("something happened!"))
_, err := StdCopy(&stdout, &stderr, iotest.TimeoutReader(&input))
if err != iotest.ErrTimeout {
t.Errorf("StdCopy: wrong error. Want ErrTimeout. Got %#v.", err)
}
}
func TestStdCopyWriteError(t *testing.T) {
var input bytes.Buffer
input.Write([]byte{2, 0, 0, 0, 0, 0, 0, 19})
input.Write([]byte("something happened!"))
var stdout, stderr errorWriter
n, err := StdCopy(stdout, stderr, &input)
if err.Error() != "something went wrong" {
t.Errorf("StdCopy: wrong error. Want %q. Got %q", "something went wrong", err)
}
if n != 0 {
t.Errorf("StdCopy: wrong number of bytes. Want 0. Got %d.", n)
}
}