Merge pull request #446 from vieux/rmi

add docker rmi
This commit is contained in:
Alexandre Beslic 2015-03-24 16:04:35 -07:00
commit 904c720ea5
11 changed files with 102 additions and 20 deletions

2
Godeps/Godeps.json generated
View File

@ -60,7 +60,7 @@
}, },
{ {
"ImportPath": "github.com/samalba/dockerclient", "ImportPath": "github.com/samalba/dockerclient",
"Rev": "0fdc3ca0e58365801f1212900def9c7c60bbe2c7" "Rev": "0689bcd74173c6abd6394b7ad435df46b0df26f8"
}, },
{ {
"ImportPath": "github.com/samuel/go-zookeeper/zk", "ImportPath": "github.com/samuel/go-zookeeper/zk",

View File

@ -333,10 +333,17 @@ func (client *DockerClient) ListImages() ([]*Image, error) {
return images, nil 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) uri := fmt.Sprintf("/%s/images/%s", APIVersion, name)
_, err := client.doRequest("DELETE", uri, nil, nil) data, err := client.doRequest("DELETE", uri, nil, nil)
return err 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 { func (client *DockerClient) PauseContainer(id string) error {

View File

@ -23,7 +23,7 @@ type Client interface {
PullImage(name string, auth *AuthConfig) error PullImage(name string, auth *AuthConfig) error
RemoveContainer(id string, force, volumes bool) error RemoveContainer(id string, force, volumes bool) error
ListImages() ([]*Image, error) ListImages() ([]*Image, error)
RemoveImage(name string) error RemoveImage(name string) ([]*ImageDelete, error)
PauseContainer(name string) error PauseContainer(name string) error
UnpauseContainer(name string) error UnpauseContainer(name string) error
} }

View File

@ -88,9 +88,9 @@ func (client *MockClient) ListImages() ([]*dockerclient.Image, error) {
return args.Get(0).([]*dockerclient.Image), args.Error(1) 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) 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 { func (client *MockClient) PauseContainer(name string) error {

View File

@ -166,3 +166,8 @@ type Info struct {
Name string Name string
Labels []string Labels []string
} }
type ImageDelete struct {
Deleted string
Untagged string
}

View File

@ -22,8 +22,6 @@ POST "/images/create" (pull implemented)
POST "/images/load" POST "/images/load"
POST "/images/{name:.*}/push" POST "/images/{name:.*}/push"
POST "/images/{name:.*}/tag" POST "/images/{name:.*}/tag"
DELETE "/images/{name:.*}"
``` ```
## Endpoints which behave differently ## Endpoints which behave differently

View File

@ -330,6 +330,46 @@ func postContainersExec(c *context, w http.ResponseWriter, r *http.Request) {
w.Write(data) 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 // GET /_ping
func ping(c *context, w http.ResponseWriter, r *http.Request) { func ping(c *context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte{'O', 'K'}) w.Write([]byte{'O', 'K'})
@ -467,7 +507,7 @@ func createRouter(c *context, enableCors bool) *mux.Router {
}, },
"DELETE": { "DELETE": {
"/containers/{name:.*}": deleteContainers, "/containers/{name:.*}": deleteContainers,
"/images/{name:.*}": notImplementedHandler, "/images/{name:.*}": deleteImages,
}, },
"OPTIONS": { "OPTIONS": {
"": optionsHandler, "": optionsHandler,

View File

@ -15,6 +15,9 @@ type Cluster interface {
// Return one image matching `IdOrName` // Return one image matching `IdOrName`
Image(IdOrName string) *Image Image(IdOrName string) *Image
// Remove an image from the cluster
RemoveImage(image *Image) ([]*dockerclient.ImageDelete, error)
// Return all containers // Return all containers
Containers() []*Container Containers() []*Container

View File

@ -1,9 +1,27 @@
package cluster package cluster
import "github.com/samalba/dockerclient" import (
"strings"
"github.com/samalba/dockerclient"
)
type Image struct { type Image struct {
dockerclient.Image dockerclient.Image
Node Node 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
}

View File

@ -173,7 +173,7 @@ func (c *Cluster) getNode(addr string) *node {
return nil return nil
} }
// Containers returns all the images in the cluster. // Images returns all the images in the cluster.
func (c *Cluster) Images() []*cluster.Image { func (c *Cluster) Images() []*cluster.Image {
c.RLock() c.RLock()
defer c.RUnlock() defer c.RUnlock()
@ -204,6 +204,16 @@ func (c *Cluster) Image(IdOrName string) *cluster.Image {
return nil 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)) { func (c *Cluster) Pull(name string, callback func(what, status string)) {
size := len(c.nodes) size := len(c.nodes)
done := make(chan bool, size) done := make(chan bool, size)

View File

@ -163,6 +163,11 @@ func (n *node) updateSpecs() error {
return nil 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. // Refresh the list of images on the node.
func (n *node) refreshImages() error { func (n *node) refreshImages() error {
images, err := n.client.ListImages() images, err := n.client.ListImages()
@ -459,9 +464,11 @@ func (n *node) Container(IdOrName string) *cluster.Container {
return nil return nil
} }
// Images returns all the images in the node
func (n *node) Images() []*cluster.Image { func (n *node) Images() []*cluster.Image {
images := []*cluster.Image{} images := []*cluster.Image{}
n.RLock() n.RLock()
for _, image := range n.images { for _, image := range n.images {
images = append(images, image) images = append(images, image)
} }
@ -474,16 +481,10 @@ func (n *node) Image(IdOrName string) *cluster.Image {
n.RLock() n.RLock()
defer n.RUnlock() defer n.RUnlock()
size := len(IdOrName) for _, image := range n.images {
for _, image := range n.Images() { if image.Match(IdOrName) {
if image.Id == IdOrName || (size > 2 && strings.HasPrefix(image.Id, IdOrName)) {
return image return image
} }
for _, t := range image.RepoTags {
if t == IdOrName || (size > 2 && strings.HasPrefix(t, IdOrName)) {
return image
}
}
} }
return nil return nil
} }