github.com/psychoss/docker@v1.9.0/daemon/image_delete.go (about) 1 package daemon 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/docker/docker/api/types" 8 derr "github.com/docker/docker/errors" 9 "github.com/docker/docker/graph/tags" 10 "github.com/docker/docker/image" 11 "github.com/docker/docker/pkg/parsers" 12 "github.com/docker/docker/pkg/stringid" 13 "github.com/docker/docker/utils" 14 ) 15 16 // ImageDelete deletes the image referenced by the given imageRef from this 17 // daemon. The given imageRef can be an image ID, ID prefix, or a repository 18 // reference (with an optional tag or digest, defaulting to the tag name 19 // "latest"). There is differing behavior depending on whether the given 20 // imageRef is a repository reference or not. 21 // 22 // If the given imageRef is a repository reference then that repository 23 // reference will be removed. However, if there exists any containers which 24 // were created using the same image reference then the repository reference 25 // cannot be removed unless either there are other repository references to the 26 // same image or force is true. Following removal of the repository reference, 27 // the referenced image itself will attempt to be deleted as described below 28 // but quietly, meaning any image delete conflicts will cause the image to not 29 // be deleted and the conflict will not be reported. 30 // 31 // There may be conflicts preventing deletion of an image and these conflicts 32 // are divided into two categories grouped by their severity: 33 // 34 // Hard Conflict: 35 // - a pull or build using the image. 36 // - any descendent image. 37 // - any running container using the image. 38 // 39 // Soft Conflict: 40 // - any stopped container using the image. 41 // - any repository tag or digest references to the image. 42 // 43 // The image cannot be removed if there are any hard conflicts and can be 44 // removed if there are soft conflicts only if force is true. 45 // 46 // If prune is true, ancestor images will each attempt to be deleted quietly, 47 // meaning any delete conflicts will cause the image to not be deleted and the 48 // conflict will not be reported. 49 // 50 // FIXME: remove ImageDelete's dependency on Daemon, then move to the graph 51 // package. This would require that we no longer need the daemon to determine 52 // whether images are being used by a stopped or running container. 53 func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.ImageDelete, error) { 54 records := []types.ImageDelete{} 55 56 img, err := daemon.repositories.LookupImage(imageRef) 57 if err != nil { 58 return nil, err 59 } 60 61 var removedRepositoryRef bool 62 if !isImageIDPrefix(img.ID, imageRef) { 63 // A repository reference was given and should be removed 64 // first. We can only remove this reference if either force is 65 // true, there are multiple repository references to this 66 // image, or there are no containers using the given reference. 67 if !(force || daemon.imageHasMultipleRepositoryReferences(img.ID)) { 68 if container := daemon.getContainerUsingImage(img.ID); container != nil { 69 // If we removed the repository reference then 70 // this image would remain "dangling" and since 71 // we really want to avoid that the client must 72 // explicitly force its removal. 73 return nil, derr.ErrorCodeImgDelUsed.WithArgs(imageRef, stringid.TruncateID(container.ID), stringid.TruncateID(img.ID)) 74 } 75 } 76 77 parsedRef, err := daemon.removeImageRef(imageRef) 78 if err != nil { 79 return nil, err 80 } 81 82 untaggedRecord := types.ImageDelete{Untagged: parsedRef} 83 84 daemon.EventsService.Log("untag", img.ID, "") 85 records = append(records, untaggedRecord) 86 87 // If has remaining references then untag finishes the remove 88 if daemon.repositories.HasReferences(img) { 89 return records, nil 90 } 91 92 removedRepositoryRef = true 93 } else { 94 // If an ID reference was given AND there is exactly one 95 // repository reference to the image then we will want to 96 // remove that reference. 97 // FIXME: Is this the behavior we want? 98 repoRefs := daemon.repositories.ByID()[img.ID] 99 if len(repoRefs) == 1 { 100 parsedRef, err := daemon.removeImageRef(repoRefs[0]) 101 if err != nil { 102 return nil, err 103 } 104 105 untaggedRecord := types.ImageDelete{Untagged: parsedRef} 106 107 daemon.EventsService.Log("untag", img.ID, "") 108 records = append(records, untaggedRecord) 109 } 110 } 111 112 return records, daemon.imageDeleteHelper(img, &records, force, prune, removedRepositoryRef) 113 } 114 115 // isImageIDPrefix returns whether the given possiblePrefix is a prefix of the 116 // given imageID. 117 func isImageIDPrefix(imageID, possiblePrefix string) bool { 118 return strings.HasPrefix(imageID, possiblePrefix) 119 } 120 121 // imageHasMultipleRepositoryReferences returns whether there are multiple 122 // repository references to the given imageID. 123 func (daemon *Daemon) imageHasMultipleRepositoryReferences(imageID string) bool { 124 return len(daemon.repositories.ByID()[imageID]) > 1 125 } 126 127 // getContainerUsingImage returns a container that was created using the given 128 // imageID. Returns nil if there is no such container. 129 func (daemon *Daemon) getContainerUsingImage(imageID string) *Container { 130 for _, container := range daemon.List() { 131 if container.ImageID == imageID { 132 return container 133 } 134 } 135 136 return nil 137 } 138 139 // removeImageRef attempts to parse and remove the given image reference from 140 // this daemon's store of repository tag/digest references. The given 141 // repositoryRef must not be an image ID but a repository name followed by an 142 // optional tag or digest reference. If tag or digest is omitted, the default 143 // tag is used. Returns the resolved image reference and an error. 144 func (daemon *Daemon) removeImageRef(repositoryRef string) (string, error) { 145 repository, ref := parsers.ParseRepositoryTag(repositoryRef) 146 if ref == "" { 147 ref = tags.DefaultTag 148 } 149 150 // Ignore the boolean value returned, as far as we're concerned, this 151 // is an idempotent operation and it's okay if the reference didn't 152 // exist in the first place. 153 _, err := daemon.repositories.Delete(repository, ref) 154 155 return utils.ImageReference(repository, ref), err 156 } 157 158 // removeAllReferencesToImageID attempts to remove every reference to the given 159 // imgID from this daemon's store of repository tag/digest references. Returns 160 // on the first encountered error. Removed references are logged to this 161 // daemon's event service. An "Untagged" types.ImageDelete is added to the 162 // given list of records. 163 func (daemon *Daemon) removeAllReferencesToImageID(imgID string, records *[]types.ImageDelete) error { 164 imageRefs := daemon.repositories.ByID()[imgID] 165 166 for _, imageRef := range imageRefs { 167 parsedRef, err := daemon.removeImageRef(imageRef) 168 if err != nil { 169 return err 170 } 171 172 untaggedRecord := types.ImageDelete{Untagged: parsedRef} 173 174 daemon.EventsService.Log("untag", imgID, "") 175 *records = append(*records, untaggedRecord) 176 } 177 178 return nil 179 } 180 181 // ImageDeleteConflict holds a soft or hard conflict and an associated error. 182 // Implements the error interface. 183 type imageDeleteConflict struct { 184 hard bool 185 imgID string 186 message string 187 } 188 189 func (idc *imageDeleteConflict) Error() string { 190 var forceMsg string 191 if idc.hard { 192 forceMsg = "cannot be forced" 193 } else { 194 forceMsg = "must be forced" 195 } 196 197 return fmt.Sprintf("conflict: unable to delete %s (%s) - %s", stringid.TruncateID(idc.imgID), forceMsg, idc.message) 198 } 199 200 // imageDeleteHelper attempts to delete the given image from this daemon. If 201 // the image has any hard delete conflicts (child images or running containers 202 // using the image) then it cannot be deleted. If the image has any soft delete 203 // conflicts (any tags/digests referencing the image or any stopped container 204 // using the image) then it can only be deleted if force is true. If the delete 205 // succeeds and prune is true, the parent images are also deleted if they do 206 // not have any soft or hard delete conflicts themselves. Any deleted images 207 // and untagged references are appended to the given records. If any error or 208 // conflict is encountered, it will be returned immediately without deleting 209 // the image. If quiet is true, any encountered conflicts will be ignored and 210 // the function will return nil immediately without deleting the image. 211 func (daemon *Daemon) imageDeleteHelper(img *image.Image, records *[]types.ImageDelete, force, prune, quiet bool) error { 212 // First, determine if this image has any conflicts. Ignore soft conflicts 213 // if force is true. 214 if conflict := daemon.checkImageDeleteConflict(img, force); conflict != nil { 215 if quiet && !daemon.imageIsDangling(img) { 216 // Ignore conflicts UNLESS the image is "dangling" in 217 // which case we want the user to know. 218 return nil 219 } 220 221 // There was a conflict and it's either a hard conflict OR we are not 222 // forcing deletion on soft conflicts. 223 return conflict 224 } 225 226 // Delete all repository tag/digest references to this image. 227 if err := daemon.removeAllReferencesToImageID(img.ID, records); err != nil { 228 return err 229 } 230 231 if err := daemon.Graph().Delete(img.ID); err != nil { 232 return err 233 } 234 235 daemon.EventsService.Log("delete", img.ID, "") 236 *records = append(*records, types.ImageDelete{Deleted: img.ID}) 237 238 if !prune || img.Parent == "" { 239 return nil 240 } 241 242 // We need to prune the parent image. This means delete it if there are 243 // no tags/digests referencing it and there are no containers using it ( 244 // either running or stopped). 245 parentImg, err := daemon.Graph().Get(img.Parent) 246 if err != nil { 247 return derr.ErrorCodeImgNoParent.WithArgs(err) 248 } 249 250 // Do not force prunings, but do so quietly (stopping on any encountered 251 // conflicts). 252 return daemon.imageDeleteHelper(parentImg, records, false, true, true) 253 } 254 255 // checkImageDeleteConflict determines whether there are any conflicts 256 // preventing deletion of the given image from this daemon. A hard conflict is 257 // any image which has the given image as a parent or any running container 258 // using the image. A soft conflict is any tags/digest referencing the given 259 // image or any stopped container using the image. If ignoreSoftConflicts is 260 // true, this function will not check for soft conflict conditions. 261 func (daemon *Daemon) checkImageDeleteConflict(img *image.Image, ignoreSoftConflicts bool) *imageDeleteConflict { 262 // Check for hard conflicts first. 263 if conflict := daemon.checkImageDeleteHardConflict(img); conflict != nil { 264 return conflict 265 } 266 267 // Then check for soft conflicts. 268 if ignoreSoftConflicts { 269 // Don't bother checking for soft conflicts. 270 return nil 271 } 272 273 return daemon.checkImageDeleteSoftConflict(img) 274 } 275 276 func (daemon *Daemon) checkImageDeleteHardConflict(img *image.Image) *imageDeleteConflict { 277 // Check if the image ID is being used by a pull or build. 278 if daemon.Graph().IsHeld(img.ID) { 279 return &imageDeleteConflict{ 280 hard: true, 281 imgID: img.ID, 282 message: "image is held by an ongoing pull or build", 283 } 284 } 285 286 // Check if the image has any descendent images. 287 if daemon.Graph().HasChildren(img.ID) { 288 return &imageDeleteConflict{ 289 hard: true, 290 imgID: img.ID, 291 message: "image has dependent child images", 292 } 293 } 294 295 // Check if any running container is using the image. 296 for _, container := range daemon.List() { 297 if !container.IsRunning() { 298 // Skip this until we check for soft conflicts later. 299 continue 300 } 301 302 if container.ImageID == img.ID { 303 return &imageDeleteConflict{ 304 imgID: img.ID, 305 hard: true, 306 message: fmt.Sprintf("image is being used by running container %s", stringid.TruncateID(container.ID)), 307 } 308 } 309 } 310 311 return nil 312 } 313 314 func (daemon *Daemon) checkImageDeleteSoftConflict(img *image.Image) *imageDeleteConflict { 315 // Check if any repository tags/digest reference this image. 316 if daemon.repositories.HasReferences(img) { 317 return &imageDeleteConflict{ 318 imgID: img.ID, 319 message: "image is referenced in one or more repositories", 320 } 321 } 322 323 // Check if any stopped containers reference this image. 324 for _, container := range daemon.List() { 325 if container.IsRunning() { 326 // Skip this as it was checked above in hard conflict conditions. 327 continue 328 } 329 330 if container.ImageID == img.ID { 331 return &imageDeleteConflict{ 332 imgID: img.ID, 333 message: fmt.Sprintf("image is being used by stopped container %s", stringid.TruncateID(container.ID)), 334 } 335 } 336 } 337 338 return nil 339 } 340 341 // imageIsDangling returns whether the given image is "dangling" which means 342 // that there are no repository references to the given image and it has no 343 // child images. 344 func (daemon *Daemon) imageIsDangling(img *image.Image) bool { 345 return !(daemon.repositories.HasReferences(img) || daemon.Graph().HasChildren(img.ID)) 346 }