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