diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index e3ece23..6e0cb77 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -16,7 +16,7 @@ }, { "ImportPath": "github.com/samalba/dockerclient", - "Rev": "578f4b4d729fb23a8ffade2f7e3f335a964fea0c" + "Rev": "3342e9407a459d3cb369a81f8aa23b6cbe3fa8da" } ] } diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/README.md b/Godeps/_workspace/src/github.com/samalba/dockerclient/README.md index 26f2528..046a9ce 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/README.md +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/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 @@ -25,7 +27,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 +43,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 +65,19 @@ func main() { // Listen to events docker.StartMonitorEvents(eventCallback, nil) + + // Hold the execution to look at the events coming 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) diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient.go index a3a3640..d08d8d1 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient.go @@ -30,8 +30,8 @@ type DockerClient struct { URL *url.URL HTTPClient *http.Client TLSConfig *tls.Config - monitorEvents int32 monitorStats int32 + eventStopChan chan (struct{}) } type Error struct { @@ -61,12 +61,30 @@ func NewDockerClientTimeout(daemonUrl string, tlsConfig *tls.Config, timeout tim } } httpClient := newHTTPClient(u, tlsConfig, timeout) - return &DockerClient{u, httpClient, tlsConfig, 0, 0}, nil + return &DockerClient{u, httpClient, tlsConfig, 0, nil}, nil } 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) + + reader, err := client.doStreamRequest(method, path, b, headers) + if err != nil { + return nil, err + } + + defer reader.Close() + data, err := ioutil.ReadAll(reader) + if err != nil { + return nil, err + } + 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(nil) + } + req, err := http.NewRequest(method, client.URL.String()+path, in) if err != nil { return nil, err } @@ -83,18 +101,19 @@ func (client *DockerClient) doRequest(method string, path string, body []byte, h } return nil, err } - defer resp.Body.Close() - data, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } 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 + } return nil, Error{StatusCode: resp.StatusCode, Status: resp.Status, msg: string(data)} } - return data, nil + + return resp.Body, nil } func (client *DockerClient) Info() (*Info, error) { @@ -212,6 +231,37 @@ func (client *DockerClient) ContainerChanges(id string) ([]*ContainerChanges, er return changes, nil } +func (client *DockerClient) readJSONStream(stream io.ReadCloser, decode func(*json.Decoder) decodingResult, stopChan <-chan struct{}) <-chan decodingResult { + resultChan := make(chan decodingResult) + + go func() { + decoder := json.NewDecoder(stream) + stopped := make(chan struct{}) + go func() { + <-stopChan + stream.Close() + stopped <- struct{}{} + }() + + defer close(resultChan) + for { + decodeResult := decode(decoder) + select { + case <-stopped: + return + default: + resultChan <- decodeResult + if decodeResult.err != nil { + stream.Close() + return + } + } + } + }() + + return resultChan +} + func (client *DockerClient) StartContainer(id string, config *HostConfig) error { data, err := json.Marshal(config) if err != nil { @@ -252,33 +302,86 @@ func (client *DockerClient) KillContainer(id, signal string) error { return nil } -func (client *DockerClient) StartMonitorEvents(cb Callback, ec chan error, args ...interface{}) { - atomic.StoreInt32(&client.monitorEvents, 1) - go client.getEvents(cb, ec, args...) -} - -func (client *DockerClient) getEvents(cb Callback, ec chan error, args ...interface{}) { - uri := fmt.Sprintf("%s/%s/events", client.URL.String(), APIVersion) +func (client *DockerClient) MonitorEvents(options *MonitorEventsOptions, stopChan <-chan struct{}) (<-chan EventOrError, 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, 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 { - ec <- err - return + return nil, err } - defer resp.Body.Close() - dec := json.NewDecoder(resp.Body) - for atomic.LoadInt32(&client.monitorEvents) > 0 { - var event *Event - if err := dec.Decode(&event); err != nil { + 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 := client.readJSONStream(resp.Body, decode, stopChan) + 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, nil +} + +func (client *DockerClient) StartMonitorEvents(cb Callback, ec chan error, args ...interface{}) { + client.eventStopChan = make(chan struct{}) + + go func() { + eventErrChan, err := client.MonitorEvents(nil, client.eventStopChan) + if err != nil { ec <- err return } - cb(event, ec, args...) - } + + for e := range eventErrChan { + if e.Error != nil { + ec <- err + return + } + cb(&e.Event, ec, args...) + } + }() } func (client *DockerClient) StopAllMonitorEvents() { - atomic.StoreInt32(&client.monitorEvents, 0) + close(client.eventStopChan) } func (client *DockerClient) StartMonitorStats(id string, cb StatCallback, ec chan error, args ...interface{}) { @@ -310,6 +413,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) @@ -349,6 +466,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 { @@ -444,3 +575,31 @@ 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) + 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/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient_test.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient_test.go index bb76ad8..0b57518 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient_test.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient_test.go @@ -2,7 +2,9 @@ package dockerclient import ( "bytes" + "encoding/json" "fmt" + "io" "reflect" "strings" "testing" @@ -155,6 +157,58 @@ 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) + } + } + + // 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) + 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) + } +} + func TestDockerClientInterface(t *testing.T) { iface := reflect.TypeOf((*Client)(nil)).Elem() test := testDockerClient(t) diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/engine_mock_test.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/engine_mock_test.go index 00fe441..114a71f 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/engine_mock_test.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/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" ) @@ -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)) } @@ -74,7 +75,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 { @@ -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/Godeps/_workspace/src/github.com/samalba/dockerclient/example_responses.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/example_responses.go index 9f683f1..670508c 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/example_responses.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/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/Godeps/_workspace/src/github.com/samalba/dockerclient/examples/events.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/examples/events.go index 07d05df..2d6de40 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/examples/events.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/examples/events.go @@ -12,10 +12,15 @@ func eventCallback(e *dockerclient.Event, ec chan error, args ...interface{}) { log.Println(e) } +var ( + client *dockerclient.DockerClient +) + func waitForInterrupt() { sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT) for _ = range sigChan { + client.StopAllMonitorEvents() os.Exit(0) } } @@ -26,7 +31,9 @@ func main() { log.Fatal(err) } - docker.StartMonitorEvents(eventCallback, nil) + client = docker + + client.StartMonitorEvents(eventCallback, nil) waitForInterrupt() } diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/interface.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/interface.go index 6267231..f3d3f14 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/interface.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/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) @@ -20,10 +21,16 @@ type Client interface { StopContainer(id string, timeout int) error RestartContainer(id string, timeout int) error KillContainer(id, signal string) 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{}) StopAllMonitorStats() + TagImage(nameOrID string, repo string, tag string, force bool) error Version() (*Version, error) PullImage(name string, auth *AuthConfig) error LoadImage(reader io.Reader) error @@ -32,4 +39,6 @@ type Client interface { RemoveImage(name string) ([]*ImageDelete, error) 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/Godeps/_workspace/src/github.com/samalba/dockerclient/mockclient/mock.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/mockclient/mock.go index d49fa37..0facc9c 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/mockclient/mock.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/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) @@ -65,6 +70,11 @@ func (client *MockClient) KillContainer(id, signal string) error { return args.Error(0) } +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{}) { client.Mock.Called(cb, ec, args) } @@ -73,6 +83,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) } @@ -125,3 +140,13 @@ 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) +} + +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) +} diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go index 5c73739..aef999d 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go @@ -1,19 +1,19 @@ package dockerclient -import "time" +import ( + "fmt" + "time" + + "github.com/docker/docker/pkg/units" +) type ContainerConfig struct { Hostname string Domainname string User string - Memory int64 - MemorySwap int64 - CpuShares int64 - Cpuset string AttachStdin bool AttachStdout bool AttachStderr bool - PortSpecs []string ExposedPorts map[string]struct{} Tty bool OpenStdin bool @@ -21,12 +21,21 @@ type ContainerConfig struct { Env []string Cmd []string Image string - Labels map[string]string Volumes map[string]struct{} + VolumeDriver string WorkingDir string Entrypoint []string NetworkDisabled bool + MacAddress string OnBuild []string + Labels map[string]string + + // FIXME: The following fields have been removed since API v1.18 + Memory int64 + MemorySwap int64 + CpuShares int64 + Cpuset string + PortSpecs []string // This is used only by the create command HostConfig HostConfig @@ -36,16 +45,42 @@ type HostConfig struct { Binds []string ContainerIDFile string LxcConf []map[string]string + Memory int64 + MemorySwap int64 + CpuShares int64 + CpuPeriod int64 + CpusetCpus string + CpusetMems string + CpuQuota int64 + BlkioWeight int64 + OomKillDisable bool Privileged bool PortBindings map[string][]PortBinding Links []string PublishAllPorts bool Dns []string DnsSearch []string + ExtraHosts []string VolumesFrom []string - SecurityOpt []string + Devices []DeviceMapping NetworkMode string + IpcMode string + PidMode string + UTSMode string + CapAdd []string + CapDrop []string RestartPolicy RestartPolicy + SecurityOpt []string + ReadonlyRootfs bool + Ulimits []Ulimit + LogConfig LogConfig + CgroupParent string +} + +type DeviceMapping struct { + PathOnHost string `json:"PathOnHost"` + PathInContainer string `json:"PathInContainer"` + CgroupPermissions string `json:"CgroupPermissions"` } type ExecConfig struct { @@ -66,6 +101,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 @@ -76,28 +123,94 @@ 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 - Pid int - ExitCode int - 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) String() 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 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 + Path string + Name string + Args []string + ExecIDs []string + Config *ContainerConfig + State *State Image string NetworkSettings struct { - IpAddress string - IpPrefixLen int + IPAddress string `json:"IpAddress"` + IPPrefixLen int `json:"IpPrefixLen"` Gateway string Bridge string Ports map[string][]PortBinding @@ -130,6 +243,7 @@ type Container struct { Ports []Port SizeRw int64 SizeRootFs int64 + Labels map[string]string } type Event struct { @@ -140,9 +254,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 { @@ -159,19 +277,40 @@ 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{} + BridgeNfIptables bool + BridgeNfIp6tables bool + DockerRootDir string + HttpProxy string + HttpsProxy string + NoProxy string } type ImageDelete struct { @@ -179,6 +318,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 @@ -255,3 +404,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"` +} diff --git a/docker.go b/docker.go index f4a03c3..ffd4449 100644 --- a/docker.go +++ b/docker.go @@ -75,7 +75,7 @@ func (d *DockerManager) getService(id string) (*Service, error) { service.Image = "" } service.Name = cleanContainerName(inspect.Name) - service.Ip = net.ParseIP(inspect.NetworkSettings.IpAddress) + service.Ip = net.ParseIP(inspect.NetworkSettings.IPAddress) service = overrideFromEnv(service, splitEnv(inspect.Config.Env)) if service == nil {