From 835cd2a641699c9365086b258a5cb875f7e27c43 Mon Sep 17 00:00:00 2001 From: Donald Huang Date: Tue, 31 Mar 2015 21:28:33 +0000 Subject: [PATCH 01/27] add SecurityOpt to HostConfig --- types.go | 1 + 1 file changed, 1 insertion(+) diff --git a/types.go b/types.go index 6bf197e..50d1743 100644 --- a/types.go +++ b/types.go @@ -43,6 +43,7 @@ type HostConfig struct { Dns []string DnsSearch []string VolumesFrom []string + SecurityOpt []string NetworkMode string RestartPolicy RestartPolicy } From 4726a5533c4ed28f72e7288d61028050adcb0722 Mon Sep 17 00:00:00 2001 From: Donald Huang Date: Tue, 31 Mar 2015 21:39:41 +0000 Subject: [PATCH 02/27] add NetworkStats struct --- types.go | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/types.go b/types.go index 6bf197e..1f4b227 100644 --- a/types.go +++ b/types.go @@ -209,6 +209,17 @@ type CpuStats struct { ThrottlingData ThrottlingData `json:"throttling_data,omitempty"` } +type NetworkStats struct { + RxBytes uint64 `json:"rx_bytes"` + RxPackets uint64 `json:"rx_packets"` + RxErrors uint64 `json:"rx_errors"` + RxDropped uint64 `json:"rx_dropped"` + TxBytes uint64 `json:"tx_bytes"` + TxPackets uint64 `json:"tx_packets"` + TxErrors uint64 `json:"tx_errors"` + TxDropped uint64 `json:"tx_dropped"` +} + type MemoryStats struct { Usage uint64 `json:"usage"` MaxUsage uint64 `json:"max_usage"` @@ -237,19 +248,9 @@ type BlkioStats struct { } type Stats struct { - Read time.Time `json:"read"` - Network struct { - RxBytes uint64 `json:"rx_bytes"` - RxPackets uint64 `json:"rx_packets"` - RxErrors uint64 `json:"rx_errors"` - RxDropped uint64 `json:"rx_dropped"` - TxBytes uint64 `json:"tx_bytes"` - TxPackets uint64 `json:"tx_packets"` - TxErrors uint64 `json:"tx_errors"` - TxDropped uint64 `json:"tx_dropped"` - } - - CpuStats CpuStats `json:"cpu_stats,omitempty"` - MemoryStats MemoryStats `json:"memory_stats,omitempty"` - BlkioStats BlkioStats `json:"blkio_stats,omitempty"` + Read time.Time `json:"read"` + NetworkStats NetworkStats `json:"network,omitempty"` + CpuStats CpuStats `json:"cpu_stats,omitempty"` + MemoryStats MemoryStats `json:"memory_stats,omitempty"` + BlkioStats BlkioStats `json:"blkio_stats,omitempty"` } From f16c233430123c9d16a380c158881408648e61bc Mon Sep 17 00:00:00 2001 From: Donald Huang Date: Mon, 6 Apr 2015 19:52:42 +0000 Subject: [PATCH 03/27] add TagImage --- dockerclient.go | 14 ++++++++++++++ interface.go | 1 + mockclient/mock.go | 5 +++++ 3 files changed, 20 insertions(+) diff --git a/dockerclient.go b/dockerclient.go index d427a6f..e14c769 100644 --- a/dockerclient.go +++ b/dockerclient.go @@ -310,6 +310,20 @@ func (client *DockerClient) StopAllMonitorStats() { atomic.StoreInt32(&client.monitorStats, 0) } +func (client *DockerClient) TagImage(nameOrID string, repo string, tag string, force bool) error { + v := url.Values{} + v.Set("repo", repo) + v.Set("tag", tag) + if force { + v.Set("force", "1") + } + uri := fmt.Sprintf("/%s/images/%s/tag?%s", APIVersion, nameOrID, v.Encode()) + if _, err := client.doRequest("POST", uri, nil, nil); err != nil { + return err + } + return nil +} + func (client *DockerClient) Version() (*Version, error) { uri := fmt.Sprintf("/%s/version", APIVersion) data, err := client.doRequest("GET", uri, nil, nil) diff --git a/interface.go b/interface.go index 87b69f5..dc71469 100644 --- a/interface.go +++ b/interface.go @@ -24,6 +24,7 @@ type Client interface { StopAllMonitorEvents() StartMonitorStats(id string, cb StatCallback, ec chan error, args ...interface{}) StopAllMonitorStats() + TagImage(nameOrID string, repo string, tag string, force bool) error Version() (*Version, error) PullImage(name string, auth *AuthConfig) error RemoveContainer(id string, force, volumes bool) error diff --git a/mockclient/mock.go b/mockclient/mock.go index 4238846..320792b 100644 --- a/mockclient/mock.go +++ b/mockclient/mock.go @@ -73,6 +73,11 @@ func (client *MockClient) StopAllMonitorEvents() { client.Mock.Called() } +func (client *MockClient) TagImage(nameOrID string, repo string, tag string, force bool) error { + args := client.Mock.Called(nameOrID, repo, tag, force) + return args.Error(0) +} + func (client *MockClient) StartMonitorStats(id string, cb dockerclient.StatCallback, ec chan error, args ...interface{}) { client.Mock.Called(id, cb, ec, args) } From e8b23b514c8687644d96d00879ee92613d3daa95 Mon Sep 17 00:00:00 2001 From: Xian Chaobo Date: Wed, 8 Apr 2015 19:34:50 +0800 Subject: [PATCH 04/27] Add and implement LoadImage interface Signed-off-by: Xian Chaobo --- dockerclient.go | 14 ++++++++++++++ interface.go | 1 + mockclient/mock.go | 5 +++++ 3 files changed, 20 insertions(+) diff --git a/dockerclient.go b/dockerclient.go index d427a6f..a3a3640 100644 --- a/dockerclient.go +++ b/dockerclient.go @@ -349,6 +349,20 @@ func (client *DockerClient) PullImage(name string, auth *AuthConfig) error { return nil } +func (client *DockerClient) LoadImage(reader io.Reader) error { + data, err := ioutil.ReadAll(reader) + if err != nil { + return err + } + + uri := fmt.Sprintf("/%s/images/load", APIVersion) + _, err = client.doRequest("POST", uri, data, nil) + if err != nil { + return err + } + return nil +} + func (client *DockerClient) RemoveContainer(id string, force, volumes bool) error { argForce := 0 argVolumes := 0 diff --git a/interface.go b/interface.go index 87b69f5..6267231 100644 --- a/interface.go +++ b/interface.go @@ -26,6 +26,7 @@ type Client interface { StopAllMonitorStats() Version() (*Version, error) PullImage(name string, auth *AuthConfig) error + LoadImage(reader io.Reader) error RemoveContainer(id string, force, volumes bool) error ListImages() ([]*Image, error) RemoveImage(name string) ([]*ImageDelete, error) diff --git a/mockclient/mock.go b/mockclient/mock.go index 4238846..d49fa37 100644 --- a/mockclient/mock.go +++ b/mockclient/mock.go @@ -91,6 +91,11 @@ func (client *MockClient) PullImage(name string, auth *dockerclient.AuthConfig) return args.Error(0) } +func (client *MockClient) LoadImage(reader io.Reader) error { + args := client.Mock.Called(reader) + return args.Error(0) +} + func (client *MockClient) RemoveContainer(id string, force, volumes bool) error { args := client.Mock.Called(id, force, volumes) return args.Error(0) From ccbd3d241a9e66929290ad6b9c93488689543af6 Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Wed, 15 Apr 2015 14:14:28 -0700 Subject: [PATCH 05/27] Fix golint issues for IP* members. Related to #91 Signed-off-by: Andrea Luzzardi --- types.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types.go b/types.go index 5c73739..54cc99d 100644 --- a/types.go +++ b/types.go @@ -96,8 +96,8 @@ type ContainerInfo struct { } Image string NetworkSettings struct { - IpAddress string - IpPrefixLen int + IPAddress string `json:"IpAddress"` + IPPrefixLen int `json:"IpPrefixLen"` Gateway string Bridge string Ports map[string][]PortBinding From e711a4ce7ccb7e109269ed7d66f75d4088ea608c Mon Sep 17 00:00:00 2001 From: Xian Chaobo Date: Wed, 22 Apr 2015 03:06:52 -0400 Subject: [PATCH 06/27] add support rename Signed-off-by: Xian Chaobo --- dockerclient.go | 9 +++++++++ interface.go | 1 + mockclient/mock.go | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/dockerclient.go b/dockerclient.go index 485f567..978e04b 100644 --- a/dockerclient.go +++ b/dockerclient.go @@ -458,3 +458,12 @@ func (client *DockerClient) Exec(config *ExecConfig) (string, error) { } return createExecResp.Id, nil } + +func (client *DockerClient) RenameContainer(oldName string, newName string) error { + uri := fmt.Sprintf("/containers/%s/rename?name=%s", oldName, newName) + _, err := client.doRequest("POST", uri, nil, nil) + if err != nil { + return err + } + return nil +} diff --git a/interface.go b/interface.go index 6dc9d8c..f29a434 100644 --- a/interface.go +++ b/interface.go @@ -33,4 +33,5 @@ type Client interface { RemoveImage(name string) ([]*ImageDelete, error) PauseContainer(name string) error UnpauseContainer(name string) error + RenameContainer(oldName string, newName string) error } diff --git a/mockclient/mock.go b/mockclient/mock.go index 2974ca9..fe24329 100644 --- a/mockclient/mock.go +++ b/mockclient/mock.go @@ -130,3 +130,8 @@ func (client *MockClient) Exec(config *dockerclient.ExecConfig) (string, error) args := client.Mock.Called(config) return args.String(0), args.Error(1) } + +func (client *MockClient) RenameContainer(oldName string, newName string) error { + args := client.Mock.Called(oldName, newName) + return args.Error(0) +} From bcc686f7781159fc98ae235bd49c7b8a964f0914 Mon Sep 17 00:00:00 2001 From: Xian Chaobo Date: Wed, 22 Apr 2015 14:29:41 +0000 Subject: [PATCH 07/27] direct return err Signed-off-by: Xian Chaobo --- dockerclient.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/dockerclient.go b/dockerclient.go index 978e04b..6fa614f 100644 --- a/dockerclient.go +++ b/dockerclient.go @@ -462,8 +462,5 @@ func (client *DockerClient) Exec(config *ExecConfig) (string, error) { func (client *DockerClient) RenameContainer(oldName string, newName string) error { uri := fmt.Sprintf("/containers/%s/rename?name=%s", oldName, newName) _, err := client.doRequest("POST", uri, nil, nil) - if err != nil { - return err - } - return nil + return err } From bc9c3a41608c3939f0369c5e049ea73610eb6390 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 23 Apr 2015 15:20:26 -0700 Subject: [PATCH 08/27] add labels to containers Signed-off-by: Victor Vieux --- types.go | 1 + 1 file changed, 1 insertion(+) diff --git a/types.go b/types.go index 54cc99d..3dbafc2 100644 --- a/types.go +++ b/types.go @@ -130,6 +130,7 @@ type Container struct { Ports []Port SizeRw int64 SizeRootFs int64 + Labels map[string]string } type Event struct { From 7cbe1867bee16dbad66455ccb7ec537dcf94154a Mon Sep 17 00:00:00 2001 From: Xian Chaobo Date: Tue, 28 Apr 2015 08:12:15 -0400 Subject: [PATCH 09/27] add import Signed-off-by: Xian Chaobo --- dockerclient.go | 57 ++++++++++++++++++++++++++++++++++++++++++++++ interface.go | 1 + mockclient/mock.go | 5 ++++ 3 files changed, 63 insertions(+) diff --git a/dockerclient.go b/dockerclient.go index 6fa614f..df092fb 100644 --- a/dockerclient.go +++ b/dockerclient.go @@ -97,6 +97,41 @@ func (client *DockerClient) doRequest(method string, path string, body []byte, h return data, nil } +func (client *DockerClient) doStreamRequest(method string, path string, in io.Reader, headers map[string]string) (io.ReadCloser, error) { + if (method == "POST" || method == "PUT") && in == nil { + in = bytes.NewReader([]byte{}) + } + req, err := http.NewRequest(method, client.URL.String()+path, in) + if err != nil { + return nil, err + } + if method == "POST" { + req.Header.Add("Content-Type", "plain/text") + } + if headers != nil { + for header, value := range headers { + req.Header.Add(header, value) + } + } + resp, err := client.HTTPClient.Do(req) + if err != nil { + if !strings.Contains(err.Error(), "connection refused") && client.TLSConfig == nil { + return nil, fmt.Errorf("%v. Are you trying to connect to a TLS-enabled daemon without TLS?", err) + } + return nil, err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 400 { + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + return nil, Error{StatusCode: resp.StatusCode, Status: resp.Status, msg: string(data)} + } + + return resp.Body, nil +} + func (client *DockerClient) Info() (*Info, error) { uri := fmt.Sprintf("/%s/info", APIVersion) data, err := client.doRequest("GET", uri, nil, nil) @@ -464,3 +499,25 @@ func (client *DockerClient) RenameContainer(oldName string, newName string) erro _, err := client.doRequest("POST", uri, nil, nil) return err } + +func (client *DockerClient) ImportImage(source string, repository string, tag string, tar io.Reader) (io.ReadCloser, error) { + var fromSrc string + v := &url.Values{} + if source == "" { + fromSrc = "-" + } else { + fromSrc = source + } + + v.Set("fromSrc", fromSrc) + v.Set("repo", repository) + if tag != "" { + v.Set("tag", tag) + } + + var in io.Reader + if fromSrc == "-" { + in = tar + } + return client.doStreamRequest("POST", "/images/create?"+v.Encode(), in, nil) +} diff --git a/interface.go b/interface.go index f29a434..0a7eb1e 100644 --- a/interface.go +++ b/interface.go @@ -34,4 +34,5 @@ type Client interface { PauseContainer(name string) error UnpauseContainer(name string) error RenameContainer(oldName string, newName string) error + ImportImage(source string, repository string, tag string, tar io.Reader) (io.ReadCloser, error) } diff --git a/mockclient/mock.go b/mockclient/mock.go index fe24329..3b2f269 100644 --- a/mockclient/mock.go +++ b/mockclient/mock.go @@ -135,3 +135,8 @@ func (client *MockClient) RenameContainer(oldName string, newName string) error args := client.Mock.Called(oldName, newName) return args.Error(0) } + +func (client *MockClient) ImportImage(source string, repository string, tag string, tar io.Reader) (io.ReadCloser, error) { + args := client.Mock.Called(source, repository, tag, tar) + return args.Get(0).(io.ReadCloser), args.Error(1) +} From b879c7cdd187ce4e2e1d04cc839966d139e6adda Mon Sep 17 00:00:00 2001 From: jamesdcl Date: Wed, 29 Apr 2015 18:28:13 +0800 Subject: [PATCH 10/27] Add LogConfig and Ulimits(new in docker 1.6). --- types.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/types.go b/types.go index 3dbafc2..b31c788 100644 --- a/types.go +++ b/types.go @@ -46,6 +46,8 @@ type HostConfig struct { SecurityOpt []string NetworkMode string RestartPolicy RestartPolicy + Ulimits []Ulimit + LogConfig LogConfig } type ExecConfig struct { @@ -256,3 +258,14 @@ type Stats struct { MemoryStats MemoryStats `json:"memory_stats,omitempty"` BlkioStats BlkioStats `json:"blkio_stats,omitempty"` } + +type Ulimit struct { + Name string `json:"name"` + Soft uint64 `json:"soft"` + Hard uint64 `json:"hard"` +} + +type LogConfig struct { + Type string `json:"type"` + Config map[string]string `json:"config"` +} From c5c297a73eaf24b9636bc2c77c27d379bd0a847d Mon Sep 17 00:00:00 2001 From: Xian Chaobo Date: Wed, 29 Apr 2015 22:45:49 -0400 Subject: [PATCH 11/27] code refactored Signed-off-by: Xian Chaobo --- dockerclient.go | 38 +++++++++++--------------------------- 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/dockerclient.go b/dockerclient.go index df092fb..217410f 100644 --- a/dockerclient.go +++ b/dockerclient.go @@ -66,34 +66,17 @@ func NewDockerClientTimeout(daemonUrl string, tlsConfig *tls.Config, timeout tim func (client *DockerClient) doRequest(method string, path string, body []byte, headers map[string]string) ([]byte, error) { b := bytes.NewBuffer(body) - req, err := http.NewRequest(method, client.URL.String()+path, b) - if err != nil { - return nil, err - } - req.Header.Add("Content-Type", "application/json") - if headers != nil { - for header, value := range headers { - req.Header.Add(header, value) - } - } - resp, err := client.HTTPClient.Do(req) + + reader, err := client.doStreamRequest(method, path, b, headers) if err != nil { - if !strings.Contains(err.Error(), "connection refused") && client.TLSConfig == nil { - return nil, fmt.Errorf("%v. Are you trying to connect to a TLS-enabled daemon without TLS?", err) - } return nil, err } - defer resp.Body.Close() - data, err := ioutil.ReadAll(resp.Body) + + defer reader.Close() + data, err := ioutil.ReadAll(reader) if err != nil { return nil, err } - if resp.StatusCode == 404 { - return nil, ErrNotFound - } - if resp.StatusCode >= 400 { - return nil, Error{StatusCode: resp.StatusCode, Status: resp.Status, msg: string(data)} - } return data, nil } @@ -105,9 +88,7 @@ func (client *DockerClient) doStreamRequest(method string, path string, in io.Re if err != nil { return nil, err } - if method == "POST" { - req.Header.Add("Content-Type", "plain/text") - } + req.Header.Add("Content-Type", "application/json") if headers != nil { for header, value := range headers { req.Header.Add(header, value) @@ -120,8 +101,11 @@ func (client *DockerClient) doStreamRequest(method string, path string, in io.Re } return nil, err } - - if resp.StatusCode < 200 || resp.StatusCode >= 400 { + if resp.StatusCode == 404 { + return nil, ErrNotFound + } + if resp.StatusCode >= 400 { + defer resp.Body.Close() data, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err From 336272eb3ade93f0b9cb24b0b1c58b509c196fbe Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Thu, 30 Apr 2015 18:06:15 -0700 Subject: [PATCH 12/27] Updated the README with a working example (following the recent updates on the lib) --- README.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 26f2528..4c3c284 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ func main() { docker, _ := dockerclient.NewDockerClient("unix:///var/run/docker.sock", nil) // Get only running containers - containers, err := docker.ListContainers(false) + containers, err := docker.ListContainers(false, false, "") if err != nil { log.Fatal(err) } @@ -41,14 +41,19 @@ func main() { } // Create a container - containerConfig := &dockerclient.ContainerConfig{Image: "ubuntu:12.04", Cmd: []string{"bash"}} - containerId, err := docker.CreateContainer(containerConfig) + containerConfig := &dockerclient.ContainerConfig{ + Image: "ubuntu:14.04", + Cmd: []string{"bash"}, + AttachStdin: true, + Tty: true} + containerId, err := docker.CreateContainer(containerConfig, "foobar") if err != nil { log.Fatal(err) } // Start the container - err = docker.StartContainer(containerId) + hostConfig := &dockerclient.HostConfig{} + err = docker.StartContainer(containerId, hostConfig) if err != nil { log.Fatal(err) } @@ -58,6 +63,8 @@ func main() { // Listen to events docker.StartMonitorEvents(eventCallback, nil) + + // Hold the execution to look at the events coming time.Sleep(3600 * time.Second) } ``` From b1771197a291ff4725bed957bf4bd9e1b15eeb7e Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Thu, 30 Apr 2015 18:13:18 -0700 Subject: [PATCH 13/27] Added maintainers list to the README (closes #100) --- README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4c3c284..046a9ce 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,9 @@ Docker client library in Go Well maintained docker client library. -Example: +# How to use it? + +Here is an example showing how to use it: ```go package main @@ -68,3 +70,14 @@ func main() { time.Sleep(3600 * time.Second) } ``` + +# Maintainers + +List of people you can ping for feedback on Pull Requests or any questions. + +- [Sam Alba](https://github.com/samalba) +- [Michael Crosby](https://github.com/crosbymichael) +- [Andrea Luzzardi](https://github.com/aluzzardi) +- [Victor Vieux](https://github.com/vieux) +- [Evan Hazlett](https://github.com/ehazlett) +- [Donald Huang](https://github.com/donhcd) From 9245fd348c8f1a35fbe2b14df3fded7bf25adf02 Mon Sep 17 00:00:00 2001 From: Xian Chaobo Date: Sun, 3 May 2015 21:27:00 -0400 Subject: [PATCH 14/27] use nil instead of bytes Signed-off-by: Xian Chaobo --- dockerclient.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dockerclient.go b/dockerclient.go index 217410f..47213e4 100644 --- a/dockerclient.go +++ b/dockerclient.go @@ -82,7 +82,7 @@ func (client *DockerClient) doRequest(method string, path string, body []byte, h func (client *DockerClient) doStreamRequest(method string, path string, in io.Reader, headers map[string]string) (io.ReadCloser, error) { if (method == "POST" || method == "PUT") && in == nil { - in = bytes.NewReader([]byte{}) + in = bytes.NewReader(nil) } req, err := http.NewRequest(method, client.URL.String()+path, in) if err != nil { From a833fc08d6904ae6380a9a1e253bc5354d31d7da Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Wed, 6 May 2015 22:24:04 -0700 Subject: [PATCH 15/27] ContainerInfo.State: Add new states. Signed-off-by: Andrea Luzzardi --- types.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/types.go b/types.go index b31c788..14f69bd 100644 --- a/types.go +++ b/types.go @@ -90,8 +90,11 @@ type ContainerInfo struct { Running bool Paused bool Restarting bool + OOMKilled bool + Dead bool Pid int ExitCode int + Error string // contains last known error when starting the container StartedAt time.Time FinishedAt time.Time Ghost bool From 88d847a58ac5a1ba04bf1c2e4df1cc6d131299a8 Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Thu, 7 May 2015 14:27:27 -0700 Subject: [PATCH 16/27] state: Support for String() and StateString() Signed-off-by: Andrea Luzzardi --- types.go | 94 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 73 insertions(+), 21 deletions(-) diff --git a/types.go b/types.go index 14f69bd..abc6b98 100644 --- a/types.go +++ b/types.go @@ -1,6 +1,11 @@ package dockerclient -import "time" +import ( + "fmt" + "time" + + "github.com/docker/docker/pkg/units" +) type ContainerConfig struct { Hostname string @@ -78,27 +83,74 @@ type PortBinding struct { HostPort string } -type ContainerInfo struct { - Id string - Created string - Path string - Name string - Args []string - ExecIDs []string - Config *ContainerConfig - State struct { - Running bool - Paused bool - Restarting bool - OOMKilled bool - Dead bool - Pid int - ExitCode int - Error string // contains last known error when starting the container - StartedAt time.Time - FinishedAt time.Time - Ghost bool +type State struct { + Running bool + Paused bool + Restarting bool + OOMKilled bool + Dead bool + Pid int + ExitCode int + Error string // contains last known error when starting the container + StartedAt time.Time + FinishedAt time.Time + Ghost bool +} + +// String returns a human-readable description of the state +// Stoken from docker/docker/daemon/state.go +func (s *State) Status() string { + if s.Running { + if s.Paused { + return fmt.Sprintf("Up %s (Paused)", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt))) + } + if s.Restarting { + return fmt.Sprintf("Restarting (%d) %s ago", s.ExitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt))) + } + + return fmt.Sprintf("Up %s", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt))) + } + + if s.Dead { + return "Dead" + } + + if s.FinishedAt.IsZero() { + return "" + } + + return fmt.Sprintf("Exited (%d) %s ago", s.ExitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt))) +} + +// StateString returns a single string to describe state +// Stoken from docker/docker/daemon/state.go +func (s *State) StateString() string { + if s.Running { + if s.Paused { + return "paused" + } + if s.Restarting { + return "restarting" + } + return "running" + } + + if s.Dead { + return "dead" } + + return "exited" +} + +type ContainerInfo struct { + Id string + Created string + Path string + Name string + Args []string + ExecIDs []string + Config *ContainerConfig + State *State Image string NetworkSettings struct { IPAddress string `json:"IpAddress"` From 9bdd27ae01f073718b27f236f658cfe0baa0affd Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Thu, 7 May 2015 15:11:56 -0700 Subject: [PATCH 17/27] state: Status() -> String() Signed-off-by: Andrea Luzzardi --- types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types.go b/types.go index abc6b98..09b118e 100644 --- a/types.go +++ b/types.go @@ -99,7 +99,7 @@ type State struct { // String returns a human-readable description of the state // Stoken from docker/docker/daemon/state.go -func (s *State) Status() string { +func (s *State) String() string { if s.Running { if s.Paused { return fmt.Sprintf("Up %s (Paused)", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt))) From 7d2ae45072df0388e9cbab1e3e3daa0ad7fe2347 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Sat, 9 May 2015 17:19:27 -0600 Subject: [PATCH 18/27] Switch WriteFlusher import to pkg/ioutils :tada: --- engine_mock_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine_mock_test.go b/engine_mock_test.go index 00fe441..3d1d24f 100644 --- a/engine_mock_test.go +++ b/engine_mock_test.go @@ -10,10 +10,10 @@ import ( "strconv" "time" + "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/jsonlog" "github.com/docker/docker/pkg/stdcopy" "github.com/docker/docker/pkg/timeutils" - "github.com/docker/docker/utils" "github.com/gorilla/mux" ) @@ -74,7 +74,7 @@ func handleImagePull(w http.ResponseWriter, r *http.Request) { func handleContainerLogs(w http.ResponseWriter, r *http.Request) { var outStream, errStream io.Writer - outStream = utils.NewWriteFlusher(w) + outStream = ioutils.NewWriteFlusher(w) // not sure how to test follow if err := r.ParseForm(); err != nil { From a318296b196e55433932be2ab16ebc1f4085d6b6 Mon Sep 17 00:00:00 2001 From: Donald Huang Date: Mon, 11 May 2015 23:53:43 +0000 Subject: [PATCH 19/27] add more fields to Info struct --- types.go | 43 +++++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/types.go b/types.go index 09b118e..41f6fa2 100644 --- a/types.go +++ b/types.go @@ -217,19 +217,38 @@ type Image struct { VirtualSize int64 } +// Info is the struct returned by /info +// The API is currently in flux, so Debug, MemoryLimit, SwapLimit, and +// IPv4Forwarding are interfaces because in docker 1.6.1 they are 0 or 1 but in +// master they are bools. type Info struct { - ID string - Containers int64 - Driver string - DriverStatus [][]string - ExecutionDriver string - Images int64 - KernelVersion string - OperatingSystem string - NCPU int64 - MemTotal int64 - Name string - Labels []string + ID string + Containers int64 + Driver string + DriverStatus [][]string + ExecutionDriver string + Images int64 + KernelVersion string + OperatingSystem string + NCPU int64 + MemTotal int64 + Name string + Labels []string + Debug interface{} + NFd int64 + NGoroutines int64 + SystemTime time.Time + NEventsListener int64 + InitPath string + InitSha1 string + IndexServerAddress string + MemoryLimit interface{} + SwapLimit interface{} + IPv4Forwarding interface{} + DockerRootDir string + HttpProxy string + HttpsProxy string + NoProxy string } type ImageDelete struct { From 90f6e653bab0f7480a784480c761a8b685e44632 Mon Sep 17 00:00:00 2001 From: Donald Huang Date: Tue, 12 May 2015 18:37:00 +0000 Subject: [PATCH 20/27] add more version fields --- types.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/types.go b/types.go index 09b118e..d855fc5 100644 --- a/types.go +++ b/types.go @@ -198,9 +198,13 @@ type Event struct { } type Version struct { - Version string - GitCommit string - GoVersion string + ApiVersion string + Arch string + GitCommit string + GoVersion string + KernelVersion string + Os string + Version string } type RespContainersCreate struct { From b56756e4eda8730a8e796216bfd7bfb4b7886f75 Mon Sep 17 00:00:00 2001 From: Donald Huang Date: Fri, 10 Apr 2015 17:37:59 +0000 Subject: [PATCH 21/27] add MonitorEvents which returns channels --- dockerclient.go | 102 +++++++++++++++++++++++++++++++++++++++++++ dockerclient_test.go | 37 ++++++++++++++++ engine_mock_test.go | 5 +++ example_responses.go | 2 + interface.go | 5 +++ mockclient/mock.go | 5 +++ types.go | 22 ++++++++++ 7 files changed, 178 insertions(+) diff --git a/dockerclient.go b/dockerclient.go index 47213e4..cc83af6 100644 --- a/dockerclient.go +++ b/dockerclient.go @@ -231,6 +231,50 @@ func (client *DockerClient) ContainerChanges(id string) ([]*ContainerChanges, er return changes, nil } +func (client *DockerClient) readJSONStream(stream io.ReadCloser, decode func(*json.Decoder) decodingResult) (<-chan decodingResult, chan<- struct{}) { + resultChan := make(chan decodingResult) + closeChan := make(chan struct{}) + go func() { + defer close(resultChan) + + internalResultsChan := make(chan decodingResult) + defer close(internalResultsChan) + + stillListening := make(chan struct{}) + defer close(stillListening) + + go func() { + decoder := json.NewDecoder(stream) + defer stream.Close() + for { + decodeResult := decode(decoder) + if _, ok := <-stillListening; !ok { + return + } + internalResultsChan <- decodeResult + if decodeResult.err != nil { + return + } + } + }() + + for { + stillListening <- struct{}{} + select { + case result := <-internalResultsChan: + resultChan <- result + if result.err != nil { + <-closeChan + return + } + case <-closeChan: + return + } + } + }() + return resultChan, closeChan +} + func (client *DockerClient) StartContainer(id string, config *HostConfig) error { data, err := json.Marshal(config) if err != nil { @@ -271,6 +315,64 @@ func (client *DockerClient) KillContainer(id, signal string) error { return nil } +func (client *DockerClient) MonitorEvents(options *MonitorEventsOptions) (<-chan EventOrError, chan<- struct{}, error) { + v := url.Values{} + if options != nil { + if options.Since != 0 { + v.Add("since", strconv.Itoa(options.Since)) + } + if options.Until != 0 { + v.Add("until", strconv.Itoa(options.Until)) + } + if options.Filters != nil { + filterMap := make(map[string][]string) + if len(options.Filters.Event) > 0 { + filterMap["event"] = []string{options.Filters.Event} + } + if len(options.Filters.Image) > 0 { + filterMap["image"] = []string{options.Filters.Image} + } + if len(options.Filters.Container) > 0 { + filterMap["container"] = []string{options.Filters.Container} + } + if len(filterMap) > 0 { + filterJSONBytes, err := json.Marshal(filterMap) + if err != nil { + return nil, nil, err + } + v.Add("filters", string(filterJSONBytes)) + } + } + } + uri := fmt.Sprintf("%s/%s/events?%s", client.URL.String(), APIVersion, v.Encode()) + resp, err := client.HTTPClient.Get(uri) + if err != nil { + return nil, nil, err + } + + decode := func(decoder *json.Decoder) decodingResult { + var event Event + if err := decoder.Decode(&event); err != nil { + return decodingResult{err: err} + } else { + return decodingResult{result: event} + } + } + decodingResultChan, closeChan := client.readJSONStream(resp.Body, decode) + eventOrErrorChan := make(chan EventOrError) + go func() { + for decodingResult := range decodingResultChan { + event, _ := decodingResult.result.(Event) + eventOrErrorChan <- EventOrError{ + Event: event, + Error: decodingResult.err, + } + } + close(eventOrErrorChan) + }() + return eventOrErrorChan, closeChan, nil +} + func (client *DockerClient) StartMonitorEvents(cb Callback, ec chan error, args ...interface{}) { atomic.StoreInt32(&client.monitorEvents, 1) go client.getEvents(cb, ec, args...) diff --git a/dockerclient_test.go b/dockerclient_test.go index bb76ad8..689e321 100644 --- a/dockerclient_test.go +++ b/dockerclient_test.go @@ -2,7 +2,9 @@ package dockerclient import ( "bytes" + "encoding/json" "fmt" + "io" "reflect" "strings" "testing" @@ -155,6 +157,41 @@ func TestContainerLogs(t *testing.T) { } } +func TestMonitorEvents(t *testing.T) { + client := testDockerClient(t) + decoder := json.NewDecoder(bytes.NewBufferString(eventsResp)) + var expectedEvents []Event + for { + var event Event + if err := decoder.Decode(&event); err != nil { + if err == io.EOF { + break + } else { + t.Fatalf("cannot parse expected resp: %s", err.Error()) + } + } else { + expectedEvents = append(expectedEvents, event) + } + } + + eventInfoChan, closeChan, err := client.MonitorEvents(nil) + if err != nil { + t.Fatalf("cannot get events from server: %s", err.Error()) + } + + for i, expectedEvent := range expectedEvents { + t.Logf("on iter %d\n", i) + select { + case eventInfo := <-eventInfoChan: + if eventInfo.Error != nil || eventInfo.Event != expectedEvent { + t.Fatalf("index %d, got:\n%#v\nexpected:\n%#v", i, eventInfo, expectedEvent) + } + } + t.Logf("done with iter %d\n", i) + } + close(closeChan) +} + func TestDockerClientInterface(t *testing.T) { iface := reflect.TypeOf((*Client)(nil)).Elem() test := testDockerClient(t) diff --git a/engine_mock_test.go b/engine_mock_test.go index 3d1d24f..114a71f 100644 --- a/engine_mock_test.go +++ b/engine_mock_test.go @@ -30,6 +30,7 @@ func init() { r.HandleFunc(baseURL+"/containers/{id}/changes", handleContainerChanges).Methods("GET") r.HandleFunc(baseURL+"/containers/{id}/kill", handleContainerKill).Methods("POST") r.HandleFunc(baseURL+"/images/create", handleImagePull).Methods("POST") + r.HandleFunc(baseURL+"/events", handleEvents).Methods("GET") testHTTPServer = httptest.NewServer(handlerAccessLog(r)) } @@ -228,3 +229,7 @@ func handlerGetContainers(w http.ResponseWriter, r *http.Request) { } w.Write([]byte(body)) } + +func handleEvents(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(eventsResp)) +} diff --git a/example_responses.go b/example_responses.go index 9f683f1..670508c 100644 --- a/example_responses.go +++ b/example_responses.go @@ -9,3 +9,5 @@ var haproxyPullOutput = `{"status":"The image you are pulling has been verified" {"status":"Already exists","progressDetail":{},"id":"511136ea3c5a"}{"status":"Already exists","progressDetail":{},"id":"1aeada447715"}{"status":"Already exists","progressDetail":{},"id":"479215127fa7"}{"status":"Already exists","progressDetail":{},"id":"66301eb54a7d"}{"status":"Already exists","progressDetail":{},"id":"e3990b07573f"}{"status":"Already exists","progressDetail":{},"id":"3d894e6f7e63"}{"status":"Already exists","progressDetail":{},"id":"4d949c40bc77"}{"status":"Already exists","progressDetail":{},"id":"55e031889365"}{"status":"Already exists","progressDetail":{},"id":"c7aa675e1876"}{"status":"The image you are pulling has been verified","id":"haproxy:latest"} {"status":"Already exists","progressDetail":{},"id":"511136ea3c5a"}{"status":"Already exists","progressDetail":{},"id":"1aeada447715"}{"status":"Already exists","progressDetail":{},"id":"479215127fa7"}{"status":"Already exists","progressDetail":{},"id":"66301eb54a7d"}{"status":"Already exists","progressDetail":{},"id":"e3990b07573f"}{"status":"Already exists","progressDetail":{},"id":"ecb4b23ca7ce"}{"status":"Already exists","progressDetail":{},"id":"f453e940c177"}{"status":"Already exists","progressDetail":{},"id":"fc5ea1bc05ab"}{"status":"Already exists","progressDetail":{},"id":"380557f8f7b3"}{"status":"Status: Image is up to date for haproxy"} ` + +var eventsResp = `{"status":"pull","id":"nginx:latest","time":1428620433}{"status":"create","id":"9b818c3b8291708fdcecd7c4086b75c222cb503be10a93d9c11040886032a48b","from":"nginx:latest","time":1428620433}{"status":"start","id":"9b818c3b8291708fdcecd7c4086b75c222cb503be10a93d9c11040886032a48b","from":"nginx:latest","time":1428620433}{"status":"die","id":"9b818c3b8291708fdcecd7c4086b75c222cb503be10a93d9c11040886032a48b","from":"nginx:latest","time":1428620442}{"status":"create","id":"352d0b412aae5a5d2b14ae9d88be59dc276602d9edb9dcc33e138e475b3e4720","from":"52.11.96.81/foobar/ubuntu:latest","time":1428620444}{"status":"start","id":"352d0b412aae5a5d2b14ae9d88be59dc276602d9edb9dcc33e138e475b3e4720","from":"52.11.96.81/foobar/ubuntu:latest","time":1428620444}{"status":"die","id":"352d0b412aae5a5d2b14ae9d88be59dc276602d9edb9dcc33e138e475b3e4720","from":"52.11.96.81/foobar/ubuntu:latest","time":1428620444}{"status":"pull","id":"debian:latest","time":1428620453}{"status":"create","id":"668887b5729946546b3072655dc6da08f0e3210111b68b704eb842adfce53f6c","from":"debian:latest","time":1428620453}{"status":"start","id":"668887b5729946546b3072655dc6da08f0e3210111b68b704eb842adfce53f6c","from":"debian:latest","time":1428620453}{"status":"die","id":"668887b5729946546b3072655dc6da08f0e3210111b68b704eb842adfce53f6c","from":"debian:latest","time":1428620453}{"status":"create","id":"eb4a19ec21ab29bbbffbf3ee2e2df9d99cb749780e1eff06a591cee5ba505180","from":"nginx:latest","time":1428620458}{"status":"start","id":"eb4a19ec21ab29bbbffbf3ee2e2df9d99cb749780e1eff06a591cee5ba505180","from":"nginx:latest","time":1428620458}{"status":"pause","id":"eb4a19ec21ab29bbbffbf3ee2e2df9d99cb749780e1eff06a591cee5ba505180","from":"nginx:latest","time":1428620462}{"status":"unpause","id":"eb4a19ec21ab29bbbffbf3ee2e2df9d99cb749780e1eff06a591cee5ba505180","from":"nginx:latest","time":1428620466}{"status":"die","id":"eb4a19ec21ab29bbbffbf3ee2e2df9d99cb749780e1eff06a591cee5ba505180","from":"nginx:latest","time":1428620469}` diff --git a/interface.go b/interface.go index 0a7eb1e..28c17d1 100644 --- a/interface.go +++ b/interface.go @@ -20,6 +20,11 @@ type Client interface { StopContainer(id string, timeout int) error RestartContainer(id string, timeout int) error KillContainer(id, signal string) error + // MonitorEvents returns an EventOrError channel and a close channel. If + // an error is ever sent, then no more events will be sent. Users must + // always close the close channel when they are done reading events, + // even if an error was sent. + MonitorEvents(options *MonitorEventsOptions) (<-chan EventOrError, chan<- struct{}, error) StartMonitorEvents(cb Callback, ec chan error, args ...interface{}) StopAllMonitorEvents() StartMonitorStats(id string, cb StatCallback, ec chan error, args ...interface{}) diff --git a/mockclient/mock.go b/mockclient/mock.go index 3b2f269..50fbbd8 100644 --- a/mockclient/mock.go +++ b/mockclient/mock.go @@ -65,6 +65,11 @@ func (client *MockClient) KillContainer(id, signal string) error { return args.Error(0) } +func (client *MockClient) MonitorEvents(options *dockerclient.MonitorEventsOptions) (<-chan dockerclient.EventOrError, chan<- struct{}, error) { + args := client.Mock.Called(options) + return args.Get(0).(<-chan dockerclient.EventOrError), args.Get(1).(chan<- struct{}), args.Error(2) +} + func (client *MockClient) StartMonitorEvents(cb dockerclient.Callback, ec chan error, args ...interface{}) { client.Mock.Called(cb, ec, args) } diff --git a/types.go b/types.go index bedcf03..2637fab 100644 --- a/types.go +++ b/types.go @@ -73,6 +73,18 @@ type LogOptions struct { Tail int64 } +type MonitorEventsFilters struct { + Event string `json:",omitempty"` + Image string `json:",omitempty"` + Container string `json:",omitempty"` +} + +type MonitorEventsOptions struct { + Since int + Until int + Filters *MonitorEventsFilters `json:",omitempty"` +} + type RestartPolicy struct { Name string MaximumRetryCount int64 @@ -260,6 +272,16 @@ type ImageDelete struct { Untagged string } +type EventOrError struct { + Event + Error error +} + +type decodingResult struct { + result interface{} + err error +} + // The following are types for the API stats endpoint type ThrottlingData struct { // Number of periods with throttling active From 8fc6e30a30b90293725d0b7d5f72d4c8dadb4362 Mon Sep 17 00:00:00 2001 From: Donald Huang Date: Mon, 18 May 2015 21:01:10 +0000 Subject: [PATCH 22/27] accept a passed in stopChan --- dockerclient.go | 25 ++++++++++++------------- dockerclient_test.go | 31 ++++++++++++++++++++++++------- interface.go | 10 +++++----- 3 files changed, 41 insertions(+), 25 deletions(-) diff --git a/dockerclient.go b/dockerclient.go index cc83af6..77c262a 100644 --- a/dockerclient.go +++ b/dockerclient.go @@ -231,9 +231,8 @@ func (client *DockerClient) ContainerChanges(id string) ([]*ContainerChanges, er return changes, nil } -func (client *DockerClient) readJSONStream(stream io.ReadCloser, decode func(*json.Decoder) decodingResult) (<-chan decodingResult, chan<- struct{}) { +func (client *DockerClient) readJSONStream(stream io.ReadCloser, decode func(*json.Decoder) decodingResult, stopChan <-chan struct{}) <-chan decodingResult { resultChan := make(chan decodingResult) - closeChan := make(chan struct{}) go func() { defer close(resultChan) @@ -259,20 +258,20 @@ func (client *DockerClient) readJSONStream(stream io.ReadCloser, decode func(*js }() for { - stillListening <- struct{}{} select { - case result := <-internalResultsChan: + case <-stopChan: + return + default: + stillListening <- struct{}{} + result := <-internalResultsChan resultChan <- result if result.err != nil { - <-closeChan return } - case <-closeChan: - return } } }() - return resultChan, closeChan + return resultChan } func (client *DockerClient) StartContainer(id string, config *HostConfig) error { @@ -315,7 +314,7 @@ func (client *DockerClient) KillContainer(id, signal string) error { return nil } -func (client *DockerClient) MonitorEvents(options *MonitorEventsOptions) (<-chan EventOrError, chan<- struct{}, error) { +func (client *DockerClient) MonitorEvents(options *MonitorEventsOptions, stopChan <-chan struct{}) (<-chan EventOrError, error) { v := url.Values{} if options != nil { if options.Since != 0 { @@ -338,7 +337,7 @@ func (client *DockerClient) MonitorEvents(options *MonitorEventsOptions) (<-chan if len(filterMap) > 0 { filterJSONBytes, err := json.Marshal(filterMap) if err != nil { - return nil, nil, err + return nil, err } v.Add("filters", string(filterJSONBytes)) } @@ -347,7 +346,7 @@ func (client *DockerClient) MonitorEvents(options *MonitorEventsOptions) (<-chan uri := fmt.Sprintf("%s/%s/events?%s", client.URL.String(), APIVersion, v.Encode()) resp, err := client.HTTPClient.Get(uri) if err != nil { - return nil, nil, err + return nil, err } decode := func(decoder *json.Decoder) decodingResult { @@ -358,7 +357,7 @@ func (client *DockerClient) MonitorEvents(options *MonitorEventsOptions) (<-chan return decodingResult{result: event} } } - decodingResultChan, closeChan := client.readJSONStream(resp.Body, decode) + decodingResultChan := client.readJSONStream(resp.Body, decode, stopChan) eventOrErrorChan := make(chan EventOrError) go func() { for decodingResult := range decodingResultChan { @@ -370,7 +369,7 @@ func (client *DockerClient) MonitorEvents(options *MonitorEventsOptions) (<-chan } close(eventOrErrorChan) }() - return eventOrErrorChan, closeChan, nil + return eventOrErrorChan, nil } func (client *DockerClient) StartMonitorEvents(cb Callback, ec chan error, args ...interface{}) { diff --git a/dockerclient_test.go b/dockerclient_test.go index 689e321..0b57518 100644 --- a/dockerclient_test.go +++ b/dockerclient_test.go @@ -174,22 +174,39 @@ func TestMonitorEvents(t *testing.T) { } } - eventInfoChan, closeChan, err := client.MonitorEvents(nil) + // test passing stop chan + stopChan := make(chan struct{}) + eventInfoChan, err := client.MonitorEvents(nil, stopChan) + if err != nil { + t.Fatalf("cannot get events from server: %s", err.Error()) + } + + eventInfo := <-eventInfoChan + if eventInfo.Error != nil || eventInfo.Event != expectedEvents[0] { + t.Fatalf("got:\n%#v\nexpected:\n%#v", eventInfo, expectedEvents[0]) + } + close(stopChan) + for i := 0; i < 3; i++ { + _, ok := <-eventInfoChan + if i == 2 && ok { + t.Fatalf("read more than 2 events successfully after closing stopChan") + } + } + + // test when you don't pass stop chan + eventInfoChan, err = client.MonitorEvents(nil, nil) if err != nil { t.Fatalf("cannot get events from server: %s", err.Error()) } for i, expectedEvent := range expectedEvents { t.Logf("on iter %d\n", i) - select { - case eventInfo := <-eventInfoChan: - if eventInfo.Error != nil || eventInfo.Event != expectedEvent { - t.Fatalf("index %d, got:\n%#v\nexpected:\n%#v", i, eventInfo, expectedEvent) - } + eventInfo := <-eventInfoChan + if eventInfo.Error != nil || eventInfo.Event != expectedEvent { + t.Fatalf("index %d, got:\n%#v\nexpected:\n%#v", i, eventInfo, expectedEvent) } t.Logf("done with iter %d\n", i) } - close(closeChan) } func TestDockerClientInterface(t *testing.T) { diff --git a/interface.go b/interface.go index 28c17d1..4a29b49 100644 --- a/interface.go +++ b/interface.go @@ -20,11 +20,11 @@ type Client interface { StopContainer(id string, timeout int) error RestartContainer(id string, timeout int) error KillContainer(id, signal string) error - // MonitorEvents returns an EventOrError channel and a close channel. If - // an error is ever sent, then no more events will be sent. Users must - // always close the close channel when they are done reading events, - // even if an error was sent. - MonitorEvents(options *MonitorEventsOptions) (<-chan EventOrError, chan<- struct{}, error) + // MonitorEvents takes options and an optional stop channel, and returns + // an EventOrError channel. If an error is ever sent, then no more + // events will be sent. If a stop channel is provided, events will stop + // being monitored after the stop channel is closed. + MonitorEvents(options *MonitorEventsOptions, stopChan <-chan struct{}) (<-chan EventOrError, error) StartMonitorEvents(cb Callback, ec chan error, args ...interface{}) StopAllMonitorEvents() StartMonitorStats(id string, cb StatCallback, ec chan error, args ...interface{}) From 375f63ca7d0b3b874f92e01bd5439398c61a4356 Mon Sep 17 00:00:00 2001 From: Donald Huang Date: Mon, 18 May 2015 21:12:09 +0000 Subject: [PATCH 23/27] unnest readJSONStream goroutine --- dockerclient.go | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/dockerclient.go b/dockerclient.go index 77c262a..0f5046d 100644 --- a/dockerclient.go +++ b/dockerclient.go @@ -233,30 +233,28 @@ func (client *DockerClient) ContainerChanges(id string) ([]*ContainerChanges, er func (client *DockerClient) readJSONStream(stream io.ReadCloser, decode func(*json.Decoder) decodingResult, stopChan <-chan struct{}) <-chan decodingResult { resultChan := make(chan decodingResult) + internalResultsChan := make(chan decodingResult) + stillListening := make(chan struct{}) + go func() { - defer close(resultChan) + decoder := json.NewDecoder(stream) + defer stream.Close() + for { + decodeResult := decode(decoder) + if _, ok := <-stillListening; !ok { + return + } + internalResultsChan <- decodeResult + if decodeResult.err != nil { + return + } + } + }() - internalResultsChan := make(chan decodingResult) + go func() { + defer close(resultChan) defer close(internalResultsChan) - - stillListening := make(chan struct{}) defer close(stillListening) - - go func() { - decoder := json.NewDecoder(stream) - defer stream.Close() - for { - decodeResult := decode(decoder) - if _, ok := <-stillListening; !ok { - return - } - internalResultsChan <- decodeResult - if decodeResult.err != nil { - return - } - } - }() - for { select { case <-stopChan: @@ -271,6 +269,7 @@ func (client *DockerClient) readJSONStream(stream io.ReadCloser, decode func(*js } } }() + return resultChan } From 80aeeac25dab631b1ee2ffe43ada5e352c913e8d Mon Sep 17 00:00:00 2001 From: Donald Huang Date: Mon, 18 May 2015 23:07:31 +0000 Subject: [PATCH 24/27] simplify readJSONStream --- dockerclient.go | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/dockerclient.go b/dockerclient.go index 0f5046d..46542be 100644 --- a/dockerclient.go +++ b/dockerclient.go @@ -233,37 +233,19 @@ func (client *DockerClient) ContainerChanges(id string) ([]*ContainerChanges, er func (client *DockerClient) readJSONStream(stream io.ReadCloser, decode func(*json.Decoder) decodingResult, stopChan <-chan struct{}) <-chan decodingResult { resultChan := make(chan decodingResult) - internalResultsChan := make(chan decodingResult) - stillListening := make(chan struct{}) go func() { decoder := json.NewDecoder(stream) defer stream.Close() - for { - decodeResult := decode(decoder) - if _, ok := <-stillListening; !ok { - return - } - internalResultsChan <- decodeResult - if decodeResult.err != nil { - return - } - } - }() - - go func() { defer close(resultChan) - defer close(internalResultsChan) - defer close(stillListening) for { + decodeResult := decode(decoder) select { case <-stopChan: return default: - stillListening <- struct{}{} - result := <-internalResultsChan - resultChan <- result - if result.err != nil { + resultChan <- decodeResult + if decodeResult.err != nil { return } } From 344ef3add4de48663e4892f75cfafcc43ddb83fe Mon Sep 17 00:00:00 2001 From: Donald Huang Date: Mon, 18 May 2015 23:09:44 +0000 Subject: [PATCH 25/27] update mock --- examples/events.go | 2 +- examples/stats/stats.go | 2 +- mockclient/mock.go | 8 ++++---- mockclient/mock_test.go | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/events.go b/examples/events.go index 07d05df..3f2346f 100644 --- a/examples/events.go +++ b/examples/events.go @@ -1,7 +1,7 @@ package main import ( - "github.com/samalba/dockerclient" + "github.com/donhcd/dockerclient" "log" "os" "os/signal" diff --git a/examples/stats/stats.go b/examples/stats/stats.go index 9027069..e0aba88 100644 --- a/examples/stats/stats.go +++ b/examples/stats/stats.go @@ -1,7 +1,7 @@ package main import ( - "github.com/samalba/dockerclient" + "github.com/donhcd/dockerclient" "log" "os" "os/signal" diff --git a/mockclient/mock.go b/mockclient/mock.go index 50fbbd8..2c90720 100644 --- a/mockclient/mock.go +++ b/mockclient/mock.go @@ -3,7 +3,7 @@ package mockclient import ( "io" - "github.com/samalba/dockerclient" + "github.com/donhcd/dockerclient" "github.com/stretchr/testify/mock" ) @@ -65,9 +65,9 @@ func (client *MockClient) KillContainer(id, signal string) error { return args.Error(0) } -func (client *MockClient) MonitorEvents(options *dockerclient.MonitorEventsOptions) (<-chan dockerclient.EventOrError, chan<- struct{}, error) { - args := client.Mock.Called(options) - return args.Get(0).(<-chan dockerclient.EventOrError), args.Get(1).(chan<- struct{}), args.Error(2) +func (client *MockClient) MonitorEvents(options *dockerclient.MonitorEventsOptions, stopChan <-chan struct{}) (<-chan dockerclient.EventOrError, error) { + args := client.Mock.Called(options, stopChan) + return args.Get(0).(<-chan dockerclient.EventOrError), args.Error(1) } func (client *MockClient) StartMonitorEvents(cb dockerclient.Callback, ec chan error, args ...interface{}) { diff --git a/mockclient/mock_test.go b/mockclient/mock_test.go index 8d91bcf..7d7d8e1 100644 --- a/mockclient/mock_test.go +++ b/mockclient/mock_test.go @@ -4,7 +4,7 @@ import ( "reflect" "testing" - "github.com/samalba/dockerclient" + "github.com/donhcd/dockerclient" ) func TestMock(t *testing.T) { From fcf9c785e694217ef457156f706a0f1d17367c07 Mon Sep 17 00:00:00 2001 From: Donald Huang Date: Tue, 19 May 2015 18:36:07 +0000 Subject: [PATCH 26/27] fix rename --- examples/events.go | 2 +- examples/stats/stats.go | 2 +- mockclient/mock.go | 2 +- mockclient/mock_test.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/events.go b/examples/events.go index 3f2346f..07d05df 100644 --- a/examples/events.go +++ b/examples/events.go @@ -1,7 +1,7 @@ package main import ( - "github.com/donhcd/dockerclient" + "github.com/samalba/dockerclient" "log" "os" "os/signal" diff --git a/examples/stats/stats.go b/examples/stats/stats.go index e0aba88..9027069 100644 --- a/examples/stats/stats.go +++ b/examples/stats/stats.go @@ -1,7 +1,7 @@ package main import ( - "github.com/donhcd/dockerclient" + "github.com/samalba/dockerclient" "log" "os" "os/signal" diff --git a/mockclient/mock.go b/mockclient/mock.go index 2c90720..f59f05b 100644 --- a/mockclient/mock.go +++ b/mockclient/mock.go @@ -3,7 +3,7 @@ package mockclient import ( "io" - "github.com/donhcd/dockerclient" + "github.com/samalba/dockerclient" "github.com/stretchr/testify/mock" ) diff --git a/mockclient/mock_test.go b/mockclient/mock_test.go index 7d7d8e1..8d91bcf 100644 --- a/mockclient/mock_test.go +++ b/mockclient/mock_test.go @@ -4,7 +4,7 @@ import ( "reflect" "testing" - "github.com/donhcd/dockerclient" + "github.com/samalba/dockerclient" ) func TestMock(t *testing.T) { From cad46cbed93da9e73518c4f9d610fa8210ebcf67 Mon Sep 17 00:00:00 2001 From: Donald Huang Date: Tue, 19 May 2015 18:35:25 +0000 Subject: [PATCH 27/27] add InspectImage --- dockerclient.go | 14 ++++++++++++++ interface.go | 1 + mockclient/mock.go | 5 +++++ types.go | 17 +++++++++++++++++ 4 files changed, 37 insertions(+) diff --git a/dockerclient.go b/dockerclient.go index 47213e4..5384d0b 100644 --- a/dockerclient.go +++ b/dockerclient.go @@ -382,6 +382,20 @@ func (client *DockerClient) PullImage(name string, auth *AuthConfig) error { return nil } +func (client *DockerClient) InspectImage(id string) (*ImageInfo, error) { + uri := fmt.Sprintf("/%s/images/%s/json", APIVersion, id) + data, err := client.doRequest("GET", uri, nil, nil) + if err != nil { + return nil, err + } + info := &ImageInfo{} + err = json.Unmarshal(data, info) + if err != nil { + return nil, err + } + return info, nil +} + func (client *DockerClient) LoadImage(reader io.Reader) error { data, err := ioutil.ReadAll(reader) if err != nil { diff --git a/interface.go b/interface.go index 0a7eb1e..3097f43 100644 --- a/interface.go +++ b/interface.go @@ -12,6 +12,7 @@ type Client interface { Info() (*Info, error) ListContainers(all, size bool, filters string) ([]Container, error) InspectContainer(id string) (*ContainerInfo, error) + InspectImage(id string) (*ImageInfo, error) CreateContainer(config *ContainerConfig, name string) (string, error) ContainerLogs(id string, options *LogOptions) (io.ReadCloser, error) ContainerChanges(id string) ([]*ContainerChanges, error) diff --git a/mockclient/mock.go b/mockclient/mock.go index 3b2f269..5086236 100644 --- a/mockclient/mock.go +++ b/mockclient/mock.go @@ -30,6 +30,11 @@ func (client *MockClient) InspectContainer(id string) (*dockerclient.ContainerIn return args.Get(0).(*dockerclient.ContainerInfo), args.Error(1) } +func (client *MockClient) InspectImage(id string) (*dockerclient.ImageInfo, error) { + args := client.Mock.Called(id) + return args.Get(0).(*dockerclient.ImageInfo), args.Error(1) +} + func (client *MockClient) CreateContainer(config *dockerclient.ContainerConfig, name string) (string, error) { args := client.Mock.Called(config, name) return args.String(0), args.Error(1) diff --git a/types.go b/types.go index bedcf03..c3041bd 100644 --- a/types.go +++ b/types.go @@ -20,6 +20,7 @@ type ContainerConfig struct { AttachStderr bool PortSpecs []string ExposedPorts map[string]struct{} + MacAddress string Tty bool OpenStdin bool StdinOnce bool @@ -142,6 +143,22 @@ func (s *State) StateString() string { return "exited" } +type ImageInfo struct { + Architecture string + Author string + Comment string + Config *ContainerConfig + Container string + ContainerConfig *ContainerConfig + Created time.Time + DockerVersion string + Id string + Os string + Parent string + Size int64 + VirtualSize int64 +} + type ContainerInfo struct { Id string Created string