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