github.com/chenchun/docker@v1.3.2-0.20150629222414-20467faf132b/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, err := daemon.Graph().ByParent()
    60  	if err != nil {
    61  		return err
    62  	}
    63  
    64  	repos := daemon.Repositories().ByID()[img.ID]
    65  
    66  	//If delete by id, see if the id belong only to one repository
    67  	deleteByID := repoName == ""
    68  	if deleteByID {
    69  		for _, repoAndTag := range repos {
    70  			parsedRepo, parsedTag := parsers.ParseRepositoryTag(repoAndTag)
    71  			if repoName == "" || repoName == parsedRepo {
    72  				repoName = parsedRepo
    73  				if parsedTag != "" {
    74  					repoAndTags[repoName] = append(repoAndTags[repoName], parsedTag)
    75  				}
    76  			} else if repoName != parsedRepo && !force && first {
    77  				// the id belongs to multiple repos, like base:latest and user:test,
    78  				// in that case return conflict
    79  				return fmt.Errorf("Conflict, cannot delete image %s because it is tagged in multiple repositories, use -f to force", name)
    80  			} else {
    81  				//the id belongs to multiple repos, with -f just delete all
    82  				repoName = parsedRepo
    83  				if parsedTag != "" {
    84  					repoAndTags[repoName] = append(repoAndTags[repoName], parsedTag)
    85  				}
    86  			}
    87  		}
    88  	} else {
    89  		repoAndTags[repoName] = append(repoAndTags[repoName], tag)
    90  	}
    91  
    92  	if !first && len(repoAndTags) > 0 {
    93  		return nil
    94  	}
    95  
    96  	if len(repos) <= 1 || (len(repoAndTags) <= 1 && deleteByID) {
    97  		if err := daemon.canDeleteImage(img.ID, force); err != nil {
    98  			return err
    99  		}
   100  	}
   101  
   102  	// Untag the current image
   103  	for repoName, tags := range repoAndTags {
   104  		for _, tag := range tags {
   105  			tagDeleted, err := daemon.Repositories().Delete(repoName, tag)
   106  			if err != nil {
   107  				return err
   108  			}
   109  			if tagDeleted {
   110  				*list = append(*list, types.ImageDelete{
   111  					Untagged: utils.ImageReference(repoName, tag),
   112  				})
   113  				daemon.EventsService.Log("untag", img.ID, "")
   114  			}
   115  		}
   116  	}
   117  	tags = daemon.Repositories().ByID()[img.ID]
   118  	if (len(tags) <= 1 && repoName == "") || len(tags) == 0 {
   119  		if len(byParents[img.ID]) == 0 {
   120  			if err := daemon.Repositories().DeleteAll(img.ID); err != nil {
   121  				return err
   122  			}
   123  			if err := daemon.Graph().Delete(img.ID); err != nil {
   124  				return err
   125  			}
   126  			*list = append(*list, types.ImageDelete{
   127  				Deleted: img.ID,
   128  			})
   129  			daemon.EventsService.Log("delete", img.ID, "")
   130  			if img.Parent != "" && !noprune {
   131  				err := daemon.imgDeleteHelper(img.Parent, list, false, force, noprune)
   132  				if first {
   133  					return err
   134  				}
   135  
   136  			}
   137  
   138  		}
   139  	}
   140  	return nil
   141  }
   142  
   143  func (daemon *Daemon) canDeleteImage(imgID string, force bool) error {
   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  				return nil
   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  }