diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index fbdd79993b..d3b9bb334d 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -60,7 +60,7 @@ }, { "ImportPath": "github.com/samalba/dockerclient", - "Rev": "0fdc3ca0e58365801f1212900def9c7c60bbe2c7" + "Rev": "0689bcd74173c6abd6394b7ad435df46b0df26f8" }, { "ImportPath": "github.com/samuel/go-zookeeper/zk", diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient.go index 81e6aaeaa0..630672a771 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient.go @@ -333,10 +333,17 @@ func (client *DockerClient) ListImages() ([]*Image, error) { return images, nil } -func (client *DockerClient) RemoveImage(name string) error { +func (client *DockerClient) RemoveImage(name string) ([]*ImageDelete, error) { uri := fmt.Sprintf("/%s/images/%s", APIVersion, name) - _, err := client.doRequest("DELETE", uri, nil, nil) - return err + data, err := client.doRequest("DELETE", uri, nil, nil) + if err != nil { + return nil, err + } + var imageDelete []*ImageDelete + if err := json.Unmarshal(data, &imageDelete); err != nil { + return nil, err + } + return imageDelete, nil } func (client *DockerClient) PauseContainer(id string) error { diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/interface.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/interface.go index bab13aa3ea..ef6684eeb8 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/interface.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/interface.go @@ -23,7 +23,7 @@ type Client interface { PullImage(name string, auth *AuthConfig) error RemoveContainer(id string, force, volumes bool) error ListImages() ([]*Image, error) - RemoveImage(name string) error + RemoveImage(name string) ([]*ImageDelete, error) PauseContainer(name string) error UnpauseContainer(name 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 4d16dcaa93..5ff76db313 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/mockclient/mock.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/mockclient/mock.go @@ -88,9 +88,9 @@ func (client *MockClient) ListImages() ([]*dockerclient.Image, error) { return args.Get(0).([]*dockerclient.Image), args.Error(1) } -func (client *MockClient) RemoveImage(name string) error { +func (client *MockClient) RemoveImage(name string) ([]*dockerclient.ImageDelete, error) { args := client.Mock.Called(name) - return args.Error(0) + return args.Get(0).([]*dockerclient.ImageDelete), args.Error(1) } func (client *MockClient) PauseContainer(name string) error { diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go index 6504e8fa3b..a3dcd123fa 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go @@ -166,3 +166,8 @@ type Info struct { Name string Labels []string } + +type ImageDelete struct { + Deleted string + Untagged string +} diff --git a/api/README.md b/api/README.md index 95a49e9e45..cdbeaf558d 100644 --- a/api/README.md +++ b/api/README.md @@ -22,8 +22,6 @@ POST "/images/create" (pull implemented) POST "/images/load" POST "/images/{name:.*}/push" POST "/images/{name:.*}/tag" - -DELETE "/images/{name:.*}" ``` ## Endpoints which behave differently diff --git a/api/api.go b/api/api.go index 4179fca68c..3ee203afae 100644 --- a/api/api.go +++ b/api/api.go @@ -330,6 +330,46 @@ func postContainersExec(c *context, w http.ResponseWriter, r *http.Request) { w.Write(data) } +// DELETE /images/{name:.*} +func deleteImages(c *context, w http.ResponseWriter, r *http.Request) { + if err := r.ParseForm(); err != nil { + httpError(w, err.Error(), http.StatusInternalServerError) + return + } + var name = mux.Vars(r)["name"] + + matchedImages := []*cluster.Image{} + for _, image := range c.cluster.Images() { + if image.Match(name) { + matchedImages = append(matchedImages, image) + } + } + + if len(matchedImages) == 0 { + httpError(w, fmt.Sprintf("No such image %s", name), http.StatusNotFound) + return + } + + out := []*dockerclient.ImageDelete{} + errs := []string{} + for _, image := range matchedImages { + content, err := c.cluster.RemoveImage(image) + if err != nil { + errs = append(errs, fmt.Sprintf("%s: %s", image.Node.Name(), err.Error())) + continue + } + out = append(out, content...) + } + + if len(errs) != 0 { + httpError(w, strings.Join(errs, ""), http.StatusInternalServerError) + } else { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(NewWriteFlusher(w)).Encode(out) + } + +} + // GET /_ping func ping(c *context, w http.ResponseWriter, r *http.Request) { w.Write([]byte{'O', 'K'}) @@ -467,7 +507,7 @@ func createRouter(c *context, enableCors bool) *mux.Router { }, "DELETE": { "/containers/{name:.*}": deleteContainers, - "/images/{name:.*}": notImplementedHandler, + "/images/{name:.*}": deleteImages, }, "OPTIONS": { "": optionsHandler, diff --git a/cluster/cluster.go b/cluster/cluster.go index 1844363b35..090a52e59d 100644 --- a/cluster/cluster.go +++ b/cluster/cluster.go @@ -15,6 +15,9 @@ type Cluster interface { // Return one image matching `IdOrName` Image(IdOrName string) *Image + // Remove an image from the cluster + RemoveImage(image *Image) ([]*dockerclient.ImageDelete, error) + // Return all containers Containers() []*Container diff --git a/cluster/image.go b/cluster/image.go index c640209e53..3c2e603446 100644 --- a/cluster/image.go +++ b/cluster/image.go @@ -1,9 +1,27 @@ package cluster -import "github.com/samalba/dockerclient" +import ( + "strings" + + "github.com/samalba/dockerclient" +) type Image struct { dockerclient.Image Node Node } + +func (image *Image) Match(IdOrName string) bool { + size := len(IdOrName) + + if image.Id == IdOrName || (size > 2 && strings.HasPrefix(image.Id, IdOrName)) { + return true + } + for _, repoTag := range image.RepoTags { + if repoTag == IdOrName || (size > 2 && strings.HasPrefix(repoTag, IdOrName)) { + return true + } + } + return false +} diff --git a/cluster/swarm/cluster.go b/cluster/swarm/cluster.go index a9c2c50fd9..588c5cc6a7 100644 --- a/cluster/swarm/cluster.go +++ b/cluster/swarm/cluster.go @@ -173,7 +173,7 @@ func (c *Cluster) getNode(addr string) *node { return nil } -// Containers returns all the images in the cluster. +// Images returns all the images in the cluster. func (c *Cluster) Images() []*cluster.Image { c.RLock() defer c.RUnlock() @@ -204,6 +204,16 @@ func (c *Cluster) Image(IdOrName string) *cluster.Image { return nil } +// RemoveImage removes an image from the cluster +func (c *Cluster) RemoveImage(image *cluster.Image) ([]*dockerclient.ImageDelete, error) { + c.Lock() + defer c.Unlock() + if n, ok := image.Node.(*node); ok { + return n.removeImage(image) + } + return nil, nil +} + func (c *Cluster) Pull(name string, callback func(what, status string)) { size := len(c.nodes) done := make(chan bool, size) diff --git a/cluster/swarm/node.go b/cluster/swarm/node.go index 310769b74e..bd67309f29 100644 --- a/cluster/swarm/node.go +++ b/cluster/swarm/node.go @@ -163,6 +163,11 @@ func (n *node) updateSpecs() error { return nil } +// Delete an image from the node. +func (n *node) removeImage(image *cluster.Image) ([]*dockerclient.ImageDelete, error) { + return n.client.RemoveImage(image.Id) +} + // Refresh the list of images on the node. func (n *node) refreshImages() error { images, err := n.client.ListImages() @@ -459,9 +464,11 @@ func (n *node) Container(IdOrName string) *cluster.Container { return nil } +// Images returns all the images in the node func (n *node) Images() []*cluster.Image { images := []*cluster.Image{} n.RLock() + for _, image := range n.images { images = append(images, image) } @@ -474,16 +481,10 @@ func (n *node) Image(IdOrName string) *cluster.Image { n.RLock() defer n.RUnlock() - size := len(IdOrName) - for _, image := range n.Images() { - if image.Id == IdOrName || (size > 2 && strings.HasPrefix(image.Id, IdOrName)) { + for _, image := range n.images { + if image.Match(IdOrName) { return image } - for _, t := range image.RepoTags { - if t == IdOrName || (size > 2 && strings.HasPrefix(t, IdOrName)) { - return image - } - } } return nil }