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..5571620eea 100644 --- a/api/api.go +++ b/api/api.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "encoding/json" "fmt" + "io" "io/ioutil" "math/rand" "net/http" @@ -89,7 +90,7 @@ func getImagesJSON(c *context, w http.ResponseWriter, r *http.Request) { accepteds, _ := filters["node"] images := []*cluster.Image{} - for _, image := range c.cluster.Images() { + for _, image := range c.cluster.Images("") { if len(accepteds) != 0 { found := false for _, accepted := range accepteds { @@ -330,6 +331,69 @@ 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"] + + client, scheme := newClientAndScheme(c.tlsConfig) + defer closeIdleConnections(client) + + images := c.cluster.Images(name) + size := len(images) + if size == 0 { + httpError(w, fmt.Sprintf("No such image %s", name), http.StatusNotFound) + return + } + + for i, image := range images { + req, err := http.NewRequest("DELETE", scheme+"://"+image.Node.Addr()+"/images/"+name, nil) + if err != nil { + if size == 1 { + httpError(w, err.Error(), http.StatusInternalServerError) + } + continue + } + resp, err := client.Do(req) + if err != nil { + if size == 1 { + httpError(w, err.Error(), http.StatusInternalServerError) + } + continue + } + + data, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + if size == 1 { + httpError(w, err.Error(), http.StatusInternalServerError) + } + continue + } + if resp.StatusCode != 200 { + if size == 1 { + w.WriteHeader(resp.StatusCode) + io.Copy(NewWriteFlusher(w), resp.Body) + } + continue + } + + sdata := bytes.NewBuffer(data).String() + if i != 0 { + w.Header().Set("Content-Type", "application/json") + sdata = strings.Replace(sdata, "[", ",", -1) + } + + if i != len(images)-1 { + sdata = strings.Replace(sdata, "]", "", -1) + } + fmt.Fprintf(w, sdata) + } +} + // GET /_ping func ping(c *context, w http.ResponseWriter, r *http.Request) { w.Write([]byte{'O', 'K'}) @@ -467,7 +531,7 @@ func createRouter(c *context, enableCors bool) *mux.Router { }, "DELETE": { "/containers/{name:.*}": deleteContainers, - "/images/{name:.*}": notImplementedHandler, + "/images/{name:.*}": deleteImages, }, "OPTIONS": { "": optionsHandler, diff --git a/api/events_test.go b/api/events_test.go index eafbdd7639..0c2cbb1bbe 100644 --- a/api/events_test.go +++ b/api/events_test.go @@ -23,7 +23,7 @@ func (fn *FakeNode) ID() string { return "node_id" } func (fn *FakeNode) Name() string { return "node_name" } func (fn *FakeNode) IP() string { return "node_ip" } func (fn *FakeNode) Addr() string { return "node_addr" } -func (fn *FakeNode) Images() []*cluster.Image { return nil } +func (fn *FakeNode) Images(_ string) []*cluster.Image { return nil } func (fn *FakeNode) Image(_ string) *cluster.Image { return nil } func (fn *FakeNode) Containers() []*cluster.Container { return nil } func (fn *FakeNode) Container(_ string) *cluster.Container { return nil } diff --git a/cluster/cluster.go b/cluster/cluster.go index 1844363b35..258d4f09e3 100644 --- a/cluster/cluster.go +++ b/cluster/cluster.go @@ -9,8 +9,9 @@ type Cluster interface { // Remove a container RemoveContainer(container *Container, force bool) error - // Return all images - Images() []*Image + // Return all images matching `name` + // if `name` == "" return all images in the cluster + Images(name string) []*Image // Return one image matching `IdOrName` Image(IdOrName string) *Image diff --git a/cluster/node.go b/cluster/node.go index a27b73c547..830a59b232 100644 --- a/cluster/node.go +++ b/cluster/node.go @@ -9,7 +9,7 @@ type Node interface { IP() string //to inject the actual IP of the machine in docker ps (hostname:port or ip:port) Addr() string //to know where to connect with the proxy - Images() []*Image //used by the API + Images(name string) []*Image //used by the API Image(IdOrName string) *Image //used by the filters Containers() []*Container //used by the filters Container(IdOrName string) *Container //used by the filters diff --git a/cluster/swarm/cluster.go b/cluster/swarm/cluster.go index c50b49085c..564cc577e6 100644 --- a/cluster/swarm/cluster.go +++ b/cluster/swarm/cluster.go @@ -156,13 +156,14 @@ func (c *Cluster) getNode(addr string) *node { } // Containers returns all the images in the cluster. -func (c *Cluster) Images() []*cluster.Image { +// If `name` is empty, return all the images +func (c *Cluster) Images(name string) []*cluster.Image { c.RLock() defer c.RUnlock() out := []*cluster.Image{} for _, n := range c.nodes { - out = append(out, n.Images()...) + out = append(out, n.Images(name)...) } return out diff --git a/cluster/swarm/node.go b/cluster/swarm/node.go index 310769b74e..29024e25aa 100644 --- a/cluster/swarm/node.go +++ b/cluster/swarm/node.go @@ -459,11 +459,30 @@ func (n *node) Container(IdOrName string) *cluster.Container { return nil } -func (n *node) Images() []*cluster.Image { +func matchImage(image *cluster.Image, 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 +} + +// Images returns a list for images matching name in the node +// If `name` is empty, returns all the images +func (n *node) Images(name string) []*cluster.Image { images := []*cluster.Image{} n.RLock() + for _, image := range n.images { - images = append(images, image) + if name == "" || matchImage(image, name) { + images = append(images, image) + } } n.RUnlock() return images @@ -474,16 +493,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 matchImage(image, IdOrName) { return image } - for _, t := range image.RepoTags { - if t == IdOrName || (size > 2 && strings.HasPrefix(t, IdOrName)) { - return image - } - } } return nil } diff --git a/scheduler/filter/affinity.go b/scheduler/filter/affinity.go index 223c0fa326..403f42711c 100644 --- a/scheduler/filter/affinity.go +++ b/scheduler/filter/affinity.go @@ -35,7 +35,7 @@ func (f *AffinityFilter) Filter(config *dockerclient.ContainerConfig, nodes []cl } case "image": images := []string{} - for _, image := range node.Images() { + for _, image := range node.Images("") { images = append(images, image.Id) images = append(images, image.RepoTags...) for _, tag := range image.RepoTags { diff --git a/scheduler/filter/fakenode_test.go b/scheduler/filter/fakenode_test.go index 54dea30915..8eefbcbf56 100644 --- a/scheduler/filter/fakenode_test.go +++ b/scheduler/filter/fakenode_test.go @@ -11,11 +11,11 @@ type FakeNode struct { labels map[string]string } -func (fn *FakeNode) ID() string { return fn.id } -func (fn *FakeNode) Name() string { return fn.name } -func (fn *FakeNode) IP() string { return "" } -func (fn *FakeNode) Addr() string { return fn.addr } -func (fn *FakeNode) Images() []*cluster.Image { return fn.images } +func (fn *FakeNode) ID() string { return fn.id } +func (fn *FakeNode) Name() string { return fn.name } +func (fn *FakeNode) IP() string { return "" } +func (fn *FakeNode) Addr() string { return fn.addr } +func (fn *FakeNode) Images(_ string) []*cluster.Image { return fn.images } func (fn *FakeNode) Image(id string) *cluster.Image { for _, image := range fn.images { if image.Id == id { diff --git a/scheduler/strategy/fakenode_test.go b/scheduler/strategy/fakenode_test.go index bd5c997c08..6d2ed7f5d0 100644 --- a/scheduler/strategy/fakenode_test.go +++ b/scheduler/strategy/fakenode_test.go @@ -21,7 +21,7 @@ func (fn *FakeNode) ID() string { return fn.id } func (fn *FakeNode) Name() string { return fn.name } func (fn *FakeNode) IP() string { return "" } func (fn *FakeNode) Addr() string { return fn.addr } -func (fn *FakeNode) Images() []*cluster.Image { return nil } +func (fn *FakeNode) Images(_ string) []*cluster.Image { return nil } func (fn *FakeNode) Image(_ string) *cluster.Image { return nil } func (fn *FakeNode) Containers() []*cluster.Container { return fn.containers } func (fn *FakeNode) Container(_ string) *cluster.Container { return nil }