github.com/damirazo/docker@v1.9.0/daemon/image_delete.go (about)

     1  package daemon
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/docker/docker/api/types"
     8  	derr "github.com/docker/docker/errors"
     9  	"github.com/docker/docker/graph/tags"
    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  // ImageDelete deletes the image referenced by the given imageRef from this
    17  // daemon. The given imageRef can be an image ID, ID prefix, or a repository
    18  // reference (with an optional tag or digest, defaulting to the tag name
    19  // "latest"). There is differing behavior depending on whether the given
    20  // imageRef is a repository reference or not.
    21  //
    22  // If the given imageRef is a repository reference then that repository
    23  // reference will be removed. However, if there exists any containers which
    24  // were created using the same image reference then the repository reference
    25  // cannot be removed unless either there are other repository references to the
    26  // same image or force is true. Following removal of the repository reference,
    27  // the referenced image itself will attempt to be deleted as described below
    28  // but quietly, meaning any image delete conflicts will cause the image to not
    29  // be deleted and the conflict will not be reported.
    30  //
    31  // There may be conflicts preventing deletion of an image and these conflicts
    32  // are divided into two categories grouped by their severity:
    33  //
    34  // Hard Conflict:
    35  // 	- a pull or build using the image.
    36  // 	- any descendent image.
    37  // 	- any running container using the image.
    38  //
    39  // Soft Conflict:
    40  // 	- any stopped container using the image.
    41  // 	- any repository tag or digest references to the image.
    42  //
    43  // The image cannot be removed if there are any hard conflicts and can be
    44  // removed if there are soft conflicts only if force is true.
    45  //
    46  // If prune is true, ancestor images will each attempt to be deleted quietly,
    47  // meaning any delete conflicts will cause the image to not be deleted and the
    48  // conflict will not be reported.
    49  //
    50  // FIXME: remove ImageDelete's dependency on Daemon, then move to the graph
    51  // package. This would require that we no longer need the daemon to determine
    52  // whether images are being used by a stopped or running container.
    53  func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.ImageDelete, error) {
    54  	records := []types.ImageDelete{}
    55  
    56  	img, err := daemon.repositories.LookupImage(imageRef)
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  
    61  	var removedRepositoryRef bool
    62  	if !isImageIDPrefix(img.ID, imageRef) {
    63  		// A repository reference was given and should be removed
    64  		// first. We can only remove this reference if either force is
    65  		// true, there are multiple repository references to this
    66  		// image, or there are no containers using the given reference.
    67  		if !(force || daemon.imageHasMultipleRepositoryReferences(img.ID)) {
    68  			if container := daemon.getContainerUsingImage(img.ID); container != nil {
    69  				// If we removed the repository reference then
    70  				// this image would remain "dangling" and since
    71  				// we really want to avoid that the client must
    72  				// explicitly force its removal.
    73  				return nil, derr.ErrorCodeImgDelUsed.WithArgs(imageRef, stringid.TruncateID(container.ID), stringid.TruncateID(img.ID))
    74  			}
    75  		}
    76  
    77  		parsedRef, err := daemon.removeImageRef(imageRef)
    78  		if err != nil {
    79  			return nil, err
    80  		}
    81  
    82  		untaggedRecord := types.ImageDelete{Untagged: parsedRef}
    83  
    84  		daemon.EventsService.Log("untag", img.ID, "")
    85  		records = append(records, untaggedRecord)
    86  
    87  		// If has remaining references then untag finishes the remove
    88  		if daemon.repositories.HasReferences(img) {
    89  			return records, nil
    90  		}
    91  
    92  		removedRepositoryRef = true
    93  	} else {
    94  		// If an ID reference was given AND there is exactly one
    95  		// repository reference to the image then we will want to
    96  		// remove that reference.
    97  		// FIXME: Is this the behavior we want?
    98  		repoRefs := daemon.repositories.ByID()[img.ID]
    99  		if len(repoRefs) == 1 {
   100  			parsedRef, err := daemon.removeImageRef(repoRefs[0])
   101  			if err != nil {
   102  				return nil, err
   103  			}
   104  
   105  			untaggedRecord := types.ImageDelete{Untagged: parsedRef}
   106  
   107  			daemon.EventsService.Log("untag", img.ID, "")
   108  			records = append(records, untaggedRecord)
   109  		}
   110  	}
   111  
   112  	return records, daemon.imageDeleteHelper(img, &records, force, prune, removedRepositoryRef)
   113  }
   114  
   115  // isImageIDPrefix returns whether the given possiblePrefix is a prefix of the
   116  // given imageID.
   117  func isImageIDPrefix(imageID, possiblePrefix string) bool {
   118  	return strings.HasPrefix(imageID, possiblePrefix)
   119  }
   120  
   121  // imageHasMultipleRepositoryReferences returns whether there are multiple
   122  // repository references to the given imageID.
   123  func (daemon *Daemon) imageHasMultipleRepositoryReferences(imageID string) bool {
   124  	return len(daemon.repositories.ByID()[imageID]) > 1
   125  }
   126  
   127  // getContainerUsingImage returns a container that was created using the given
   128  // imageID. Returns nil if there is no such container.
   129  func (daemon *Daemon) getContainerUsingImage(imageID string) *Container {
   130  	for _, container := range daemon.List() {
   131  		if container.ImageID == imageID {
   132  			return container
   133  		}
   134  	}
   135  
   136  	return nil
   137  }
   138  
   139  // removeImageRef attempts to parse and remove the given image reference from
   140  // this daemon's store of repository tag/digest references. The given
   141  // repositoryRef must not be an image ID but a repository name followed by an
   142  // optional tag or digest reference. If tag or digest is omitted, the default
   143  // tag is used. Returns the resolved image reference and an error.
   144  func (daemon *Daemon) removeImageRef(repositoryRef string) (string, error) {
   145  	repository, ref := parsers.ParseRepositoryTag(repositoryRef)
   146  	if ref == "" {
   147  		ref = tags.DefaultTag
   148  	}
   149  
   150  	// Ignore the boolean value returned, as far as we're concerned, this
   151  	// is an idempotent operation and it's okay if the reference didn't
   152  	// exist in the first place.
   153  	_, err := daemon.repositories.Delete(repository, ref)
   154  
   155  	return utils.ImageReference(repository, ref), err
   156  }
   157  
   158  // removeAllReferencesToImageID attempts to remove every reference to the given
   159  // imgID from this daemon's store of repository tag/digest references. Returns
   160  // on the first encountered error. Removed references are logged to this
   161  // daemon's event service. An "Untagged" types.ImageDelete is added to the
   162  // given list of records.
   163  func (daemon *Daemon) removeAllReferencesToImageID(imgID string, records *[]types.ImageDelete) error {
   164  	imageRefs := daemon.repositories.ByID()[imgID]
   165  
   166  	for _, imageRef := range imageRefs {
   167  		parsedRef, err := daemon.removeImageRef(imageRef)
   168  		if err != nil {
   169  			return err
   170  		}
   171  
   172  		untaggedRecord := types.ImageDelete{Untagged: parsedRef}
   173  
   174  		daemon.EventsService.Log("untag", imgID, "")
   175  		*records = append(*records, untaggedRecord)
   176  	}
   177  
   178  	return nil
   179  }
   180  
   181  // ImageDeleteConflict holds a soft or hard conflict and an associated error.
   182  // Implements the error interface.
   183  type imageDeleteConflict struct {
   184  	hard    bool
   185  	imgID   string
   186  	message string
   187  }
   188  
   189  func (idc *imageDeleteConflict) Error() string {
   190  	var forceMsg string
   191  	if idc.hard {
   192  		forceMsg = "cannot be forced"
   193  	} else {
   194  		forceMsg = "must be forced"
   195  	}
   196  
   197  	return fmt.Sprintf("conflict: unable to delete %s (%s) - %s", stringid.TruncateID(idc.imgID), forceMsg, idc.message)
   198  }
   199  
   200  // imageDeleteHelper attempts to delete the given image from this daemon. If
   201  // the image has any hard delete conflicts (child images or running containers
   202  // using the image) then it cannot be deleted. If the image has any soft delete
   203  // conflicts (any tags/digests referencing the image or any stopped container
   204  // using the image) then it can only be deleted if force is true. If the delete
   205  // succeeds and prune is true, the parent images are also deleted if they do
   206  // not have any soft or hard delete conflicts themselves. Any deleted images
   207  // and untagged references are appended to the given records. If any error or
   208  // conflict is encountered, it will be returned immediately without deleting
   209  // the image. If quiet is true, any encountered conflicts will be ignored and
   210  // the function will return nil immediately without deleting the image.
   211  func (daemon *Daemon) imageDeleteHelper(img *image.Image, records *[]types.ImageDelete, force, prune, quiet bool) error {
   212  	// First, determine if this image has any conflicts. Ignore soft conflicts
   213  	// if force is true.
   214  	if conflict := daemon.checkImageDeleteConflict(img, force); conflict != nil {
   215  		if quiet && !daemon.imageIsDangling(img) {
   216  			// Ignore conflicts UNLESS the image is "dangling" in
   217  			// which case we want the user to know.
   218  			return nil
   219  		}
   220  
   221  		// There was a conflict and it's either a hard conflict OR we are not
   222  		// forcing deletion on soft conflicts.
   223  		return conflict
   224  	}
   225  
   226  	// Delete all repository tag/digest references to this image.
   227  	if err := daemon.removeAllReferencesToImageID(img.ID, records); err != nil {
   228  		return err
   229  	}
   230  
   231  	if err := daemon.Graph().Delete(img.ID); err != nil {
   232  		return err
   233  	}
   234  
   235  	daemon.EventsService.Log("delete", img.ID, "")
   236  	*records = append(*records, types.ImageDelete{Deleted: img.ID})
   237  
   238  	if !prune || img.Parent == "" {
   239  		return nil
   240  	}
   241  
   242  	// We need to prune the parent image. This means delete it if there are
   243  	// no tags/digests referencing it and there are no containers using it (
   244  	// either running or stopped).
   245  	parentImg, err := daemon.Graph().Get(img.Parent)
   246  	if err != nil {
   247  		return derr.ErrorCodeImgNoParent.WithArgs(err)
   248  	}
   249  
   250  	// Do not force prunings, but do so quietly (stopping on any encountered
   251  	// conflicts).
   252  	return daemon.imageDeleteHelper(parentImg, records, false, true, true)
   253  }
   254  
   255  // checkImageDeleteConflict determines whether there are any conflicts
   256  // preventing deletion of the given image from this daemon. A hard conflict is
   257  // any image which has the given image as a parent or any running container
   258  // using the image. A soft conflict is any tags/digest referencing the given
   259  // image or any stopped container using the image. If ignoreSoftConflicts is
   260  // true, this function will not check for soft conflict conditions.
   261  func (daemon *Daemon) checkImageDeleteConflict(img *image.Image, ignoreSoftConflicts bool) *imageDeleteConflict {
   262  	// Check for hard conflicts first.
   263  	if conflict := daemon.checkImageDeleteHardConflict(img); conflict != nil {
   264  		return conflict
   265  	}
   266  
   267  	// Then check for soft conflicts.
   268  	if ignoreSoftConflicts {
   269  		// Don't bother checking for soft conflicts.
   270  		return nil
   271  	}
   272  
   273  	return daemon.checkImageDeleteSoftConflict(img)
   274  }
   275  
   276  func (daemon *Daemon) checkImageDeleteHardConflict(img *image.Image) *imageDeleteConflict {
   277  	// Check if the image ID is being used by a pull or build.
   278  	if daemon.Graph().IsHeld(img.ID) {
   279  		return &imageDeleteConflict{
   280  			hard:    true,
   281  			imgID:   img.ID,
   282  			message: "image is held by an ongoing pull or build",
   283  		}
   284  	}
   285  
   286  	// Check if the image has any descendent images.
   287  	if daemon.Graph().HasChildren(img.ID) {
   288  		return &imageDeleteConflict{
   289  			hard:    true,
   290  			imgID:   img.ID,
   291  			message: "image has dependent child images",
   292  		}
   293  	}
   294  
   295  	// Check if any running container is using the image.
   296  	for _, container := range daemon.List() {
   297  		if !container.IsRunning() {
   298  			// Skip this until we check for soft conflicts later.
   299  			continue
   300  		}
   301  
   302  		if container.ImageID == img.ID {
   303  			return &imageDeleteConflict{
   304  				imgID:   img.ID,
   305  				hard:    true,
   306  				message: fmt.Sprintf("image is being used by running container %s", stringid.TruncateID(container.ID)),
   307  			}
   308  		}
   309  	}
   310  
   311  	return nil
   312  }
   313  
   314  func (daemon *Daemon) checkImageDeleteSoftConflict(img *image.Image) *imageDeleteConflict {
   315  	// Check if any repository tags/digest reference this image.
   316  	if daemon.repositories.HasReferences(img) {
   317  		return &imageDeleteConflict{
   318  			imgID:   img.ID,
   319  			message: "image is referenced in one or more repositories",
   320  		}
   321  	}
   322  
   323  	// Check if any stopped containers reference this image.
   324  	for _, container := range daemon.List() {
   325  		if container.IsRunning() {
   326  			// Skip this as it was checked above in hard conflict conditions.
   327  			continue
   328  		}
   329  
   330  		if container.ImageID == img.ID {
   331  			return &imageDeleteConflict{
   332  				imgID:   img.ID,
   333  				message: fmt.Sprintf("image is being used by stopped container %s", stringid.TruncateID(container.ID)),
   334  			}
   335  		}
   336  	}
   337  
   338  	return nil
   339  }
   340  
   341  // imageIsDangling returns whether the given image is "dangling" which means
   342  // that there are no repository references to the given image and it has no
   343  // child images.
   344  func (daemon *Daemon) imageIsDangling(img *image.Image) bool {
   345  	return !(daemon.repositories.HasReferences(img) || daemon.Graph().HasChildren(img.ID))
   346  }