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