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