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