github.com/torfuzx/docker@v1.8.1/daemon/image_delete.go (about)

     1  package daemon
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/Sirupsen/logrus"
     8  	"github.com/docker/docker/api/types"
     9  	"github.com/docker/docker/graph"
    10  	"github.com/docker/docker/image"
    11  	"github.com/docker/docker/pkg/parsers"
    12  	"github.com/docker/docker/pkg/stringid"
    13  	"github.com/docker/docker/utils"
    14  )
    15  
    16  // FIXME: remove ImageDelete's dependency on Daemon, then move to graph/
    17  func (daemon *Daemon) ImageDelete(name string, force, noprune bool) ([]types.ImageDelete, error) {
    18  	list := []types.ImageDelete{}
    19  	if err := daemon.imgDeleteHelper(name, &list, true, force, noprune); err != nil {
    20  		return nil, err
    21  	}
    22  	if len(list) == 0 {
    23  		return nil, fmt.Errorf("Conflict, %s wasn't deleted", name)
    24  	}
    25  
    26  	return list, nil
    27  }
    28  
    29  func (daemon *Daemon) imgDeleteHelper(name string, list *[]types.ImageDelete, first, force, noprune bool) error {
    30  	var (
    31  		repoName, tag string
    32  		tags          = []string{}
    33  	)
    34  	repoAndTags := make(map[string][]string)
    35  
    36  	// FIXME: please respect DRY and centralize repo+tag parsing in a single central place! -- shykes
    37  	repoName, tag = parsers.ParseRepositoryTag(name)
    38  	if tag == "" {
    39  		tag = graph.DEFAULTTAG
    40  	}
    41  
    42  	if name == "" {
    43  		return fmt.Errorf("Image name can not be blank")
    44  	}
    45  
    46  	img, err := daemon.Repositories().LookupImage(name)
    47  	if err != nil {
    48  		if r, _ := daemon.Repositories().Get(repoName); r != nil {
    49  			return fmt.Errorf("No such image: %s", utils.ImageReference(repoName, tag))
    50  		}
    51  		return fmt.Errorf("No such image: %s", name)
    52  	}
    53  
    54  	if strings.Contains(img.ID, name) {
    55  		repoName = ""
    56  		tag = ""
    57  	}
    58  
    59  	byParents := daemon.Graph().ByParent()
    60  
    61  	repos := daemon.Repositories().ByID()[img.ID]
    62  
    63  	//If delete by id, see if the id belong only to one repository
    64  	deleteByID := repoName == ""
    65  	if deleteByID {
    66  		for _, repoAndTag := range repos {
    67  			parsedRepo, parsedTag := parsers.ParseRepositoryTag(repoAndTag)
    68  			if repoName == "" || repoName == parsedRepo {
    69  				repoName = parsedRepo
    70  				if parsedTag != "" {
    71  					repoAndTags[repoName] = append(repoAndTags[repoName], parsedTag)
    72  				}
    73  			} else if repoName != parsedRepo && !force && first {
    74  				// the id belongs to multiple repos, like base:latest and user:test,
    75  				// in that case return conflict
    76  				return fmt.Errorf("Conflict, cannot delete image %s because it is tagged in multiple repositories, use -f to force", name)
    77  			} else {
    78  				//the id belongs to multiple repos, with -f just delete all
    79  				repoName = parsedRepo
    80  				if parsedTag != "" {
    81  					repoAndTags[repoName] = append(repoAndTags[repoName], parsedTag)
    82  				}
    83  			}
    84  		}
    85  	} else {
    86  		repoAndTags[repoName] = append(repoAndTags[repoName], tag)
    87  	}
    88  
    89  	if !first && len(repoAndTags) > 0 {
    90  		return nil
    91  	}
    92  
    93  	if len(repos) <= 1 || (len(repoAndTags) <= 1 && deleteByID) {
    94  		if err := daemon.canDeleteImage(img.ID, force); err != nil {
    95  			return err
    96  		}
    97  	}
    98  
    99  	// Untag the current image
   100  	for repoName, tags := range repoAndTags {
   101  		for _, tag := range tags {
   102  			tagDeleted, err := daemon.Repositories().Delete(repoName, tag)
   103  			if err != nil {
   104  				return err
   105  			}
   106  			if tagDeleted {
   107  				*list = append(*list, types.ImageDelete{
   108  					Untagged: utils.ImageReference(repoName, tag),
   109  				})
   110  				daemon.EventsService.Log("untag", img.ID, "")
   111  			}
   112  		}
   113  	}
   114  	tags = daemon.Repositories().ByID()[img.ID]
   115  	if (len(tags) <= 1 && repoName == "") || len(tags) == 0 {
   116  		if len(byParents[img.ID]) == 0 {
   117  			if err := daemon.Repositories().DeleteAll(img.ID); err != nil {
   118  				return err
   119  			}
   120  			if err := daemon.Graph().Delete(img.ID); err != nil {
   121  				return err
   122  			}
   123  			*list = append(*list, types.ImageDelete{
   124  				Deleted: img.ID,
   125  			})
   126  			daemon.EventsService.Log("delete", img.ID, "")
   127  			if img.Parent != "" && !noprune {
   128  				err := daemon.imgDeleteHelper(img.Parent, list, false, force, noprune)
   129  				if first {
   130  					return err
   131  				}
   132  
   133  			}
   134  
   135  		}
   136  	}
   137  	return nil
   138  }
   139  
   140  func (daemon *Daemon) canDeleteImage(imgID string, force bool) error {
   141  	if daemon.Graph().IsHeld(imgID) {
   142  		return fmt.Errorf("Conflict, cannot delete because %s is held by an ongoing pull or build", stringid.TruncateID(imgID))
   143  	}
   144  	for _, container := range daemon.List() {
   145  		if container.ImageID == "" {
   146  			// This technically should never happen, but if the container
   147  			// has no ImageID then log the situation and move on.
   148  			// If we allowed processing to continue then the code later
   149  			// on would fail with a "Prefix can't be empty" error even
   150  			// though the bad container has nothing to do with the image
   151  			// we're trying to delete.
   152  			logrus.Errorf("Container %q has no image associated with it!", container.ID)
   153  			continue
   154  		}
   155  		parent, err := daemon.Repositories().LookupImage(container.ImageID)
   156  		if err != nil {
   157  			if daemon.Graph().IsNotExist(err, container.ImageID) {
   158  				continue
   159  			}
   160  			return err
   161  		}
   162  
   163  		if err := daemon.graph.WalkHistory(parent, func(p image.Image) error {
   164  			if imgID == p.ID {
   165  				if container.IsRunning() {
   166  					if force {
   167  						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))
   168  					}
   169  					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))
   170  				} else if !force {
   171  					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))
   172  				}
   173  			}
   174  			return nil
   175  		}); err != nil {
   176  			return err
   177  		}
   178  	}
   179  	return nil
   180  }