mirror of https://github.com/docker/docs.git
Merge pull request #1162 from jimmyxian/add-support-force-remove-image
Add support force remove image
This commit is contained in:
commit
fc0cc899e6
|
@ -104,7 +104,7 @@
|
|||
},
|
||||
{
|
||||
"ImportPath": "github.com/samalba/dockerclient",
|
||||
"Rev": "68832c185bb6304fed26a986891fd27b6ed17987"
|
||||
"Rev": "8802d66ce78e69ff16692f6fa89ad969a5711b8b"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/samuel/go-zookeeper/zk",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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{}{{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -324,6 +324,11 @@ type EventOrError struct {
|
|||
Error error
|
||||
}
|
||||
|
||||
type WaitResult struct {
|
||||
ExitCode int
|
||||
Error error
|
||||
}
|
||||
|
||||
type decodingResult struct {
|
||||
result interface{}
|
||||
err error
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"* ]]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue