image_delete.go 12 KB

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