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",
"Rev": "68832c185bb6304fed26a986891fd27b6ed17987"
"Rev": "8802d66ce78e69ff16692f6fa89ad969a5711b8b"
},
{
"ImportPath": "github.com/samuel/go-zookeeper/zk",

View File

@ -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",

View File

@ -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)

View File

@ -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)

View File

@ -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{}{{

View File

@ -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

View File

@ -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)

View File

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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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
}

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
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

View File

@ -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"* ]]
}