From f5ac0c87e3e62133760325e598509d591163a516 Mon Sep 17 00:00:00 2001 From: Xian Chaobo Date: Wed, 19 Aug 2015 22:02:59 -0400 Subject: [PATCH 1/2] update godeps Signed-off-by: Xian Chaobo --- Godeps/Godeps.json | 2 +- .../github.com/samalba/dockerclient/README.md | 15 +++ .../samalba/dockerclient/dockerclient.go | 104 +++++++++++++----- .../samalba/dockerclient/dockerclient_test.go | 21 ++++ .../samalba/dockerclient/engine_mock_test.go | 10 ++ .../samalba/dockerclient/interface.go | 7 +- .../samalba/dockerclient/mockclient/mock.go | 21 +++- .../github.com/samalba/dockerclient/types.go | 5 + 8 files changed, 153 insertions(+), 32 deletions(-) diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 1c83f669d8..f50e0d661c 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -104,7 +104,7 @@ }, { "ImportPath": "github.com/samalba/dockerclient", - "Rev": "68832c185bb6304fed26a986891fd27b6ed17987" + "Rev": "8802d66ce78e69ff16692f6fa89ad969a5711b8b" }, { "ImportPath": "github.com/samuel/go-zookeeper/zk", diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/README.md b/Godeps/_workspace/src/github.com/samalba/dockerclient/README.md index 046a9ce694..5a5027b8b7 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/README.md +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/README.md @@ -15,6 +15,7 @@ import ( "github.com/samalba/dockerclient" "log" "time" + "os" ) // Callback used to listen to Docker's events @@ -42,6 +43,20 @@ func main() { log.Println(info) } + // Build a docker image + // some.tar contains the build context (Dockerfile any any files it needs to add/copy) + dockerBuildContext, err := os.Open("some.tar") + defer dockerBuildContext.Close() + buildImageConfig := &dockerclient.BuildImage{ + Context: dockerBuildContext, + RepoName: "your_image_name", + SuppressOutput: false, + } + reader, err := docker.BuildImage(buildImageConfig) + if err != nil { + log.Fatal(err) + } + // Create a container containerConfig := &dockerclient.ContainerConfig{ Image: "ubuntu:14.04", diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient.go index e96d27468a..db21d9fa51 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient.go @@ -272,6 +272,56 @@ func (client *DockerClient) readJSONStream(stream io.ReadCloser, decode func(*js return resultChan } +func (client *DockerClient) ExecCreate(config *ExecConfig) (string, error) { + data, err := json.Marshal(config) + if err != nil { + return "", err + } + uri := fmt.Sprintf("/%s/containers/%s/exec", APIVersion, config.Container) + resp, err := client.doRequest("POST", uri, data, nil) + if err != nil { + return "", err + } + var createExecResp struct { + Id string + } + if err = json.Unmarshal(resp, &createExecResp); err != nil { + return "", err + } + return createExecResp.Id, nil +} + +func (client *DockerClient) ExecStart(id string, config *ExecConfig) error { + data, err := json.Marshal(config) + if err != nil { + return err + } + + uri := fmt.Sprintf("/%s/exec/%s/start", APIVersion, id) + if _, err := client.doRequest("POST", uri, data, nil); err != nil { + return err + } + + return nil +} + +func (client *DockerClient) ExecResize(id string, width, height int) error { + v := url.Values{} + + w := strconv.Itoa(width) + h := strconv.Itoa(height) + + v.Set("w", w) + v.Set("h", h) + + uri := fmt.Sprintf("/%s/exec/%s/resize?%s", APIVersion, id, v.Encode()) + if _, err := client.doRequest("POST", client.URL.String()+uri, nil, nil); err != nil { + return err + } + + return nil +} + func (client *DockerClient) StartContainer(id string, config *HostConfig) error { data, err := json.Marshal(config) if err != nil { @@ -312,6 +362,26 @@ func (client *DockerClient) KillContainer(id, signal string) error { return nil } +func (client *DockerClient) Wait(id string) <-chan WaitResult { + ch := make(chan WaitResult) + uri := fmt.Sprintf("/%s/containers/%s/wait", APIVersion, id) + + go func() { + data, err := client.doRequest("POST", uri, nil, nil) + if err != nil { + ch <- WaitResult{ExitCode: -1, Error: err} + return + } + + var result struct { + StatusCode int `json:"StatusCode"` + } + err = json.Unmarshal(data, &result) + ch <- WaitResult{ExitCode: result.StatusCode, Error: err} + }() + return ch +} + func (client *DockerClient) MonitorEvents(options *MonitorEventsOptions, stopChan <-chan struct{}) (<-chan EventOrError, error) { v := url.Values{} if options != nil { @@ -556,8 +626,14 @@ func (client *DockerClient) ListImages(all bool) ([]*Image, error) { return images, nil } -func (client *DockerClient) RemoveImage(name string) ([]*ImageDelete, error) { - uri := fmt.Sprintf("/%s/images/%s", APIVersion, name) +func (client *DockerClient) RemoveImage(name string, force bool) ([]*ImageDelete, error) { + argForce := 0 + if force { + argForce = 1 + } + + args := fmt.Sprintf("force=%d", argForce) + uri := fmt.Sprintf("/%s/images/%s?%s", APIVersion, name, args) data, err := client.doRequest("DELETE", uri, nil, nil) if err != nil { return nil, err @@ -586,30 +662,6 @@ func (client *DockerClient) UnpauseContainer(id string) error { return nil } -func (client *DockerClient) Exec(config *ExecConfig) (string, error) { - data, err := json.Marshal(config) - if err != nil { - return "", err - } - uri := fmt.Sprintf("/containers/%s/exec", config.Container) - resp, err := client.doRequest("POST", uri, data, nil) - if err != nil { - return "", err - } - var createExecResp struct { - Id string - } - if err = json.Unmarshal(resp, &createExecResp); err != nil { - return "", err - } - uri = fmt.Sprintf("/exec/%s/start", createExecResp.Id) - resp, err = client.doRequest("POST", uri, data, nil) - if err != nil { - return "", err - } - 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) 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 0b57518719..88257e01b7 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient_test.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient_test.go @@ -8,6 +8,7 @@ import ( "reflect" "strings" "testing" + "time" "github.com/docker/docker/pkg/stdcopy" ) @@ -47,6 +48,26 @@ func TestKillContainer(t *testing.T) { } } +func TestWait(t *testing.T) { + client := testDockerClient(t) + + // This provokes an error on the server. + select { + case wr := <-client.Wait("1234"): + assertEqual(t, wr.ExitCode, int(-1), "") + case <-time.After(2 * time.Second): + t.Fatal("Timed out!") + } + + // Valid case. + select { + case wr := <-client.Wait("valid-id"): + assertEqual(t, wr.ExitCode, int(0), "") + case <-time.After(2 * time.Second): + t.Fatal("Timed out!") + } +} + func TestPullImage(t *testing.T) { client := testDockerClient(t) err := client.PullImage("busybox", nil) 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 65d94f4c56..7d3a6d93b8 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 @@ -29,6 +29,7 @@ func init() { r.HandleFunc(baseURL+"/containers/{id}/logs", handleContainerLogs).Methods("GET") r.HandleFunc(baseURL+"/containers/{id}/changes", handleContainerChanges).Methods("GET") r.HandleFunc(baseURL+"/containers/{id}/kill", handleContainerKill).Methods("POST") + r.HandleFunc(baseURL+"/containers/{id}/wait", handleWait).Methods("POST") r.HandleFunc(baseURL+"/images/create", handleImagePull).Methods("POST") r.HandleFunc(baseURL+"/events", handleEvents).Methods("GET") testHTTPServer = httptest.NewServer(handlerAccessLog(r)) @@ -46,6 +47,15 @@ func handleContainerKill(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "{%q:%q", "Id", "421373210afd132") } +func handleWait(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + if vars["id"] == "valid-id" { + fmt.Fprintf(w, `{"StatusCode":0}`) + } else { + http.Error(w, "failed", 500) + } +} + func handleImagePull(w http.ResponseWriter, r *http.Request) { imageName := r.URL.Query()["fromImage"][0] responses := []map[string]interface{}{{ diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/interface.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/interface.go index 17f0641abc..1ccc53c30e 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/interface.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/interface.go @@ -16,11 +16,14 @@ type Client interface { CreateContainer(config *ContainerConfig, name string) (string, error) ContainerLogs(id string, options *LogOptions) (io.ReadCloser, error) ContainerChanges(id string) ([]*ContainerChanges, error) - Exec(config *ExecConfig) (string, error) + ExecCreate(config *ExecConfig) (string, error) + ExecStart(id string, config *ExecConfig) error + ExecResize(id string, width, height int) error StartContainer(id string, config *HostConfig) error StopContainer(id string, timeout int) error RestartContainer(id string, timeout int) error KillContainer(id, signal string) error + Wait(id string) <-chan WaitResult // 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 @@ -36,7 +39,7 @@ type Client interface { LoadImage(reader io.Reader) error RemoveContainer(id string, force, volumes bool) error ListImages(all bool) ([]*Image, error) - RemoveImage(name string) ([]*ImageDelete, error) + RemoveImage(name string, force bool) ([]*ImageDelete, error) PauseContainer(name string) error UnpauseContainer(name string) error RenameContainer(oldName string, newName string) 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 6babeea6f1..aed1a16baa 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/mockclient/mock.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/mockclient/mock.go @@ -70,6 +70,11 @@ func (client *MockClient) KillContainer(id, signal string) error { return args.Error(0) } +func (client *MockClient) Wait(id string) <-chan dockerclient.WaitResult { + args := client.Mock.Called(id) + return args.Get(0).(<-chan dockerclient.WaitResult) +} + 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) @@ -121,8 +126,8 @@ func (client *MockClient) ListImages(all bool) ([]*dockerclient.Image, error) { return args.Get(0).([]*dockerclient.Image), args.Error(1) } -func (client *MockClient) RemoveImage(name string) ([]*dockerclient.ImageDelete, error) { - args := client.Mock.Called(name) +func (client *MockClient) RemoveImage(name string, force bool) ([]*dockerclient.ImageDelete, error) { + args := client.Mock.Called(name, force) return args.Get(0).([]*dockerclient.ImageDelete), args.Error(1) } @@ -136,11 +141,21 @@ func (client *MockClient) UnpauseContainer(name string) error { return args.Error(0) } -func (client *MockClient) Exec(config *dockerclient.ExecConfig) (string, error) { +func (client *MockClient) ExecCreate(config *dockerclient.ExecConfig) (string, error) { args := client.Mock.Called(config) return args.String(0), args.Error(1) } +func (client *MockClient) ExecStart(id string, config *dockerclient.ExecConfig) error { + args := client.Mock.Called(id, config) + return args.Error(0) +} + +func (client *MockClient) ExecResize(id string, width, height int) error { + args := client.Mock.Called(id, width, height) + return args.Error(0) +} + func (client *MockClient) RenameContainer(oldName string, newName string) error { args := client.Mock.Called(oldName, newName) return args.Error(0) diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go index e1b5527993..15e1e8bcdf 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go @@ -324,6 +324,11 @@ type EventOrError struct { Error error } +type WaitResult struct { + ExitCode int + Error error +} + type decodingResult struct { result interface{} err error From 12e956e681f1d12bc16af8a2f868723301db5615 Mon Sep 17 00:00:00 2001 From: Xian Chaobo Date: Wed, 19 Aug 2015 23:09:02 -0400 Subject: [PATCH 2/2] add support force remove images Signed-off-by: Xian Chaobo --- api/handlers.go | 3 ++- cluster/cluster.go | 2 +- cluster/engine.go | 4 ++-- cluster/mesos/cluster.go | 2 +- cluster/swarm/cluster.go | 4 ++-- test/integration/api/rmi.bats | 24 ++++++++++++++++++++++++ 6 files changed, 32 insertions(+), 7 deletions(-) diff --git a/api/handlers.go b/api/handlers.go index f2d39f57a1..4d0a939419 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -511,8 +511,9 @@ func deleteImages(c *context, w http.ResponseWriter, r *http.Request) { return } var name = mux.Vars(r)["name"] + force := boolValue(r, "force") - out, err := c.cluster.RemoveImages(name) + out, err := c.cluster.RemoveImages(name, force) if err != nil { httpError(w, err.Error(), http.StatusInternalServerError) return diff --git a/cluster/cluster.go b/cluster/cluster.go index f50262334b..b24bf26eab 100644 --- a/cluster/cluster.go +++ b/cluster/cluster.go @@ -21,7 +21,7 @@ type Cluster interface { Image(IDOrName string) *Image // Remove images from the cluster - RemoveImages(name string) ([]*dockerclient.ImageDelete, error) + RemoveImages(name string, force bool) ([]*dockerclient.ImageDelete, error) // Return all containers Containers() Containers diff --git a/cluster/engine.go b/cluster/engine.go index f010121a04..cdfe468234 100644 --- a/cluster/engine.go +++ b/cluster/engine.go @@ -179,8 +179,8 @@ func (e *Engine) updateSpecs() error { } // RemoveImage deletes an image from the engine. -func (e *Engine) RemoveImage(image *Image, name string) ([]*dockerclient.ImageDelete, error) { - return e.client.RemoveImage(name) +func (e *Engine) RemoveImage(image *Image, name string, force bool) ([]*dockerclient.ImageDelete, error) { + return e.client.RemoveImage(name, force) } // RefreshImages refreshes the list of images on the engine. diff --git a/cluster/mesos/cluster.go b/cluster/mesos/cluster.go index 434c1da55a..199342990e 100644 --- a/cluster/mesos/cluster.go +++ b/cluster/mesos/cluster.go @@ -221,7 +221,7 @@ func (c *Cluster) Image(IDOrName string) *cluster.Image { } // RemoveImages removes images from the cluster -func (c *Cluster) RemoveImages(name string) ([]*dockerclient.ImageDelete, error) { +func (c *Cluster) RemoveImages(name string, force bool) ([]*dockerclient.ImageDelete, error) { return nil, errNotSupported } diff --git a/cluster/swarm/cluster.go b/cluster/swarm/cluster.go index 9e990ebe0f..e4fb7dab59 100644 --- a/cluster/swarm/cluster.go +++ b/cluster/swarm/cluster.go @@ -290,7 +290,7 @@ func (c *Cluster) Image(IDOrName string) *cluster.Image { } // RemoveImages removes all the images that match `name` from the cluster -func (c *Cluster) RemoveImages(name string) ([]*dockerclient.ImageDelete, error) { +func (c *Cluster) RemoveImages(name string, force bool) ([]*dockerclient.ImageDelete, error) { c.Lock() defer c.Unlock() @@ -300,7 +300,7 @@ func (c *Cluster) RemoveImages(name string) ([]*dockerclient.ImageDelete, error) for _, e := range c.engines { for _, image := range e.Images(true) { if image.Match(name, true) { - content, err := image.Engine.RemoveImage(image, name) + content, err := image.Engine.RemoveImage(image, name, force) if err != nil { errs = append(errs, fmt.Sprintf("%s: %s", image.Engine.Name, err.Error())) continue diff --git a/test/integration/api/rmi.bats b/test/integration/api/rmi.bats index ca4605cc44..79960ba395 100644 --- a/test/integration/api/rmi.bats +++ b/test/integration/api/rmi.bats @@ -78,3 +78,27 @@ function teardown() { [[ "${output}" == *"busybox"* ]] [[ "${output}" != *"testimage"* ]] } + +@test "docker rmi --force" { + start_docker_with_busybox 1 + start_docker 1 + + swarm_manage + + # make sure same image id have two repo-tags + docker_swarm tag busybox:latest testimage:latest + + run docker_swarm images + [[ "${output}" == *"busybox"* ]] + [[ "${output}" == *"testimage"* ]] + + # get busybox image id + busybox_id=`docker_swarm inspect --format='{{.Id}}' busybox:latest` + + # test rmi with force + docker_swarm rmi -f ${busybox_id} + + run docker_swarm images + [[ "${output}" != *"busybox"* ]] + [[ "${output}" != *"testimage"* ]] +}