mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
d942c59b69
When we use the engine/env object we can run into a situation where a string is passed in as the value but later on when we json serialize the name/value pairs, because the string is made up of just numbers it appears as an integer and not a string - meaning no quotes. This can cause parsing issues for clients. I tried to find all spots where we call env.Set() and the type of the name being set might end up having a value that could look like an int (like author). In those cases I switched it to use env.SetJson() instead because that will wrap it in quotes. One interesting thing to note about the testcase that I modified is that the escaped quotes should have been there all along and we were incorrectly letting it thru. If you look at the metadata stored for that resource you can see the quotes were escaped and we lost them during the serialization steps because of the env.Set() stuff. The use of env is probably not the best way to do all of this. Closes: #9602 Signed-off-by: Doug Davis <dug@us.ibm.com>
159 lines
4.3 KiB
Go
159 lines
4.3 KiB
Go
package daemon
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/docker/docker/engine"
|
|
"github.com/docker/docker/graph"
|
|
"github.com/docker/docker/image"
|
|
"github.com/docker/docker/pkg/parsers"
|
|
"github.com/docker/docker/utils"
|
|
)
|
|
|
|
func (daemon *Daemon) ImageDelete(job *engine.Job) engine.Status {
|
|
if n := len(job.Args); n != 1 {
|
|
return job.Errorf("Usage: %s IMAGE", job.Name)
|
|
}
|
|
imgs := engine.NewTable("", 0)
|
|
if err := daemon.DeleteImage(job.Eng, job.Args[0], imgs, true, job.GetenvBool("force"), job.GetenvBool("noprune")); err != nil {
|
|
return job.Error(err)
|
|
}
|
|
if len(imgs.Data) == 0 {
|
|
return job.Errorf("Conflict, %s wasn't deleted", job.Args[0])
|
|
}
|
|
if _, err := imgs.WriteListTo(job.Stdout); err != nil {
|
|
return job.Error(err)
|
|
}
|
|
return engine.StatusOK
|
|
}
|
|
|
|
// FIXME: make this private and use the job instead
|
|
func (daemon *Daemon) DeleteImage(eng *engine.Engine, name string, imgs *engine.Table, first, force, noprune bool) error {
|
|
var (
|
|
repoName, tag string
|
|
tags = []string{}
|
|
)
|
|
|
|
// FIXME: please respect DRY and centralize repo+tag parsing in a single central place! -- shykes
|
|
repoName, tag = parsers.ParseRepositoryTag(name)
|
|
if tag == "" {
|
|
tag = graph.DEFAULTTAG
|
|
}
|
|
|
|
img, err := daemon.Repositories().LookupImage(name)
|
|
if err != nil {
|
|
if r, _ := daemon.Repositories().Get(repoName); r != nil {
|
|
return fmt.Errorf("No such image: %s:%s", repoName, tag)
|
|
}
|
|
return fmt.Errorf("No such image: %s", name)
|
|
}
|
|
|
|
if strings.Contains(img.ID, name) {
|
|
repoName = ""
|
|
tag = ""
|
|
}
|
|
|
|
byParents, err := daemon.Graph().ByParent()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
repos := daemon.Repositories().ByID()[img.ID]
|
|
|
|
//If delete by id, see if the id belong only to one repository
|
|
if repoName == "" {
|
|
for _, repoAndTag := range repos {
|
|
parsedRepo, parsedTag := parsers.ParseRepositoryTag(repoAndTag)
|
|
if repoName == "" || repoName == parsedRepo {
|
|
repoName = parsedRepo
|
|
if parsedTag != "" {
|
|
tags = append(tags, parsedTag)
|
|
}
|
|
} else if repoName != parsedRepo && !force {
|
|
// the id belongs to multiple repos, like base:latest and user:test,
|
|
// in that case return conflict
|
|
return fmt.Errorf("Conflict, cannot delete image %s because it is tagged in multiple repositories, use -f to force", name)
|
|
}
|
|
}
|
|
} else {
|
|
tags = append(tags, tag)
|
|
}
|
|
|
|
if !first && len(tags) > 0 {
|
|
return nil
|
|
}
|
|
|
|
if len(repos) <= 1 {
|
|
if err := daemon.canDeleteImage(img.ID, force); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Untag the current image
|
|
for _, tag := range tags {
|
|
tagDeleted, err := daemon.Repositories().Delete(repoName, tag)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if tagDeleted {
|
|
out := &engine.Env{}
|
|
out.Set("Untagged", repoName+":"+tag)
|
|
imgs.Add(out)
|
|
eng.Job("log", "untag", img.ID, "").Run()
|
|
}
|
|
}
|
|
tags = daemon.Repositories().ByID()[img.ID]
|
|
if (len(tags) <= 1 && repoName == "") || len(tags) == 0 {
|
|
if len(byParents[img.ID]) == 0 {
|
|
if err := daemon.Repositories().DeleteAll(img.ID); err != nil {
|
|
return err
|
|
}
|
|
if err := daemon.Graph().Delete(img.ID); err != nil {
|
|
return err
|
|
}
|
|
out := &engine.Env{}
|
|
out.SetJson("Deleted", img.ID)
|
|
imgs.Add(out)
|
|
eng.Job("log", "delete", img.ID, "").Run()
|
|
if img.Parent != "" && !noprune {
|
|
err := daemon.DeleteImage(eng, img.Parent, imgs, false, force, noprune)
|
|
if first {
|
|
return err
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (daemon *Daemon) canDeleteImage(imgID string, force bool) error {
|
|
for _, container := range daemon.List() {
|
|
parent, err := daemon.Repositories().LookupImage(container.Image)
|
|
if err != nil {
|
|
if daemon.Graph().IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
if err := parent.WalkHistory(func(p *image.Image) error {
|
|
if imgID == p.ID {
|
|
if container.IsRunning() {
|
|
if force {
|
|
return fmt.Errorf("Conflict, cannot force delete %s because the running container %s is using it, stop it and retry", utils.TruncateID(imgID), utils.TruncateID(container.ID))
|
|
}
|
|
return fmt.Errorf("Conflict, cannot delete %s because the running container %s is using it, stop it and use -f to force", utils.TruncateID(imgID), utils.TruncateID(container.ID))
|
|
} else if !force {
|
|
return fmt.Errorf("Conflict, cannot delete %s because the container %s is using it, use -f to force", utils.TruncateID(imgID), utils.TruncateID(container.ID))
|
|
}
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|