Merge pull request #1162 from jimmyxian/add-support-force-remove-image

Add support force remove image
This commit is contained in:
Alexandre Beslic 2015-08-19 20:41:58 -07:00
commit fc0cc899e6
14 changed files with 185 additions and 39 deletions

2
Godeps/Godeps.json generated
View File

@ -104,7 +104,7 @@
}, },
{ {
"ImportPath": "github.com/samalba/dockerclient", "ImportPath": "github.com/samalba/dockerclient",
"Rev": "68832c185bb6304fed26a986891fd27b6ed17987" "Rev": "8802d66ce78e69ff16692f6fa89ad969a5711b8b"
}, },
{ {
"ImportPath": "github.com/samuel/go-zookeeper/zk", "ImportPath": "github.com/samuel/go-zookeeper/zk",

View File

@ -15,6 +15,7 @@ import (
"github.com/samalba/dockerclient" "github.com/samalba/dockerclient"
"log" "log"
"time" "time"
"os"
) )
// Callback used to listen to Docker's events // Callback used to listen to Docker's events
@ -42,6 +43,20 @@ func main() {
log.Println(info) 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 // Create a container
containerConfig := &dockerclient.ContainerConfig{ containerConfig := &dockerclient.ContainerConfig{
Image: "ubuntu:14.04", Image: "ubuntu:14.04",

View File

@ -272,6 +272,56 @@ func (client *DockerClient) readJSONStream(stream io.ReadCloser, decode func(*js
return resultChan 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 { func (client *DockerClient) StartContainer(id string, config *HostConfig) error {
data, err := json.Marshal(config) data, err := json.Marshal(config)
if err != nil { if err != nil {
@ -312,6 +362,26 @@ func (client *DockerClient) KillContainer(id, signal string) error {
return nil 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) { func (client *DockerClient) MonitorEvents(options *MonitorEventsOptions, stopChan <-chan struct{}) (<-chan EventOrError, error) {
v := url.Values{} v := url.Values{}
if options != nil { if options != nil {
@ -556,8 +626,14 @@ func (client *DockerClient) ListImages(all bool) ([]*Image, error) {
return images, nil return images, nil
} }
func (client *DockerClient) RemoveImage(name string) ([]*ImageDelete, error) { func (client *DockerClient) RemoveImage(name string, force bool) ([]*ImageDelete, error) {
uri := fmt.Sprintf("/%s/images/%s", APIVersion, name) 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) data, err := client.doRequest("DELETE", uri, nil, nil)
if err != nil { if err != nil {
return nil, err return nil, err
@ -586,30 +662,6 @@ func (client *DockerClient) UnpauseContainer(id string) error {
return nil 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 { func (client *DockerClient) RenameContainer(oldName string, newName string) error {
uri := fmt.Sprintf("/containers/%s/rename?name=%s", oldName, newName) uri := fmt.Sprintf("/containers/%s/rename?name=%s", oldName, newName)
_, err := client.doRequest("POST", uri, nil, nil) _, err := client.doRequest("POST", uri, nil, nil)

View File

@ -8,6 +8,7 @@ import (
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
"time"
"github.com/docker/docker/pkg/stdcopy" "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) { func TestPullImage(t *testing.T) {
client := testDockerClient(t) client := testDockerClient(t)
err := client.PullImage("busybox", nil) err := client.PullImage("busybox", nil)

View File

@ -29,6 +29,7 @@ func init() {
r.HandleFunc(baseURL+"/containers/{id}/logs", handleContainerLogs).Methods("GET") r.HandleFunc(baseURL+"/containers/{id}/logs", handleContainerLogs).Methods("GET")
r.HandleFunc(baseURL+"/containers/{id}/changes", handleContainerChanges).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}/kill", handleContainerKill).Methods("POST")
r.HandleFunc(baseURL+"/containers/{id}/wait", handleWait).Methods("POST")
r.HandleFunc(baseURL+"/images/create", handleImagePull).Methods("POST") r.HandleFunc(baseURL+"/images/create", handleImagePull).Methods("POST")
r.HandleFunc(baseURL+"/events", handleEvents).Methods("GET") r.HandleFunc(baseURL+"/events", handleEvents).Methods("GET")
testHTTPServer = httptest.NewServer(handlerAccessLog(r)) testHTTPServer = httptest.NewServer(handlerAccessLog(r))
@ -46,6 +47,15 @@ func handleContainerKill(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "{%q:%q", "Id", "421373210afd132") 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) { func handleImagePull(w http.ResponseWriter, r *http.Request) {
imageName := r.URL.Query()["fromImage"][0] imageName := r.URL.Query()["fromImage"][0]
responses := []map[string]interface{}{{ responses := []map[string]interface{}{{

View File

@ -16,11 +16,14 @@ type Client interface {
CreateContainer(config *ContainerConfig, name string) (string, error) CreateContainer(config *ContainerConfig, name string) (string, error)
ContainerLogs(id string, options *LogOptions) (io.ReadCloser, error) ContainerLogs(id string, options *LogOptions) (io.ReadCloser, error)
ContainerChanges(id string) ([]*ContainerChanges, 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 StartContainer(id string, config *HostConfig) error
StopContainer(id string, timeout int) error StopContainer(id string, timeout int) error
RestartContainer(id string, timeout int) error RestartContainer(id string, timeout int) error
KillContainer(id, signal string) error KillContainer(id, signal string) error
Wait(id string) <-chan WaitResult
// MonitorEvents takes options and an optional stop channel, and returns // MonitorEvents takes options and an optional stop channel, and returns
// an EventOrError channel. If an error is ever sent, then no more // 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 // 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 LoadImage(reader io.Reader) error
RemoveContainer(id string, force, volumes bool) error RemoveContainer(id string, force, volumes bool) error
ListImages(all bool) ([]*Image, error) ListImages(all bool) ([]*Image, error)
RemoveImage(name string) ([]*ImageDelete, error) RemoveImage(name string, force bool) ([]*ImageDelete, error)
PauseContainer(name string) error PauseContainer(name string) error
UnpauseContainer(name string) error UnpauseContainer(name string) error
RenameContainer(oldName string, newName string) error RenameContainer(oldName string, newName string) error

View File

@ -70,6 +70,11 @@ func (client *MockClient) KillContainer(id, signal string) error {
return args.Error(0) 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) { func (client *MockClient) MonitorEvents(options *dockerclient.MonitorEventsOptions, stopChan <-chan struct{}) (<-chan dockerclient.EventOrError, error) {
args := client.Mock.Called(options, stopChan) args := client.Mock.Called(options, stopChan)
return args.Get(0).(<-chan dockerclient.EventOrError), args.Error(1) 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) return args.Get(0).([]*dockerclient.Image), args.Error(1)
} }
func (client *MockClient) RemoveImage(name string) ([]*dockerclient.ImageDelete, error) { func (client *MockClient) RemoveImage(name string, force bool) ([]*dockerclient.ImageDelete, error) {
args := client.Mock.Called(name) args := client.Mock.Called(name, force)
return args.Get(0).([]*dockerclient.ImageDelete), args.Error(1) return args.Get(0).([]*dockerclient.ImageDelete), args.Error(1)
} }
@ -136,11 +141,21 @@ func (client *MockClient) UnpauseContainer(name string) error {
return args.Error(0) 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) args := client.Mock.Called(config)
return args.String(0), args.Error(1) 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 { func (client *MockClient) RenameContainer(oldName string, newName string) error {
args := client.Mock.Called(oldName, newName) args := client.Mock.Called(oldName, newName)
return args.Error(0) return args.Error(0)

View File

@ -324,6 +324,11 @@ type EventOrError struct {
Error error Error error
} }
type WaitResult struct {
ExitCode int
Error error
}
type decodingResult struct { type decodingResult struct {
result interface{} result interface{}
err error err error

View File

@ -511,8 +511,9 @@ func deleteImages(c *context, w http.ResponseWriter, r *http.Request) {
return return
} }
var name = mux.Vars(r)["name"] 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 { if err != nil {
httpError(w, err.Error(), http.StatusInternalServerError) httpError(w, err.Error(), http.StatusInternalServerError)
return return

View File

@ -21,7 +21,7 @@ type Cluster interface {
Image(IDOrName string) *Image Image(IDOrName string) *Image
// Remove images from the cluster // Remove images from the cluster
RemoveImages(name string) ([]*dockerclient.ImageDelete, error) RemoveImages(name string, force bool) ([]*dockerclient.ImageDelete, error)
// Return all containers // Return all containers
Containers() Containers Containers() Containers

View File

@ -179,8 +179,8 @@ func (e *Engine) updateSpecs() error {
} }
// RemoveImage deletes an image from the engine. // RemoveImage deletes an image from the engine.
func (e *Engine) RemoveImage(image *Image, name string) ([]*dockerclient.ImageDelete, error) { func (e *Engine) RemoveImage(image *Image, name string, force bool) ([]*dockerclient.ImageDelete, error) {
return e.client.RemoveImage(name) return e.client.RemoveImage(name, force)
} }
// RefreshImages refreshes the list of images on the engine. // RefreshImages refreshes the list of images on the engine.

View File

@ -221,7 +221,7 @@ func (c *Cluster) Image(IDOrName string) *cluster.Image {
} }
// RemoveImages removes images from the cluster // 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 return nil, errNotSupported
} }

View File

@ -290,7 +290,7 @@ func (c *Cluster) Image(IDOrName string) *cluster.Image {
} }
// RemoveImages removes all the images that match `name` from the cluster // 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() c.Lock()
defer c.Unlock() defer c.Unlock()
@ -300,7 +300,7 @@ func (c *Cluster) RemoveImages(name string) ([]*dockerclient.ImageDelete, error)
for _, e := range c.engines { for _, e := range c.engines {
for _, image := range e.Images(true) { for _, image := range e.Images(true) {
if image.Match(name, true) { if image.Match(name, true) {
content, err := image.Engine.RemoveImage(image, name) content, err := image.Engine.RemoveImage(image, name, force)
if err != nil { if err != nil {
errs = append(errs, fmt.Sprintf("%s: %s", image.Engine.Name, err.Error())) errs = append(errs, fmt.Sprintf("%s: %s", image.Engine.Name, err.Error()))
continue continue

View File

@ -78,3 +78,27 @@ function teardown() {
[[ "${output}" == *"busybox"* ]] [[ "${output}" == *"busybox"* ]]
[[ "${output}" != *"testimage"* ]] [[ "${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"* ]]
}