mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
71a4990229
I ran into a situation where I was trying: `docker rmi busybox` and it kept failing saying: `could not find image: Prefix can't be empty` While I have no idea how I got into this situation, it turns out this is error message is from `daemon.canDeleteImage()`. In that func we loop over all containers checking to see if they're using the image we're trying to delete. In my case though, I had a container with no ImageID. So the code would die tryig to find that image (hence the "Prefix can't be empty" err). This would stop all processing despite the fact that the container we're checking had nothing to do with 'busybox'. My change logs the bad situation in the logs and then skips that container. There's no reason to fail all `docker rmi ...` calls just because of one bad container. Will continue to try to figure out how I got a container w/o an ImageID but as of now I have no idea, I didn't do anything but normal docker cli commands. Signed-off-by: Doug Davis <dug@us.ibm.com>
179 lines
5.2 KiB
Go
179 lines
5.2 KiB
Go
package daemon
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/graph"
|
|
"github.com/docker/docker/image"
|
|
"github.com/docker/docker/pkg/parsers"
|
|
"github.com/docker/docker/pkg/stringid"
|
|
"github.com/docker/docker/utils"
|
|
)
|
|
|
|
// FIXME: remove ImageDelete's dependency on Daemon, then move to graph/
|
|
func (daemon *Daemon) ImageDelete(name string, force, noprune bool) ([]types.ImageDelete, error) {
|
|
list := []types.ImageDelete{}
|
|
if err := daemon.imgDeleteHelper(name, &list, true, force, noprune); err != nil {
|
|
return nil, err
|
|
}
|
|
if len(list) == 0 {
|
|
return nil, fmt.Errorf("Conflict, %s wasn't deleted", name)
|
|
}
|
|
|
|
return list, nil
|
|
}
|
|
|
|
func (daemon *Daemon) imgDeleteHelper(name string, list *[]types.ImageDelete, first, force, noprune bool) error {
|
|
var (
|
|
repoName, tag string
|
|
tags = []string{}
|
|
)
|
|
repoAndTags := make(map[string][]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
|
|
}
|
|
|
|
if name == "" {
|
|
return fmt.Errorf("Image name can not be blank")
|
|
}
|
|
|
|
img, err := daemon.Repositories().LookupImage(name)
|
|
if err != nil {
|
|
if r, _ := daemon.Repositories().Get(repoName); r != nil {
|
|
return fmt.Errorf("No such image: %s", utils.ImageReference(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 != "" {
|
|
repoAndTags[repoName] = append(repoAndTags[repoName], parsedTag)
|
|
}
|
|
} else if repoName != parsedRepo && !force && first {
|
|
// 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 {
|
|
//the id belongs to multiple repos, with -f just delete all
|
|
repoName = parsedRepo
|
|
if parsedTag != "" {
|
|
repoAndTags[repoName] = append(repoAndTags[repoName], parsedTag)
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
repoAndTags[repoName] = append(repoAndTags[repoName], tag)
|
|
}
|
|
|
|
if !first && len(repoAndTags) > 0 {
|
|
return nil
|
|
}
|
|
|
|
if len(repos) <= 1 {
|
|
if err := daemon.canDeleteImage(img.ID, force); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Untag the current image
|
|
for repoName, tags := range repoAndTags {
|
|
for _, tag := range tags {
|
|
tagDeleted, err := daemon.Repositories().Delete(repoName, tag)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if tagDeleted {
|
|
*list = append(*list, types.ImageDelete{
|
|
Untagged: utils.ImageReference(repoName, tag),
|
|
})
|
|
daemon.EventsService.Log("untag", img.ID, "")
|
|
}
|
|
}
|
|
}
|
|
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
|
|
}
|
|
*list = append(*list, types.ImageDelete{
|
|
Deleted: img.ID,
|
|
})
|
|
daemon.EventsService.Log("delete", img.ID, "")
|
|
if img.Parent != "" && !noprune {
|
|
err := daemon.imgDeleteHelper(img.Parent, list, false, force, noprune)
|
|
if first {
|
|
return err
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (daemon *Daemon) canDeleteImage(imgID string, force bool) error {
|
|
for _, container := range daemon.List() {
|
|
if container.ImageID == "" {
|
|
// This technically should never happen, but if the container
|
|
// has no ImageID then log the situation and move on.
|
|
// If we allowed processing to continue then the code later
|
|
// on would fail with a "Prefix can't be empty" error even
|
|
// though the bad container has nothing to do with the image
|
|
// we're trying to delete.
|
|
logrus.Errorf("Container %q has no image associated with it!", container.ID)
|
|
continue
|
|
}
|
|
parent, err := daemon.Repositories().LookupImage(container.ImageID)
|
|
if err != nil {
|
|
if daemon.Graph().IsNotExist(err, container.ImageID) {
|
|
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", stringid.TruncateID(imgID), stringid.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", stringid.TruncateID(imgID), stringid.TruncateID(container.ID))
|
|
} else if !force {
|
|
return fmt.Errorf("Conflict, cannot delete %s because the container %s is using it, use -f to force", stringid.TruncateID(imgID), stringid.TruncateID(container.ID))
|
|
}
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|