2018-02-07 20:52:47 +00:00
package images // import "github.com/docker/docker/daemon/images"
2014-08-05 06:41:30 +00:00
import (
2022-07-08 12:26:17 +00:00
"context"
2014-08-05 06:41:30 +00:00
"fmt"
"strings"
2016-07-20 23:11:28 +00:00
"time"
2014-08-05 06:41:30 +00:00
2023-08-30 16:31:46 +00:00
"github.com/distribution/reference"
2024-01-20 15:01:43 +00:00
"github.com/docker/docker/api/types/backend"
2023-08-26 13:24:46 +00:00
"github.com/docker/docker/api/types/events"
2022-07-25 15:24:03 +00:00
imagetypes "github.com/docker/docker/api/types/image"
2015-11-12 19:55:17 +00:00
"github.com/docker/docker/container"
2018-01-11 19:53:06 +00:00
"github.com/docker/docker/errdefs"
2015-07-20 17:57:15 +00:00
"github.com/docker/docker/image"
2015-03-24 11:25:26 +00:00
"github.com/docker/docker/pkg/stringid"
2017-07-19 14:20:13 +00:00
"github.com/pkg/errors"
2014-08-05 06:41:30 +00:00
)
2016-01-12 23:03:08 +00:00
type conflictType int
const (
2018-05-19 11:38:54 +00:00
conflictDependentChild conflictType = 1 << iota
2016-01-12 23:03:08 +00:00
conflictRunningContainer
conflictActiveReference
conflictStoppedContainer
conflictHard = conflictDependentChild | conflictRunningContainer
conflictSoft = conflictActiveReference | conflictStoppedContainer
)
2015-08-15 07:30:25 +00:00
// ImageDelete deletes the image referenced by the given imageRef from this
// daemon. The given imageRef can be an image ID, ID prefix, or a repository
// reference (with an optional tag or digest, defaulting to the tag name
// "latest"). There is differing behavior depending on whether the given
// imageRef is a repository reference or not.
//
// If the given imageRef is a repository reference then that repository
// reference will be removed. However, if there exists any containers which
// were created using the same image reference then the repository reference
// cannot be removed unless either there are other repository references to the
// same image or force is true. Following removal of the repository reference,
// the referenced image itself will attempt to be deleted as described below
// but quietly, meaning any image delete conflicts will cause the image to not
// be deleted and the conflict will not be reported.
//
// There may be conflicts preventing deletion of an image and these conflicts
// are divided into two categories grouped by their severity:
//
// Hard Conflict:
2022-07-08 16:27:07 +00:00
// - a pull or build using the image.
// - any descendant image.
// - any running container using the image.
2015-08-15 07:30:25 +00:00
//
// Soft Conflict:
2022-07-08 16:27:07 +00:00
// - any stopped container using the image.
// - any repository tag or digest references to the image.
2015-08-15 07:30:25 +00:00
//
// The image cannot be removed if there are any hard conflicts and can be
// removed if there are soft conflicts only if force is true.
//
// If prune is true, ancestor images will each attempt to be deleted quietly,
// meaning any delete conflicts will cause the image to not be deleted and the
// conflict will not be reported.
2022-11-24 18:04:31 +00:00
func ( i * ImageService ) ImageDelete ( ctx context . Context , imageRef string , force , prune bool ) ( [ ] imagetypes . DeleteResponse , error ) {
2016-07-20 23:11:28 +00:00
start := time . Now ( )
2022-11-24 18:04:31 +00:00
records := [ ] imagetypes . DeleteResponse { }
2015-08-15 07:30:25 +00:00
2024-01-20 15:01:43 +00:00
img , err := i . GetImage ( ctx , imageRef , backend . GetImageOpts { } )
2015-08-15 07:30:25 +00:00
if err != nil {
2017-07-19 14:20:13 +00:00
return nil , err
2014-08-05 06:41:30 +00:00
}
2015-08-15 07:30:25 +00:00
2018-02-14 20:19:37 +00:00
imgID := img . ID ( )
2018-02-02 22:18:46 +00:00
repoRefs := i . referenceStore . References ( imgID . Digest ( ) )
using := func ( c * container . Container ) bool {
return c . ImageID == imgID
}
2015-11-18 22:20:54 +00:00
2015-08-15 07:30:25 +00:00
var removedRepositoryRef bool
2015-11-18 22:20:54 +00:00
if ! isImageIDPrefix ( imgID . String ( ) , imageRef ) {
2015-08-15 07:30:25 +00:00
// A repository reference was given and should be removed
// first. We can only remove this reference if either force is
// true, there are multiple repository references to this
// image, or there are no containers using the given reference.
2016-07-26 07:45:39 +00:00
if ! force && isSingleReference ( repoRefs ) {
2022-08-08 10:55:10 +00:00
if ctr := i . containers . First ( using ) ; ctr != nil {
2015-08-15 07:30:25 +00:00
// If we removed the repository reference then
// this image would remain "dangling" and since
// we really want to avoid that the client must
// explicitly force its removal.
2022-08-08 10:55:10 +00:00
err := errors . Errorf ( "conflict: unable to remove repository reference %q (must force) - container %s is using its referenced image %s" , imageRef , stringid . TruncateID ( ctr . ID ) , stringid . TruncateID ( imgID . String ( ) ) )
2017-11-29 04:09:37 +00:00
return nil , errdefs . Conflict ( err )
2015-08-15 07:30:25 +00:00
}
}
2017-01-26 00:54:18 +00:00
parsedRef , err := reference . ParseNormalizedNamed ( imageRef )
2015-08-15 07:30:25 +00:00
if err != nil {
return nil , err
}
2018-02-02 22:18:46 +00:00
parsedRef , err = i . removeImageRef ( parsedRef )
2015-11-18 22:20:54 +00:00
if err != nil {
return nil , err
}
2022-11-24 18:04:31 +00:00
untaggedRecord := imagetypes . DeleteResponse { Untagged : reference . FamiliarString ( parsedRef ) }
2015-08-15 07:30:25 +00:00
2023-08-26 13:24:46 +00:00
i . LogImageEvent ( imgID . String ( ) , imgID . String ( ) , events . ActionUnTag )
2015-08-15 07:30:25 +00:00
records = append ( records , untaggedRecord )
2018-02-02 22:18:46 +00:00
repoRefs = i . referenceStore . References ( imgID . Digest ( ) )
2016-01-07 01:57:21 +00:00
2016-06-16 23:06:47 +00:00
// If a tag reference was removed and the only remaining
// references to the same repository are digest references,
// then clean up those digest references.
2016-01-07 01:57:21 +00:00
if _ , isCanonical := parsedRef . ( reference . Canonical ) ; ! isCanonical {
2016-06-16 23:06:47 +00:00
foundRepoTagRef := false
2016-01-07 01:57:21 +00:00
for _ , repoRef := range repoRefs {
2016-06-16 23:06:47 +00:00
if _ , repoRefIsCanonical := repoRef . ( reference . Canonical ) ; ! repoRefIsCanonical && parsedRef . Name ( ) == repoRef . Name ( ) {
foundRepoTagRef = true
2016-01-07 01:57:21 +00:00
break
}
}
2016-06-16 23:06:47 +00:00
if ! foundRepoTagRef {
// Remove canonical references from same repository
2018-05-19 11:38:54 +00:00
var remainingRefs [ ] reference . Named
2016-01-07 01:57:21 +00:00
for _ , repoRef := range repoRefs {
2016-06-16 23:06:47 +00:00
if _ , repoRefIsCanonical := repoRef . ( reference . Canonical ) ; repoRefIsCanonical && parsedRef . Name ( ) == repoRef . Name ( ) {
2018-02-02 22:18:46 +00:00
if _ , err := i . removeImageRef ( repoRef ) ; err != nil {
2016-06-16 23:06:47 +00:00
return records , err
}
2022-11-24 18:04:31 +00:00
records = append ( records , imagetypes . DeleteResponse { Untagged : reference . FamiliarString ( repoRef ) } )
2016-06-16 23:06:47 +00:00
} else {
remainingRefs = append ( remainingRefs , repoRef )
}
2016-01-07 01:57:21 +00:00
}
2016-06-16 23:06:47 +00:00
repoRefs = remainingRefs
2016-01-07 01:57:21 +00:00
}
}
// If it has remaining references then the untag finished the remove
if len ( repoRefs ) > 0 {
2015-10-28 23:07:02 +00:00
return records , nil
}
2015-08-15 07:30:25 +00:00
removedRepositoryRef = true
} else {
2016-06-16 23:06:47 +00:00
// If an ID reference was given AND there is at most one tag
// reference to the image AND all references are within one
// repository, then remove all references.
if isSingleReference ( repoRefs ) {
2016-01-12 23:03:08 +00:00
c := conflictHard
if ! force {
c |= conflictSoft &^ conflictActiveReference
}
2018-02-02 22:18:46 +00:00
if conflict := i . checkImageDeleteConflict ( imgID , c ) ; conflict != nil {
2016-01-12 18:55:34 +00:00
return nil , conflict
}
2016-06-16 23:06:47 +00:00
for _ , repoRef := range repoRefs {
2018-02-02 22:18:46 +00:00
parsedRef , err := i . removeImageRef ( repoRef )
2016-06-16 23:06:47 +00:00
if err != nil {
return nil , err
}
2023-08-26 13:24:46 +00:00
i . LogImageEvent ( imgID . String ( ) , imgID . String ( ) , events . ActionUnTag )
2022-11-24 18:04:31 +00:00
records = append ( records , imagetypes . DeleteResponse { Untagged : reference . FamiliarString ( parsedRef ) } )
2016-06-16 23:06:47 +00:00
}
2015-08-15 07:30:25 +00:00
}
2014-08-05 06:41:30 +00:00
}
2015-04-09 11:59:50 +00:00
2018-02-02 22:18:46 +00:00
if err := i . imageDeleteHelper ( imgID , & records , force , prune , removedRepositoryRef ) ; err != nil {
2016-07-20 23:11:28 +00:00
return nil , err
}
2024-03-12 14:46:12 +00:00
ImageActions . WithValues ( "delete" ) . UpdateSince ( start )
2016-07-20 23:11:28 +00:00
return records , nil
2014-08-05 06:41:30 +00:00
}
2016-06-16 23:06:47 +00:00
// isSingleReference returns true when all references are from one repository
// and there is at most one tag. Returns false for empty input.
func isSingleReference ( repoRefs [ ] reference . Named ) bool {
if len ( repoRefs ) <= 1 {
return len ( repoRefs ) == 1
}
var singleRef reference . Named
canonicalRefs := map [ string ] struct { } { }
for _ , repoRef := range repoRefs {
if _ , isCanonical := repoRef . ( reference . Canonical ) ; isCanonical {
canonicalRefs [ repoRef . Name ( ) ] = struct { } { }
} else if singleRef == nil {
singleRef = repoRef
} else {
return false
}
}
if singleRef == nil {
// Just use first canonical ref
singleRef = repoRefs [ 0 ]
}
_ , ok := canonicalRefs [ singleRef . Name ( ) ]
return len ( canonicalRefs ) == 1 && ok
}
2015-08-15 07:30:25 +00:00
// isImageIDPrefix returns whether the given possiblePrefix is a prefix of the
// given imageID.
func isImageIDPrefix ( imageID , possiblePrefix string ) bool {
2015-11-18 22:20:54 +00:00
if strings . HasPrefix ( imageID , possiblePrefix ) {
return true
}
if i := strings . IndexRune ( imageID , ':' ) ; i >= 0 {
return strings . HasPrefix ( imageID [ i + 1 : ] , possiblePrefix )
}
2015-08-15 07:30:25 +00:00
2015-11-18 22:20:54 +00:00
return false
2015-08-15 07:30:25 +00:00
}
// removeImageRef attempts to parse and remove the given image reference from
// this daemon's store of repository tag/digest references. The given
// repositoryRef must not be an image ID but a repository name followed by an
// optional tag or digest reference. If tag or digest is omitted, the default
// tag is used. Returns the resolved image reference and an error.
2018-02-07 20:52:47 +00:00
func ( i * ImageService ) removeImageRef ( ref reference . Named ) ( reference . Named , error ) {
2017-01-26 00:54:18 +00:00
ref = reference . TagNameOnly ( ref )
2015-08-15 07:30:25 +00:00
// Ignore the boolean value returned, as far as we're concerned, this
// is an idempotent operation and it's okay if the reference didn't
// exist in the first place.
2018-02-02 22:18:46 +00:00
_ , err := i . referenceStore . Delete ( ref )
2015-08-15 07:30:25 +00:00
2015-11-18 22:20:54 +00:00
return ref , err
2015-08-15 07:30:25 +00:00
}
// removeAllReferencesToImageID attempts to remove every reference to the given
// imgID from this daemon's store of repository tag/digest references. Returns
// on the first encountered error. Removed references are logged to this
2016-11-10 16:27:56 +00:00
// daemon's event service. An "Untagged" types.ImageDeleteResponseItem is added to the
2015-08-15 07:30:25 +00:00
// given list of records.
2022-11-24 18:04:31 +00:00
func ( i * ImageService ) removeAllReferencesToImageID ( imgID image . ID , records * [ ] imagetypes . DeleteResponse ) error {
2023-08-26 16:25:27 +00:00
for _ , imageRef := range i . referenceStore . References ( imgID . Digest ( ) ) {
2018-02-02 22:18:46 +00:00
parsedRef , err := i . removeImageRef ( imageRef )
2015-08-15 07:30:25 +00:00
if err != nil {
return err
2014-08-05 06:41:30 +00:00
}
2023-08-26 13:24:46 +00:00
i . LogImageEvent ( imgID . String ( ) , imgID . String ( ) , events . ActionUnTag )
2022-11-24 18:04:31 +00:00
* records = append ( * records , imagetypes . DeleteResponse {
2023-08-26 16:25:27 +00:00
Untagged : reference . FamiliarString ( parsedRef ) ,
} )
2015-08-15 07:30:25 +00:00
}
return nil
}
// ImageDeleteConflict holds a soft or hard conflict and an associated error.
// Implements the error interface.
type imageDeleteConflict struct {
hard bool
2015-11-26 01:57:20 +00:00
used bool
2015-11-18 22:20:54 +00:00
imgID image . ID
2015-08-15 07:30:25 +00:00
message string
}
func ( idc * imageDeleteConflict ) Error ( ) string {
var forceMsg string
if idc . hard {
forceMsg = "cannot be forced"
2014-08-05 06:41:30 +00:00
} else {
2015-08-15 07:30:25 +00:00
forceMsg = "must be forced"
}
2015-11-18 22:20:54 +00:00
return fmt . Sprintf ( "conflict: unable to delete %s (%s) - %s" , stringid . TruncateID ( idc . imgID . String ( ) ) , forceMsg , idc . message )
2015-08-15 07:30:25 +00:00
}
2017-07-19 14:20:13 +00:00
func ( idc * imageDeleteConflict ) Conflict ( ) { }
2015-08-15 07:30:25 +00:00
// imageDeleteHelper attempts to delete the given image from this daemon. If
// the image has any hard delete conflicts (child images or running containers
// using the image) then it cannot be deleted. If the image has any soft delete
// conflicts (any tags/digests referencing the image or any stopped container
// using the image) then it can only be deleted if force is true. If the delete
// succeeds and prune is true, the parent images are also deleted if they do
// not have any soft or hard delete conflicts themselves. Any deleted images
// and untagged references are appended to the given records. If any error or
// conflict is encountered, it will be returned immediately without deleting
// the image. If quiet is true, any encountered conflicts will be ignored and
// the function will return nil immediately without deleting the image.
2022-11-24 18:04:31 +00:00
func ( i * ImageService ) imageDeleteHelper ( imgID image . ID , records * [ ] imagetypes . DeleteResponse , force , prune , quiet bool ) error {
2015-08-15 07:30:25 +00:00
// First, determine if this image has any conflicts. Ignore soft conflicts
// if force is true.
2016-01-12 23:03:08 +00:00
c := conflictHard
if ! force {
c |= conflictSoft
}
2018-02-02 22:18:46 +00:00
if conflict := i . checkImageDeleteConflict ( imgID , c ) ; conflict != nil {
if quiet && ( ! i . imageIsDangling ( imgID ) || conflict . used ) {
2015-11-26 01:57:20 +00:00
// Ignore conflicts UNLESS the image is "dangling" or not being used in
2015-08-15 07:30:25 +00:00
// which case we want the user to know.
return nil
}
// There was a conflict and it's either a hard conflict OR we are not
// forcing deletion on soft conflicts.
return conflict
}
2018-02-02 22:18:46 +00:00
parent , err := i . imageStore . GetParent ( imgID )
2015-11-18 22:20:54 +00:00
if err != nil {
// There may be no parent
parent = ""
}
2015-08-15 07:30:25 +00:00
// Delete all repository tag/digest references to this image.
2018-02-02 22:18:46 +00:00
if err := i . removeAllReferencesToImageID ( imgID , records ) ; err != nil {
2015-08-15 07:30:25 +00:00
return err
}
2018-02-02 22:18:46 +00:00
removedLayers , err := i . imageStore . Delete ( imgID )
2015-11-18 22:20:54 +00:00
if err != nil {
2015-08-15 07:30:25 +00:00
return err
2014-08-05 06:41:30 +00:00
}
2023-08-26 13:24:46 +00:00
i . LogImageEvent ( imgID . String ( ) , imgID . String ( ) , events . ActionDelete )
2022-11-24 18:04:31 +00:00
* records = append ( * records , imagetypes . DeleteResponse { Deleted : imgID . String ( ) } )
2015-11-18 22:20:54 +00:00
for _ , removedLayer := range removedLayers {
2022-11-24 18:04:31 +00:00
* records = append ( * records , imagetypes . DeleteResponse { Deleted : removedLayer . ChainID . String ( ) } )
2015-11-18 22:20:54 +00:00
}
2015-08-15 07:30:25 +00:00
2015-11-18 22:20:54 +00:00
if ! prune || parent == "" {
2014-08-05 06:41:30 +00:00
return nil
}
2015-08-15 07:30:25 +00:00
// We need to prune the parent image. This means delete it if there are
// no tags/digests referencing it and there are no containers using it (
// either running or stopped).
// Do not force prunings, but do so quietly (stopping on any encountered
// conflicts).
2018-02-02 22:18:46 +00:00
return i . imageDeleteHelper ( parent , records , false , true , true )
2015-08-15 07:30:25 +00:00
}
// checkImageDeleteConflict determines whether there are any conflicts
// preventing deletion of the given image from this daemon. A hard conflict is
// any image which has the given image as a parent or any running container
// using the image. A soft conflict is any tags/digest referencing the given
// image or any stopped container using the image. If ignoreSoftConflicts is
// true, this function will not check for soft conflict conditions.
2018-02-07 20:52:47 +00:00
func ( i * ImageService ) checkImageDeleteConflict ( imgID image . ID , mask conflictType ) * imageDeleteConflict {
2016-02-11 23:21:52 +00:00
// Check if the image has any descendant images.
2018-02-02 22:18:46 +00:00
if mask & conflictDependentChild != 0 && len ( i . imageStore . Children ( imgID ) ) > 0 {
2015-08-15 07:30:25 +00:00
return & imageDeleteConflict {
hard : true ,
2015-11-18 22:20:54 +00:00
imgID : imgID ,
2015-08-15 07:30:25 +00:00
message : "image has dependent child images" ,
2014-08-05 06:41:30 +00:00
}
}
2016-01-12 23:03:08 +00:00
if mask & conflictRunningContainer != 0 {
// Check if any running container is using the image.
2016-01-15 23:55:46 +00:00
running := func ( c * container . Container ) bool {
2019-08-19 10:28:37 +00:00
return c . ImageID == imgID && c . IsRunning ( )
2016-01-15 23:55:46 +00:00
}
2022-08-08 10:55:10 +00:00
if ctr := i . containers . First ( running ) ; ctr != nil {
2016-01-15 23:55:46 +00:00
return & imageDeleteConflict {
imgID : imgID ,
hard : true ,
used : true ,
2022-08-08 10:55:10 +00:00
message : fmt . Sprintf ( "image is being used by running container %s" , stringid . TruncateID ( ctr . ID ) ) ,
2015-08-15 07:30:25 +00:00
}
2014-08-05 06:41:30 +00:00
}
}
2015-08-15 07:30:25 +00:00
// Check if any repository tags/digest reference this image.
2018-02-02 22:18:46 +00:00
if mask & conflictActiveReference != 0 && len ( i . referenceStore . References ( imgID . Digest ( ) ) ) > 0 {
2015-08-15 07:30:25 +00:00
return & imageDeleteConflict {
2015-11-18 22:20:54 +00:00
imgID : imgID ,
2016-07-18 11:14:27 +00:00
message : "image is referenced in multiple repositories" ,
2015-08-15 07:30:25 +00:00
}
2015-04-08 02:29:29 +00:00
}
2015-08-15 07:30:25 +00:00
2016-01-12 23:03:08 +00:00
if mask & conflictStoppedContainer != 0 {
// Check if any stopped containers reference this image.
2016-01-15 23:55:46 +00:00
stopped := func ( c * container . Container ) bool {
return ! c . IsRunning ( ) && c . ImageID == imgID
}
2022-08-08 10:55:10 +00:00
if ctr := i . containers . First ( stopped ) ; ctr != nil {
2016-01-15 23:55:46 +00:00
return & imageDeleteConflict {
imgID : imgID ,
used : true ,
2022-08-08 10:55:10 +00:00
message : fmt . Sprintf ( "image is being used by stopped container %s" , stringid . TruncateID ( ctr . ID ) ) ,
2014-08-05 06:41:30 +00:00
}
}
}
2015-08-15 07:30:25 +00:00
2014-08-05 06:41:30 +00:00
return nil
}
2015-08-15 07:30:25 +00:00
// imageIsDangling returns whether the given image is "dangling" which means
// that there are no repository references to the given image and it has no
// child images.
2018-02-07 20:52:47 +00:00
func ( i * ImageService ) imageIsDangling ( imgID image . ID ) bool {
2018-02-02 22:18:46 +00:00
return ! ( len ( i . referenceStore . References ( imgID . Digest ( ) ) ) > 0 || len ( i . imageStore . Children ( imgID ) ) > 0 )
2015-08-15 07:30:25 +00:00
}