github.com/jogo/docker@v1.7.0-rc1/daemon/image_delete.go (about)

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