Merge pull request #1805 from vishh/update_deps

Updating docker client to the latest version
This commit is contained in:
Brendan Burns 2014-10-15 11:27:09 -07:00
commit f006923a67
15 changed files with 723 additions and 49 deletions

4
Godeps/Godeps.json generated
View File

@ -60,8 +60,8 @@
},
{
"ImportPath": "github.com/fsouza/go-dockerclient",
"Comment": "0.2.1-241-g0dbb508",
"Rev": "0dbb508e94dd899a6743d035d8f249c7634d26da"
"Comment": "0.2.1-267-g15d2c6e",
"Rev": "15d2c6e3eb670c545d0af0604d7f9aff3871af04"
},
{
"ImportPath": "github.com/golang/glog",

View File

@ -1,7 +1,7 @@
language: go
go:
- 1.1.2
- 1.2
- 1.2.2
- 1.3.1
- tip
env:

View File

@ -5,9 +5,11 @@ Andreas Jaekle <andreas@jaekle.net>
Andrews Medina <andrewsmedina@gmail.com>
Andy Goldstein <andy.goldstein@redhat.com>
Ben McCann <benmccann.com>
Carlos Diaz-Padron <cpadron@mozilla.com>
Cezar Sa Espinola <cezar.sa@corp.globo.com>
Cheah Chu Yeow <chuyeow@gmail.com>
cheneydeng <cheneydeng@qq.com>
CMGS <ilskdw@gmail.com>
Daniel, Dao Quang Minh <dqminh89@gmail.com>
David Huie <dahuie@gmail.com>
Ed <edrocksit@gmail.com>
@ -29,6 +31,7 @@ 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>
Robert Williamson <williamson.robert@gmail.com>
Salvador Gironès <salvadorgirones@gmail.com>
Simon Eskildsen <sirup@sirupsen.com>
Simon Menke <simon.menke@gmail.com>

View File

@ -30,7 +30,6 @@ func main() {
fmt.Println("Size: ", img.Size)
fmt.Println("VirtualSize: ", img.VirtualSize)
fmt.Println("ParentId: ", img.ParentId)
fmt.Println("Repository: ", img.Repository)
}
}
```

View File

@ -16,7 +16,7 @@ const (
// Change represents a change in a container.
//
// See http://goo.gl/DpGyzK for more details.
// See http://goo.gl/QkW9sH for more details.
type Change struct {
Path string
Kind ChangeType

View File

@ -4,7 +4,7 @@
// Package docker provides a client for the Docker remote API.
//
// See http://goo.gl/mxyql for more details on the remote API.
// See http://goo.gl/G3plxW for more details on the remote API.
package docker
import (
@ -356,20 +356,30 @@ func (c *Client) stream(method, path string, setRawTerminal, rawJSONStream bool,
return nil
}
func (c *Client) hijack(method, path string, success chan struct{}, setRawTerminal bool, in io.Reader, stderr, stdout io.Writer) error {
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 err != nil {
return err
}
}
var params io.Reader
if data != nil {
buf, err := json.Marshal(data)
if err != nil {
return err
}
params = bytes.NewBuffer(buf)
}
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), params)
if err != nil {
return err
}

View File

@ -18,7 +18,7 @@ import (
// ListContainersOptions specify parameters to the ListContainers function.
//
// See http://goo.gl/QpCnDN for more details.
// See http://goo.gl/XqtcyU for more details.
type ListContainersOptions struct {
All bool
Size bool
@ -51,7 +51,7 @@ type APIContainers struct {
// ListContainers returns a slice of containers matching the given criteria.
//
// See http://goo.gl/QpCnDN for more details.
// See http://goo.gl/XqtcyU for more details.
func (c *Client) ListContainers(opts ListContainersOptions) ([]APIContainers, error) {
path := "/containers/json?" + queryString(opts)
body, _, err := c.do("GET", path, nil)
@ -161,6 +161,7 @@ type Config struct {
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"`
AttachStdin bool `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty"`
AttachStdout bool `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty"`
AttachStderr bool `json:"AttachStderr,omitempty" yaml:"AttachStderr,omitempty"`
@ -208,7 +209,7 @@ type Container struct {
// InspectContainer returns information about a container by its ID.
//
// See http://goo.gl/2o52Sx for more details.
// See http://goo.gl/CxVuJ5 for more details.
func (c *Client) InspectContainer(id string) (*Container, error) {
path := "/containers/" + id + "/json"
body, status, err := c.do("GET", path, nil)
@ -228,7 +229,7 @@ func (c *Client) InspectContainer(id string) (*Container, error) {
// ContainerChanges returns changes in the filesystem of the given container.
//
// See http://goo.gl/DpGyzK for more details.
// See http://goo.gl/QkW9sH for more details.
func (c *Client) ContainerChanges(id string) ([]Change, error) {
path := "/containers/" + id + "/changes"
body, status, err := c.do("GET", path, nil)
@ -248,7 +249,7 @@ func (c *Client) ContainerChanges(id string) ([]Change, error) {
// CreateContainerOptions specify parameters to the CreateContainer function.
//
// See http://goo.gl/WPPYtB for more details.
// See http://goo.gl/mErxNp for more details.
type CreateContainerOptions struct {
Name string
Config *Config `qs:"-"`
@ -257,7 +258,7 @@ type CreateContainerOptions struct {
// CreateContainer creates a new container, returning the container instance,
// or an error in case of failure.
//
// See http://goo.gl/tjihUc for more details.
// 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)
@ -333,7 +334,7 @@ type HostConfig struct {
// StartContainer starts a container, returning an error in case of failure.
//
// See http://goo.gl/y5GZlE for more details.
// See http://goo.gl/iM5GYs for more details.
func (c *Client) StartContainer(id string, hostConfig *HostConfig) error {
if hostConfig == nil {
hostConfig = &HostConfig{}
@ -355,7 +356,7 @@ func (c *Client) StartContainer(id string, hostConfig *HostConfig) error {
// StopContainer stops a container, killing it after the given timeout (in
// seconds).
//
// See http://goo.gl/X2mj8t for more details.
// See http://goo.gl/EbcpXt for more details.
func (c *Client) StopContainer(id string, timeout uint) error {
path := fmt.Sprintf("/containers/%s/stop?t=%d", id, timeout)
_, status, err := c.do("POST", path, nil)
@ -374,7 +375,7 @@ func (c *Client) StopContainer(id string, timeout uint) error {
// RestartContainer stops a container, killing it after the given timeout (in
// seconds), during the stop process.
//
// See http://goo.gl/zms73Z for more details.
// See http://goo.gl/VOzR2n for more details.
func (c *Client) RestartContainer(id string, timeout uint) error {
path := fmt.Sprintf("/containers/%s/restart?t=%d", id, timeout)
_, status, err := c.do("POST", path, nil)
@ -417,8 +418,43 @@ func (c *Client) UnpauseContainer(id string) error {
return nil
}
// TopResult represents the list of processes running in a container, as
// returned by /containers/<id>/top.
//
// See http://goo.gl/qu4gse for more details.
type TopResult struct {
Titles []string
Processes [][]string
}
// TopContainer returns processes running inside a container
//
// See http://goo.gl/qu4gse for more details.
func (c *Client) TopContainer(id string, psArgs string) (TopResult, error) {
var args string
var result TopResult
if psArgs != "" {
args = fmt.Sprintf("?ps_args=%s", psArgs)
}
path := fmt.Sprintf("/containers/%s/top%s", id, args)
body, status, err := c.do("GET", path, nil)
if status == http.StatusNotFound {
return result, &NoSuchContainer{ID: id}
}
if err != nil {
return result, err
}
err = json.Unmarshal(body, &result)
if err != nil {
return result, err
}
return result, nil
}
// KillContainerOptions represents the set of options that can be used in a
// call to KillContainer.
//
// See http://goo.gl/TFkECx for more details.
type KillContainerOptions struct {
// The ID of the container.
ID string `qs:"-"`
@ -430,7 +466,7 @@ type KillContainerOptions struct {
// KillContainer kills a container, returning an error in case of failure.
//
// See http://goo.gl/DPbbBy for more details.
// See http://goo.gl/TFkECx for more details.
func (c *Client) KillContainer(opts KillContainerOptions) error {
path := "/containers/" + opts.ID + "/kill" + "?" + queryString(opts)
_, status, err := c.do("POST", path, nil)
@ -444,6 +480,8 @@ func (c *Client) KillContainer(opts KillContainerOptions) error {
}
// RemoveContainerOptions encapsulates options to remove a container.
//
// See http://goo.gl/ZB83ji for more details.
type RemoveContainerOptions struct {
// The ID of the container.
ID string `qs:"-"`
@ -459,7 +497,7 @@ type RemoveContainerOptions struct {
// RemoveContainer removes a container, returning an error in case of failure.
//
// See http://goo.gl/PBvGdU for more details.
// See http://goo.gl/ZB83ji for more details.
func (c *Client) RemoveContainer(opts RemoveContainerOptions) error {
path := "/containers/" + opts.ID + "?" + queryString(opts)
_, status, err := c.do("DELETE", path, nil)
@ -475,7 +513,7 @@ func (c *Client) RemoveContainer(opts RemoveContainerOptions) error {
// CopyFromContainerOptions is the set of options that can be used when copying
// files or folders from a container.
//
// See http://goo.gl/mnxRMl for more details.
// See http://goo.gl/rINMlw for more details.
type CopyFromContainerOptions struct {
OutputStream io.Writer `json:"-"`
Container string `json:"-"`
@ -485,7 +523,7 @@ type CopyFromContainerOptions struct {
// CopyFromContainer copy files or folders from a container, using a given
// resource.
//
// See http://goo.gl/mnxRMl for more details.
// See http://goo.gl/rINMlw for more details.
func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error {
if opts.Container == "" {
return &NoSuchContainer{ID: opts.Container}
@ -505,7 +543,7 @@ func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error {
// WaitContainer blocks until the given container stops, return the exit code
// of the container status.
//
// See http://goo.gl/gnHJL2 for more details.
// See http://goo.gl/J88DHU for more details.
func (c *Client) WaitContainer(id string) (int, error) {
body, status, err := c.do("POST", "/containers/"+id+"/wait", nil)
if status == http.StatusNotFound {
@ -524,7 +562,7 @@ func (c *Client) WaitContainer(id string) (int, error) {
// CommitContainerOptions aggregates parameters to the CommitContainer method.
//
// See http://goo.gl/628gxm for more details.
// See http://goo.gl/Jn8pe8 for more details.
type CommitContainerOptions struct {
Container string
Repository string `qs:"repo"`
@ -536,7 +574,7 @@ type CommitContainerOptions struct {
// CommitContainer creates a new image from a container's changes.
//
// See http://goo.gl/628gxm for more details.
// See http://goo.gl/Jn8pe8 for more details.
func (c *Client) CommitContainer(opts CommitContainerOptions) (*Image, error) {
path := "/commit?" + queryString(opts)
body, status, err := c.do("POST", path, opts.Run)
@ -557,7 +595,7 @@ func (c *Client) CommitContainer(opts CommitContainerOptions) (*Image, error) {
// AttachToContainerOptions is the set of options that can be used when
// attaching to a container.
//
// See http://goo.gl/oPzcqH for more details.
// See http://goo.gl/RRAhws for more details.
type AttachToContainerOptions struct {
Container string `qs:"-"`
InputStream io.Reader `qs:"-"`
@ -592,13 +630,13 @@ type AttachToContainerOptions struct {
// AttachToContainer attaches to a container, using the given options.
//
// See http://goo.gl/oPzcqH for more details.
// See http://goo.gl/RRAhws for more details.
func (c *Client) AttachToContainer(opts AttachToContainerOptions) error {
if opts.Container == "" {
return &NoSuchContainer{ID: opts.Container}
}
path := "/containers/" + opts.Container + "/attach?" + queryString(opts)
return c.hijack("POST", path, opts.Success, opts.RawTerminal, opts.InputStream, opts.ErrorStream, opts.OutputStream)
return c.hijack("POST", path, opts.Success, opts.RawTerminal, opts.InputStream, opts.ErrorStream, opts.OutputStream, nil)
}
// LogsOptions represents the set of options used when getting logs from a
@ -645,7 +683,7 @@ func (c *Client) ResizeContainerTTY(id string, height, width int) error {
// ExportContainerOptions is the set of parameters to the ExportContainer
// method.
//
// See http://goo.gl/Lqk0FZ for more details.
// See http://goo.gl/hnzE62 for more details.
type ExportContainerOptions struct {
ID string
OutputStream io.Writer
@ -654,7 +692,7 @@ type ExportContainerOptions struct {
// ExportContainer export the contents of container id as tar archive
// and prints the exported contents to stdout.
//
// See http://goo.gl/Lqk0FZ for more details.
// See http://goo.gl/hnzE62 for more details.
func (c *Client) ExportContainer(opts ExportContainerOptions) error {
if opts.ID == "" {
return &NoSuchContainer{ID: opts.ID}

View File

@ -1414,3 +1414,82 @@ func TestNeverRestart(t *testing.T) {
t.Errorf("NeverRestart(): wrong MaximumRetryCount. Want 0. Got %d", policy.MaximumRetryCount)
}
}
func TestTopContainer(t *testing.T) {
jsonTop := `{
"Processes": [
[
"ubuntu",
"3087",
"815",
"0",
"01:44",
"?",
"00:00:00",
"cmd1"
],
[
"root",
"3158",
"3087",
"0",
"01:44",
"?",
"00:00:01",
"cmd2"
]
],
"Titles": [
"UID",
"PID",
"PPID",
"C",
"STIME",
"TTY",
"TIME",
"CMD"
]
}`
var expected TopResult
err := json.Unmarshal([]byte(jsonTop), &expected)
if err != nil {
t.Fatal(err)
}
id := "4fa6e0f0"
fakeRT := &FakeRoundTripper{message: jsonTop, status: http.StatusOK}
client := newTestClient(fakeRT)
processes, err := client.TopContainer(id, "")
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(processes, expected) {
t.Errorf("TopContainer: Expected %#v. Got %#v.", expected, processes)
}
if len(processes.Processes) != 2 || len(processes.Processes[0]) != 8 ||
processes.Processes[0][7] != "cmd1" {
t.Errorf("TopContainer: Process list to include cmd1. Got %#v.", expected, processes)
}
expectedURI := "/containers/" + id + "/top"
if !strings.HasSuffix(fakeRT.requests[0].URL.String(), expectedURI) {
t.Errorf("TopContainer: Expected URI to have %q. Got %q.", expectedURI, fakeRT.requests[0].URL.String())
}
}
func TestTopContainerNotFound(t *testing.T) {
client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound})
_, err := client.TopContainer("abef348", "")
expected := &NoSuchContainer{ID: "abef348"}
if !reflect.DeepEqual(err, expected) {
t.Errorf("StopContainer: Wrong error returned. Want %#v. Got %#v.", expected, err)
}
}
func TestTopContainerWithPsArgs(t *testing.T) {
fakeRT := &FakeRoundTripper{message: "no such container", status: http.StatusNotFound}
client := newTestClient(fakeRT)
client.TopContainer("abef348", "aux")
expectedURI := "/containers/abef348/top?ps_args=aux"
if !strings.HasSuffix(fakeRT.requests[0].URL.String(), expectedURI) {
t.Errorf("TopContainer: Expected URI to have %q. Got %q.", expectedURI, fakeRT.requests[0].URL.String())
}
}

View File

@ -0,0 +1,126 @@
// 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.
// Docs can currently be found at https://github.com/docker/docker/blob/master/docs/sources/reference/api/docker_remote_api_v1.15.md#exec-create
package docker
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
)
// CreateExecOptions specify parameters to the CreateExecContainer function.
//
// See http://goo.gl/8izrzI for more details
type CreateExecOptions struct {
AttachStdin bool `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty"`
AttachStdout bool `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty"`
AttachStderr bool `json:"AttachStderr,omitempty" yaml:"AttachStderr,omitempty"`
Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty"`
Cmd []string `json:"Cmd,omitempty" yaml:"Cmd,omitempty"`
Container string `json:"Container,omitempty" yaml:"Container,omitempty"`
}
// StartExecOptions specify parameters to the StartExecContainer function.
//
// See http://goo.gl/JW8Lxl for more details
type StartExecOptions struct {
Detach bool `json:"Detach,omitempty" yaml:"Detach,omitempty"`
Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty"`
InputStream io.Reader `qs:"-"`
OutputStream io.Writer `qs:"-"`
ErrorStream io.Writer `qs:"-"`
// Use raw terminal? Usually true when the container contains a TTY.
RawTerminal bool `qs:"-"`
// If set, after a successful connect, a sentinel will be sent and then the
// client will block on receive before continuing.
//
// It must be an unbuffered channel. Using a buffered channel can lead
// to unexpected behavior.
Success chan struct{} `json:"-"`
}
type Exec struct {
Id string `json:"Id,omitempty" yaml:"Id,omitempty"`
}
// CreateExec sets up an exec instance in a running container `id`, returning the exec
// instance, or an error in case of failure.
//
// See http://goo.gl/8izrzI for more details
func (c *Client) CreateExec(opts CreateExecOptions) (*Exec, error) {
path := fmt.Sprintf("/containers/%s/exec", opts.Container)
body, status, err := c.do("POST", path, opts)
if status == http.StatusNotFound {
return nil, &NoSuchContainer{ID: opts.Container}
}
if err != nil {
return nil, err
}
var exec Exec
err = json.Unmarshal(body, &exec)
if err != nil {
return nil, err
}
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.
//
// See http://goo.gl/JW8Lxl for more details
func (c *Client) StartExec(id string, opts StartExecOptions) error {
if id == "" {
return &NoSuchExec{ID: id}
}
path := fmt.Sprintf("/exec/%s/start", id)
if opts.Detach {
_, status, err := c.do("POST", path, opts)
if status == http.StatusNotFound {
return &NoSuchExec{ID: id}
}
if err != nil {
return err
}
return nil
}
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.
//
// See http://goo.gl/YDSx1f for more details
func (c *Client) ResizeExecTTY(id string, height, width int) error {
params := make(url.Values)
params.Set("h", strconv.Itoa(height))
params.Set("w", strconv.Itoa(width))
path := fmt.Sprintf("/exec/%s/resize?%s", id, params.Encode())
_, _, err := c.do("POST", path, nil)
return err
}
// NoSuchExec is the error returned when a given exec instance does not exist.
type NoSuchExec struct {
ID string
}
func (err *NoSuchExec) Error() string {
return "No such exec instance: " + err.ID
}

View File

@ -0,0 +1,128 @@
// 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 (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
)
func TestExecCreate(t *testing.T) {
jsonContainer := `{"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"}`
var expected struct{ Id string }
err := json.Unmarshal([]byte(jsonContainer), &expected)
if err != nil {
t.Fatal(err)
}
fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK}
client := newTestClient(fakeRT)
config := CreateExecOptions{
Container: "test",
AttachStdin: true,
AttachStdout: true,
AttachStderr: false,
Tty: false,
Cmd: []string{"touch", "/tmp/file"},
}
execObj, err := client.CreateExec(config)
if err != nil {
t.Fatal(err)
}
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" {
t.Errorf("ExecCreate: wrong HTTP method. Want %q. Got %q.", "POST", req.Method)
}
expectedURL, _ := url.Parse(client.getURL("/containers/test/exec"))
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 }
err = json.NewDecoder(req.Body).Decode(&gotBody)
if err != nil {
t.Fatal(err)
}
}
func TestExecStartDetached(t *testing.T) {
execId := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
fakeRT := &FakeRoundTripper{status: http.StatusOK}
client := newTestClient(fakeRT)
config := StartExecOptions{
Detach: true,
}
err := client.StartExec(execId, config)
if err != nil {
t.Fatal(err)
}
req := fakeRT.requests[0]
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"))
if gotPath := req.URL.Path; gotPath != expectedURL.Path {
t.Errorf("ExecCreate: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath)
}
t.Log(req.Body)
var gotBody struct{ Detach bool }
err = json.NewDecoder(req.Body).Decode(&gotBody)
if err != nil {
t.Fatal(err)
}
if !gotBody.Detach {
t.Fatal("Expected Detach in StartExecOptions to be true")
}
}
func TestExecStartAndAttach(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, stderr bytes.Buffer
success := make(chan struct{})
execId := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
opts := StartExecOptions{
OutputStream: &stdout,
ErrorStream: &stderr,
InputStream: reader,
RawTerminal: true,
Success: success,
}
go client.StartExec(execId, opts)
<-success
}
func TestExecResize(t *testing.T) {
execId := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
fakeRT := &FakeRoundTripper{status: http.StatusOK}
client := newTestClient(fakeRT)
err := client.ResizeExecTTY(execId, 10, 20)
if err != nil {
t.Fatal(err)
}
req := fakeRT.requests[0]
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"))
if gotPath := req.URL.RequestURI(); gotPath != expectedURL.RequestURI() {
t.Errorf("ExecCreate: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath)
}
}

View File

@ -42,6 +42,16 @@ type Image struct {
Size int64 `json:"Size,omitempty" yaml:"Size,omitempty"`
}
// ImageHistory represent a layer in an image's history returned by the
// ImageHistory call.
type ImageHistory struct {
ID string `json:"Id" yaml:"Id"`
Tags []string `json:"Tags,omitempty" yaml:"Tags,omitempty"`
Created int64 `json:"Created,omitempty" yaml:"Created,omitempty"`
CreatedBy string `json:"CreatedBy,omitempty" yaml:"CreatedBy,omitempty"`
Size int64 `json:"Size,omitempty" yaml:"Size,omitempty"`
}
type ImagePre012 struct {
ID string `json:"id"`
Parent string `json:"parent,omitempty"`
@ -71,7 +81,7 @@ var (
// ListImages returns the list of available images in the server.
//
// See http://goo.gl/dkMrwP for more details.
// See http://goo.gl/VmcR6v for more details.
func (c *Client) ListImages(all bool) ([]APIImages, error) {
path := "/images/json?all="
if all {
@ -91,9 +101,28 @@ func (c *Client) ListImages(all bool) ([]APIImages, error) {
return images, nil
}
// ImageHistory returns the history of the image by its name or ID.
//
// See http://goo.gl/2oJmNs for more details.
func (c *Client) ImageHistory(name string) ([]ImageHistory, error) {
body, status, err := c.do("GET", "/images/"+name+"/history", nil)
if status == http.StatusNotFound {
return nil, ErrNoSuchImage
}
if err != nil {
return nil, err
}
var history []ImageHistory
err = json.Unmarshal(body, &history)
if err != nil {
return nil, err
}
return history, nil
}
// RemoveImage removes an image by its name or ID.
//
// See http://goo.gl/7hjHHy for more details.
// See http://goo.gl/znj0wM for more details.
func (c *Client) RemoveImage(name string) error {
_, status, err := c.do("DELETE", "/images/"+name, nil)
if status == http.StatusNotFound {
@ -104,7 +133,7 @@ func (c *Client) RemoveImage(name string) error {
// InspectImage returns an image by its name or ID.
//
// See http://goo.gl/pHEbma for more details.
// See http://goo.gl/Q112NY for more details.
func (c *Client) InspectImage(name string) (*Image, error) {
body, status, err := c.do("GET", "/images/"+name+"/json", nil)
if status == http.StatusNotFound {
@ -147,7 +176,7 @@ func (c *Client) InspectImage(name string) (*Image, error) {
// PushImageOptions represents options to use in the PushImage method.
//
// See http://goo.gl/GBmyhc for more details.
// See http://goo.gl/pN8A3P for more details.
type PushImageOptions struct {
// Name of the image
Name string
@ -158,7 +187,8 @@ type PushImageOptions struct {
// Registry server to push the image
Registry string
OutputStream io.Writer `qs:"-"`
OutputStream io.Writer `qs:"-"`
RawJSONStream bool `qs:"-"`
}
// AuthConfiguration represents authentication options to use in the PushImage
@ -174,7 +204,7 @@ type AuthConfiguration struct {
// An empty instance of AuthConfiguration may be used for unauthenticated
// pushes.
//
// See http://goo.gl/GBmyhc for more details.
// See http://goo.gl/pN8A3P for more details.
func (c *Client) PushImage(opts PushImageOptions, auth AuthConfiguration) error {
if opts.Name == "" {
return ErrNoSuchImage
@ -188,13 +218,13 @@ func (c *Client) PushImage(opts PushImageOptions, auth AuthConfiguration) error
headers["X-Registry-Auth"] = base64.URLEncoding.EncodeToString(buf.Bytes())
return c.stream("POST", path, true, false, headers, nil, opts.OutputStream, nil)
return c.stream("POST", path, true, opts.RawJSONStream, headers, nil, opts.OutputStream, nil)
}
// PullImageOptions present the set of options available for pulling an image
// from a registry.
//
// See http://goo.gl/PhBKnS for more details.
// See http://goo.gl/ACyYNS for more details.
type PullImageOptions struct {
Repository string `qs:"fromImage"`
Registry string
@ -205,7 +235,7 @@ type PullImageOptions struct {
// PullImage pulls an image from a remote registry, logging progress to w.
//
// See http://goo.gl/PhBKnS for more details.
// See http://goo.gl/ACyYNS for more details.
func (c *Client) PullImage(opts PullImageOptions, auth AuthConfiguration) error {
if opts.Repository == "" {
return ErrNoSuchImage
@ -288,9 +318,11 @@ func (c *Client) ImportImage(opts ImportImageOptions) error {
return c.createImage(queryString(&opts), nil, opts.InputStream, opts.OutputStream, false)
}
// BuildImageOptions present the set of informations available for building
// an image from a tarfile with a Dockerfile in it,the details about Dockerfile
// see http://docs.docker.io/en/latest/reference/builder/
// BuildImageOptions present the set of informations available for building an
// image from a tarfile with a Dockerfile in it.
//
// For more details about the Docker building process, see
// http://goo.gl/tlPXPu.
type BuildImageOptions struct {
Name string `qs:"t"`
NoCache bool `qs:"nocache"`
@ -299,11 +331,14 @@ type BuildImageOptions struct {
ForceRmTmpContainer bool `qs:"forcerm"`
InputStream io.Reader `qs:"-"`
OutputStream io.Writer `qs:"-"`
RawJSONStream bool `qs:"-"`
Remote string `qs:"remote"`
}
// BuildImage builds an image from a tarball's url or a Dockerfile in the input
// stream.
//
// See http://goo.gl/wRsW76 for more details.
func (c *Client) BuildImage(opts BuildImageOptions) error {
if opts.OutputStream == nil {
return ErrMissingOutputStream
@ -318,17 +353,21 @@ func (c *Client) BuildImage(opts BuildImageOptions) error {
return ErrMissingRepo
}
return c.stream("POST", fmt.Sprintf("/build?%s",
queryString(&opts)), true, false, headers, opts.InputStream, opts.OutputStream, nil)
queryString(&opts)), true, opts.RawJSONStream, 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.
//
// See http://goo.gl/5g6qFy for more details.
type TagImageOptions struct {
Repo string
Tag string
Force bool
}
// TagImage adds a tag to the image 'name'
// TagImage adds a tag to the image identified by the given name.
//
// See http://goo.gl/5g6qFy for more details.
func (c *Client) TagImage(name string, opts TagImageOptions) error {
if name == "" {
return ErrNoSuchImage
@ -349,3 +388,30 @@ func isURL(u string) bool {
}
return p.Scheme == "http" || p.Scheme == "https"
}
// APIImageSearch reflect the result of a search on the dockerHub
//
// See http://goo.gl/xI5lLZ for more details.
type APIImageSearch struct {
Description string `json:"description,omitempty" yaml:"description,omitempty"`
IsOfficial bool `json:"is_official,omitempty" yaml:"is_official,omitempty"`
IsAutomated bool `json:"is_automated,omitempty" yaml:"is_automated,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
StarCount int `json:"star_count,omitempty" yaml:"star_count,omitempty"`
}
// SearchImages search the docker hub with a specific given term.
//
// See http://goo.gl/xI5lLZ for more details.
func (c *Client) SearchImages(term string) ([]APIImageSearch, error) {
body, _, err := c.do("GET", "/images/search?term="+term, nil)
if err != nil {
return nil, err
}
var searchResult []APIImageSearch
err = json.Unmarshal(body, &searchResult)
if err != nil {
return nil, err
}
return searchResult, nil
}

View File

@ -122,6 +122,48 @@ func TestListImagesParameters(t *testing.T) {
}
}
func TestImageHistory(t *testing.T) {
body := `[
{
"Id": "25daec02219d2d852f7526137213a9b199926b4b24e732eab5b8bc6c49bd470e",
"Tags": [
"debian:7.6",
"debian:latest",
"debian:7",
"debian:wheezy"
],
"Created": 1409856216,
"CreatedBy": "/bin/sh -c #(nop) CMD [/bin/bash]"
},
{
"Id": "41026a5347fb5be6ed16115bf22df8569697139f246186de9ae8d4f67c335dce",
"Created": 1409856213,
"CreatedBy": "/bin/sh -c #(nop) ADD file:1ee9e97209d00e3416a4543b23574cc7259684741a46bbcbc755909b8a053a38 in /",
"Size": 85178663
},
{
"Id": "511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158",
"Tags": [
"scratch:latest"
],
"Created": 1371157430
}
]`
var expected []ImageHistory
err := json.Unmarshal([]byte(body), &expected)
if err != nil {
t.Fatal(err)
}
client := newTestClient(&FakeRoundTripper{message: body, status: http.StatusOK})
history, err := client.ImageHistory("debian:latest")
if err != nil {
t.Error(err)
}
if !reflect.DeepEqual(history, expected) {
t.Errorf("ImageHistory: Wrong return value. Want %#v. Got %#v.", expected, history)
}
}
func TestRemoveImage(t *testing.T) {
name := "test"
fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent}
@ -224,6 +266,35 @@ func TestPushImage(t *testing.T) {
}
}
func TestPushImageWithRawJSON(t *testing.T) {
body := `
{"status":"Pushing..."}
{"status":"Pushing", "progress":"1/? (n/a)", "progressDetail":{"current":1}}}
{"status":"Image successfully pushed"}
`
fakeRT := &FakeRoundTripper{
message: body,
status: http.StatusOK,
header: map[string]string{
"Content-Type": "application/json",
},
}
client := newTestClient(fakeRT)
var buf bytes.Buffer
err := client.PushImage(PushImageOptions{
Name: "test",
OutputStream: &buf,
RawJSONStream: true,
}, AuthConfiguration{})
if err != nil {
t.Fatal(err)
}
if buf.String() != body {
t.Errorf("PushImage: Wrong raw output. Want %q. Got %q.", body, buf.String())
}
}
func TestPushImageWithAuthentication(t *testing.T) {
fakeRT := &FakeRoundTripper{message: "Pushing 1/100", status: http.StatusOK}
client := newTestClient(fakeRT)
@ -579,7 +650,7 @@ func TestBuildImageParametersForRemoteBuild(t *testing.T) {
expected := map[string][]string{"t": {opts.Name}, "remote": {opts.Remote}, "q": {"1"}}
got := map[string][]string(req.URL.Query())
if !reflect.DeepEqual(got, expected) {
t.Errorf("ImportImage: wrong query string. Want %#v. Got %#v.", expected, got)
t.Errorf("BuildImage: wrong query string. Want %#v. Got %#v.", expected, got)
}
}
@ -608,6 +679,44 @@ func TestBuildImageMissingOutputStream(t *testing.T) {
}
}
func TestBuildImageWithRawJSON(t *testing.T) {
body := `
{"stream":"Step 0 : FROM ubuntu:latest\n"}
{"stream":" ---\u003e 4300eb9d3c8d\n"}
{"stream":"Step 1 : MAINTAINER docker <eng@docker.com>\n"}
{"stream":" ---\u003e Using cache\n"}
{"stream":" ---\u003e 3a3ed758c370\n"}
{"stream":"Step 2 : CMD /usr/bin/top\n"}
{"stream":" ---\u003e Running in 36b1479cc2e4\n"}
{"stream":" ---\u003e 4b6188aebe39\n"}
{"stream":"Removing intermediate container 36b1479cc2e4\n"}
{"stream":"Successfully built 4b6188aebe39\n"}
`
fakeRT := &FakeRoundTripper{
message: body,
status: http.StatusOK,
header: map[string]string{
"Content-Type": "application/json",
},
}
client := newTestClient(fakeRT)
var buf bytes.Buffer
opts := BuildImageOptions{
Name: "testImage",
RmTmpContainer: true,
InputStream: &buf,
OutputStream: &buf,
RawJSONStream: true,
}
err := client.BuildImage(opts)
if err != nil {
t.Fatal(err)
}
if buf.String() != body {
t.Errorf("BuildImage: Wrong raw output. Want %q. Got %q.", body, buf.String())
}
}
func TestBuildImageRemoteWithoutName(t *testing.T) {
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
client := newTestClient(fakeRT)
@ -710,3 +819,43 @@ func TestExportImage(t *testing.T) {
t.Errorf("ExportIMage: wrong path. Expected %q. Got %q.", expectedPath, req.URL.Path)
}
}
func TestSearchImages(t *testing.T) {
body := `[
{
"description":"A container with Cassandra 2.0.3",
"is_official":true,
"is_automated":true,
"name":"poklet/cassandra",
"star_count":17
},
{
"description":"A container with Cassandra 2.0.3",
"is_official":true,
"is_automated":false,
"name":"poklet/cassandra",
"star_count":17
}
,
{
"description":"A container with Cassandra 2.0.3",
"is_official":false,
"is_automated":true,
"name":"poklet/cassandra",
"star_count":17
}
]`
var expected []APIImageSearch
err := json.Unmarshal([]byte(body), &expected)
if err != nil {
t.Fatal(err)
}
client := newTestClient(&FakeRoundTripper{message: body, status: http.StatusOK})
result, err := client.SearchImages("cassandra")
if err != nil {
t.Error(err)
}
if !reflect.DeepEqual(result, expected) {
t.Errorf("SearchImages: Wrong return value. Want %#v. Got %#v.", expected, result)
}
}

View File

@ -11,7 +11,7 @@ import (
// Version returns version information about the docker server.
//
// See http://goo.gl/IqKNRE for more details.
// See http://goo.gl/BOZrF5 for more details.
func (c *Client) Version() (*Env, error) {
body, _, err := c.do("GET", "/version", nil)
if err != nil {
@ -24,9 +24,9 @@ func (c *Client) Version() (*Env, error) {
return &env, nil
}
// Info returns system-wide information, like the number of running containers.
// Info returns system-wide information about the Docker server.
//
// See http://goo.gl/LOmySw for more details.
// See http://goo.gl/wmqZsW for more details.
func (c *Client) Info() (*Env, error) {
body, _, err := c.do("GET", "/info", nil)
if err != nil {

View File

@ -31,7 +31,7 @@ import (
// It can used in standalone mode, listening for connections or as an arbitrary
// HTTP handler.
//
// For more details on the remote API, check http://goo.gl/yMI1S.
// For more details on the remote API, check http://goo.gl/G3plxW.
type DockerServer struct {
containers []*docker.Container
cMut sync.RWMutex
@ -88,6 +88,7 @@ func (s *DockerServer) buildMuxer() {
s.mux.Path("/containers/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.listContainers))
s.mux.Path("/containers/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.createContainer))
s.mux.Path("/containers/{id:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectContainer))
s.mux.Path("/containers/{id:.*}/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:.*}/stop").Methods("POST").HandlerFunc(s.handlerWrapper(s.stopContainer))
s.mux.Path("/containers/{id:.*}/pause").Methods("POST").HandlerFunc(s.handlerWrapper(s.pauseContainer))
@ -339,6 +340,29 @@ func (s *DockerServer) inspectContainer(w http.ResponseWriter, r *http.Request)
json.NewEncoder(w).Encode(container)
}
func (s *DockerServer) topContainer(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
}
if !container.State.Running {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "Container %s is not running", id)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
result := docker.TopResult{
Titles: []string{"UID", "PID", "PPID", "C", "STIME", "TTY", "TIME", "CMD"},
Processes: [][]string{
{"root", "7535", "7516", "0", "03:20", "?", "00:00:00", container.Path + " " + strings.Join(container.Args, " ")},
},
}
json.NewEncoder(w).Encode(result)
}
func (s *DockerServer) startContainer(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
container, _, err := s.findContainer(id)

View File

@ -343,6 +343,58 @@ func TestInspectContainerNotFound(t *testing.T) {
}
}
func TestTopContainer(t *testing.T) {
server := DockerServer{}
addContainers(&server, 1)
server.containers[0].State.Running = true
server.buildMuxer()
recorder := httptest.NewRecorder()
path := fmt.Sprintf("/containers/%s/top", server.containers[0].ID)
request, _ := http.NewRequest("GET", path, nil)
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Errorf("TopContainer: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
}
var got docker.TopResult
err := json.NewDecoder(recorder.Body).Decode(&got)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(got.Titles, []string{"UID", "PID", "PPID", "C", "STIME", "TTY", "TIME", "CMD"}) {
t.Fatalf("TopContainer: Unexpected titles, got: %#v", got.Titles)
}
if len(got.Processes) != 1 {
t.Fatalf("TopContainer: Unexpected process len, got: %d", len(got.Processes))
}
if got.Processes[0][len(got.Processes[0])-1] != "ls -la .." {
t.Fatalf("TopContainer: Unexpected command name, got: %s", got.Processes[0][len(got.Processes[0])-1])
}
}
func TestTopContainerNotFound(t *testing.T) {
server := DockerServer{}
server.buildMuxer()
recorder := httptest.NewRecorder()
request, _ := http.NewRequest("GET", "/containers/xyz/top", nil)
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusNotFound {
t.Errorf("TopContainer: wrong status. Want %d. Got %d.", http.StatusNotFound, recorder.Code)
}
}
func TestTopContainerStopped(t *testing.T) {
server := DockerServer{}
addContainers(&server, 1)
server.buildMuxer()
recorder := httptest.NewRecorder()
path := fmt.Sprintf("/containers/%s/top", server.containers[0].ID)
request, _ := http.NewRequest("GET", path, nil)
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusInternalServerError {
t.Errorf("TopContainer: wrong status. Want %d. Got %d.", http.StatusInternalServerError, recorder.Code)
}
}
func TestStartContainer(t *testing.T) {
server := DockerServer{}
addContainers(&server, 1)