1package images // import "github.com/docker/docker/daemon/images" 2 3import ( 4 "fmt" 5 "strings" 6 "time" 7 8 "github.com/docker/distribution/reference" 9 "github.com/docker/docker/api/types" 10 "github.com/docker/docker/container" 11 "github.com/docker/docker/errdefs" 12 "github.com/docker/docker/image" 13 "github.com/docker/docker/pkg/stringid" 14 "github.com/docker/docker/pkg/system" 15 "github.com/pkg/errors" 16) 17 18type conflictType int 19 20const ( 21 conflictDependentChild conflictType = 1 << iota 22 conflictRunningContainer 23 conflictActiveReference 24 conflictStoppedContainer 25 conflictHard = conflictDependentChild | conflictRunningContainer 26 conflictSoft = conflictActiveReference | conflictStoppedContainer 27) 28 29// ImageDelete deletes the image referenced by the given imageRef from this 30// daemon. The given imageRef can be an image ID, ID prefix, or a repository 31// reference (with an optional tag or digest, defaulting to the tag name 32// "latest"). There is differing behavior depending on whether the given 33// imageRef is a repository reference or not. 34// 35// If the given imageRef is a repository reference then that repository 36// reference will be removed. However, if there exists any containers which 37// were created using the same image reference then the repository reference 38// cannot be removed unless either there are other repository references to the 39// same image or force is true. Following removal of the repository reference, 40// the referenced image itself will attempt to be deleted as described below 41// but quietly, meaning any image delete conflicts will cause the image to not 42// be deleted and the conflict will not be reported. 43// 44// There may be conflicts preventing deletion of an image and these conflicts 45// are divided into two categories grouped by their severity: 46// 47// Hard Conflict: 48// - a pull or build using the image. 49// - any descendant image. 50// - any running container using the image. 51// 52// Soft Conflict: 53// - any stopped container using the image. 54// - any repository tag or digest references to the image. 55// 56// The image cannot be removed if there are any hard conflicts and can be 57// removed if there are soft conflicts only if force is true. 58// 59// If prune is true, ancestor images will each attempt to be deleted quietly, 60// meaning any delete conflicts will cause the image to not be deleted and the 61// conflict will not be reported. 62// 63func (i *ImageService) ImageDelete(imageRef string, force, prune bool) ([]types.ImageDeleteResponseItem, error) { 64 start := time.Now() 65 records := []types.ImageDeleteResponseItem{} 66 67 img, err := i.GetImage(imageRef) 68 if err != nil { 69 return nil, err 70 } 71 if !system.IsOSSupported(img.OperatingSystem()) { 72 return nil, errors.Errorf("unable to delete image: %q", system.ErrNotSupportedOperatingSystem) 73 } 74 75 imgID := img.ID() 76 repoRefs := i.referenceStore.References(imgID.Digest()) 77 78 using := func(c *container.Container) bool { 79 return c.ImageID == imgID 80 } 81 82 var removedRepositoryRef bool 83 if !isImageIDPrefix(imgID.String(), imageRef) { 84 // A repository reference was given and should be removed 85 // first. We can only remove this reference if either force is 86 // true, there are multiple repository references to this 87 // image, or there are no containers using the given reference. 88 if !force && isSingleReference(repoRefs) { 89 if container := i.containers.First(using); container != nil { 90 // If we removed the repository reference then 91 // this image would remain "dangling" and since 92 // we really want to avoid that the client must 93 // explicitly force its removal. 94 err := errors.Errorf("conflict: unable to remove repository reference %q (must force) - container %s is using its referenced image %s", imageRef, stringid.TruncateID(container.ID), stringid.TruncateID(imgID.String())) 95 return nil, errdefs.Conflict(err) 96 } 97 } 98 99 parsedRef, err := reference.ParseNormalizedNamed(imageRef) 100 if err != nil { 101 return nil, err 102 } 103 104 parsedRef, err = i.removeImageRef(parsedRef) 105 if err != nil { 106 return nil, err 107 } 108 109 untaggedRecord := types.ImageDeleteResponseItem{Untagged: reference.FamiliarString(parsedRef)} 110 111 i.LogImageEvent(imgID.String(), imgID.String(), "untag") 112 records = append(records, untaggedRecord) 113 114 repoRefs = i.referenceStore.References(imgID.Digest()) 115 116 // If a tag reference was removed and the only remaining 117 // references to the same repository are digest references, 118 // then clean up those digest references. 119 if _, isCanonical := parsedRef.(reference.Canonical); !isCanonical { 120 foundRepoTagRef := false 121 for _, repoRef := range repoRefs { 122 if _, repoRefIsCanonical := repoRef.(reference.Canonical); !repoRefIsCanonical && parsedRef.Name() == repoRef.Name() { 123 foundRepoTagRef = true 124 break 125 } 126 } 127 if !foundRepoTagRef { 128 // Remove canonical references from same repository 129 var remainingRefs []reference.Named 130 for _, repoRef := range repoRefs { 131 if _, repoRefIsCanonical := repoRef.(reference.Canonical); repoRefIsCanonical && parsedRef.Name() == repoRef.Name() { 132 if _, err := i.removeImageRef(repoRef); err != nil { 133 return records, err 134 } 135 136 untaggedRecord := types.ImageDeleteResponseItem{Untagged: reference.FamiliarString(repoRef)} 137 records = append(records, untaggedRecord) 138 } else { 139 remainingRefs = append(remainingRefs, repoRef) 140 141 } 142 } 143 repoRefs = remainingRefs 144 } 145 } 146 147 // If it has remaining references then the untag finished the remove 148 if len(repoRefs) > 0 { 149 return records, nil 150 } 151 152 removedRepositoryRef = true 153 } else { 154 // If an ID reference was given AND there is at most one tag 155 // reference to the image AND all references are within one 156 // repository, then remove all references. 157 if isSingleReference(repoRefs) { 158 c := conflictHard 159 if !force { 160 c |= conflictSoft &^ conflictActiveReference 161 } 162 if conflict := i.checkImageDeleteConflict(imgID, c); conflict != nil { 163 return nil, conflict 164 } 165 166 for _, repoRef := range repoRefs { 167 parsedRef, err := i.removeImageRef(repoRef) 168 if err != nil { 169 return nil, err 170 } 171 172 untaggedRecord := types.ImageDeleteResponseItem{Untagged: reference.FamiliarString(parsedRef)} 173 174 i.LogImageEvent(imgID.String(), imgID.String(), "untag") 175 records = append(records, untaggedRecord) 176 } 177 } 178 } 179 180 if err := i.imageDeleteHelper(imgID, &records, force, prune, removedRepositoryRef); err != nil { 181 return nil, err 182 } 183 184 imageActions.WithValues("delete").UpdateSince(start) 185 186 return records, nil 187} 188 189// isSingleReference returns true when all references are from one repository 190// and there is at most one tag. Returns false for empty input. 191func isSingleReference(repoRefs []reference.Named) bool { 192 if len(repoRefs) <= 1 { 193 return len(repoRefs) == 1 194 } 195 var singleRef reference.Named 196 canonicalRefs := map[string]struct{}{} 197 for _, repoRef := range repoRefs { 198 if _, isCanonical := repoRef.(reference.Canonical); isCanonical { 199 canonicalRefs[repoRef.Name()] = struct{}{} 200 } else if singleRef == nil { 201 singleRef = repoRef 202 } else { 203 return false 204 } 205 } 206 if singleRef == nil { 207 // Just use first canonical ref 208 singleRef = repoRefs[0] 209 } 210 _, ok := canonicalRefs[singleRef.Name()] 211 return len(canonicalRefs) == 1 && ok 212} 213 214// isImageIDPrefix returns whether the given possiblePrefix is a prefix of the 215// given imageID. 216func isImageIDPrefix(imageID, possiblePrefix string) bool { 217 if strings.HasPrefix(imageID, possiblePrefix) { 218 return true 219 } 220 221 if i := strings.IndexRune(imageID, ':'); i >= 0 { 222 return strings.HasPrefix(imageID[i+1:], possiblePrefix) 223 } 224 225 return false 226} 227 228// removeImageRef attempts to parse and remove the given image reference from 229// this daemon's store of repository tag/digest references. The given 230// repositoryRef must not be an image ID but a repository name followed by an 231// optional tag or digest reference. If tag or digest is omitted, the default 232// tag is used. Returns the resolved image reference and an error. 233func (i *ImageService) removeImageRef(ref reference.Named) (reference.Named, error) { 234 ref = reference.TagNameOnly(ref) 235 236 // Ignore the boolean value returned, as far as we're concerned, this 237 // is an idempotent operation and it's okay if the reference didn't 238 // exist in the first place. 239 _, err := i.referenceStore.Delete(ref) 240 241 return ref, err 242} 243 244// removeAllReferencesToImageID attempts to remove every reference to the given 245// imgID from this daemon's store of repository tag/digest references. Returns 246// on the first encountered error. Removed references are logged to this 247// daemon's event service. An "Untagged" types.ImageDeleteResponseItem is added to the 248// given list of records. 249func (i *ImageService) removeAllReferencesToImageID(imgID image.ID, records *[]types.ImageDeleteResponseItem) error { 250 imageRefs := i.referenceStore.References(imgID.Digest()) 251 252 for _, imageRef := range imageRefs { 253 parsedRef, err := i.removeImageRef(imageRef) 254 if err != nil { 255 return err 256 } 257 258 untaggedRecord := types.ImageDeleteResponseItem{Untagged: reference.FamiliarString(parsedRef)} 259 260 i.LogImageEvent(imgID.String(), imgID.String(), "untag") 261 *records = append(*records, untaggedRecord) 262 } 263 264 return nil 265} 266 267// ImageDeleteConflict holds a soft or hard conflict and an associated error. 268// Implements the error interface. 269type imageDeleteConflict struct { 270 hard bool 271 used bool 272 imgID image.ID 273 message string 274} 275 276func (idc *imageDeleteConflict) Error() string { 277 var forceMsg string 278 if idc.hard { 279 forceMsg = "cannot be forced" 280 } else { 281 forceMsg = "must be forced" 282 } 283 284 return fmt.Sprintf("conflict: unable to delete %s (%s) - %s", stringid.TruncateID(idc.imgID.String()), forceMsg, idc.message) 285} 286 287func (idc *imageDeleteConflict) Conflict() {} 288 289// imageDeleteHelper attempts to delete the given image from this daemon. If 290// the image has any hard delete conflicts (child images or running containers 291// using the image) then it cannot be deleted. If the image has any soft delete 292// conflicts (any tags/digests referencing the image or any stopped container 293// using the image) then it can only be deleted if force is true. If the delete 294// succeeds and prune is true, the parent images are also deleted if they do 295// not have any soft or hard delete conflicts themselves. Any deleted images 296// and untagged references are appended to the given records. If any error or 297// conflict is encountered, it will be returned immediately without deleting 298// the image. If quiet is true, any encountered conflicts will be ignored and 299// the function will return nil immediately without deleting the image. 300func (i *ImageService) imageDeleteHelper(imgID image.ID, records *[]types.ImageDeleteResponseItem, force, prune, quiet bool) error { 301 // First, determine if this image has any conflicts. Ignore soft conflicts 302 // if force is true. 303 c := conflictHard 304 if !force { 305 c |= conflictSoft 306 } 307 if conflict := i.checkImageDeleteConflict(imgID, c); conflict != nil { 308 if quiet && (!i.imageIsDangling(imgID) || conflict.used) { 309 // Ignore conflicts UNLESS the image is "dangling" or not being used in 310 // which case we want the user to know. 311 return nil 312 } 313 314 // There was a conflict and it's either a hard conflict OR we are not 315 // forcing deletion on soft conflicts. 316 return conflict 317 } 318 319 parent, err := i.imageStore.GetParent(imgID) 320 if err != nil { 321 // There may be no parent 322 parent = "" 323 } 324 325 // Delete all repository tag/digest references to this image. 326 if err := i.removeAllReferencesToImageID(imgID, records); err != nil { 327 return err 328 } 329 330 removedLayers, err := i.imageStore.Delete(imgID) 331 if err != nil { 332 return err 333 } 334 335 i.LogImageEvent(imgID.String(), imgID.String(), "delete") 336 *records = append(*records, types.ImageDeleteResponseItem{Deleted: imgID.String()}) 337 for _, removedLayer := range removedLayers { 338 *records = append(*records, types.ImageDeleteResponseItem{Deleted: removedLayer.ChainID.String()}) 339 } 340 341 if !prune || parent == "" { 342 return nil 343 } 344 345 // We need to prune the parent image. This means delete it if there are 346 // no tags/digests referencing it and there are no containers using it ( 347 // either running or stopped). 348 // Do not force prunings, but do so quietly (stopping on any encountered 349 // conflicts). 350 return i.imageDeleteHelper(parent, records, false, true, true) 351} 352 353// checkImageDeleteConflict determines whether there are any conflicts 354// preventing deletion of the given image from this daemon. A hard conflict is 355// any image which has the given image as a parent or any running container 356// using the image. A soft conflict is any tags/digest referencing the given 357// image or any stopped container using the image. If ignoreSoftConflicts is 358// true, this function will not check for soft conflict conditions. 359func (i *ImageService) checkImageDeleteConflict(imgID image.ID, mask conflictType) *imageDeleteConflict { 360 // Check if the image has any descendant images. 361 if mask&conflictDependentChild != 0 && len(i.imageStore.Children(imgID)) > 0 { 362 return &imageDeleteConflict{ 363 hard: true, 364 imgID: imgID, 365 message: "image has dependent child images", 366 } 367 } 368 369 if mask&conflictRunningContainer != 0 { 370 // Check if any running container is using the image. 371 running := func(c *container.Container) bool { 372 return c.IsRunning() && c.ImageID == imgID 373 } 374 if container := i.containers.First(running); container != nil { 375 return &imageDeleteConflict{ 376 imgID: imgID, 377 hard: true, 378 used: true, 379 message: fmt.Sprintf("image is being used by running container %s", stringid.TruncateID(container.ID)), 380 } 381 } 382 } 383 384 // Check if any repository tags/digest reference this image. 385 if mask&conflictActiveReference != 0 && len(i.referenceStore.References(imgID.Digest())) > 0 { 386 return &imageDeleteConflict{ 387 imgID: imgID, 388 message: "image is referenced in multiple repositories", 389 } 390 } 391 392 if mask&conflictStoppedContainer != 0 { 393 // Check if any stopped containers reference this image. 394 stopped := func(c *container.Container) bool { 395 return !c.IsRunning() && c.ImageID == imgID 396 } 397 if container := i.containers.First(stopped); container != nil { 398 return &imageDeleteConflict{ 399 imgID: imgID, 400 used: true, 401 message: fmt.Sprintf("image is being used by stopped container %s", stringid.TruncateID(container.ID)), 402 } 403 } 404 } 405 406 return nil 407} 408 409// imageIsDangling returns whether the given image is "dangling" which means 410// that there are no repository references to the given image and it has no 411// child images. 412func (i *ImageService) imageIsDangling(imgID image.ID) bool { 413 return !(len(i.referenceStore.References(imgID.Digest())) > 0 || len(i.imageStore.Children(imgID)) > 0) 414} 415