image_delete.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. package daemon
  2. import (
  3. "fmt"
  4. "strings"
  5. "github.com/Sirupsen/logrus"
  6. "github.com/docker/docker/api/types"
  7. "github.com/docker/docker/graph"
  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. // FIXME: remove ImageDelete's dependency on Daemon, then move to graph/
  14. func (daemon *Daemon) ImageDelete(name string, force, noprune bool) ([]types.ImageDelete, error) {
  15. list := []types.ImageDelete{}
  16. if err := daemon.imgDeleteHelper(name, &list, true, force, noprune); err != nil {
  17. return nil, err
  18. }
  19. if len(list) == 0 {
  20. return nil, fmt.Errorf("Conflict, %s wasn't deleted", name)
  21. }
  22. return list, nil
  23. }
  24. func (daemon *Daemon) imgDeleteHelper(name string, list *[]types.ImageDelete, first, force, noprune bool) error {
  25. var (
  26. repoName, tag string
  27. tags = []string{}
  28. )
  29. repoAndTags := make(map[string][]string)
  30. // FIXME: please respect DRY and centralize repo+tag parsing in a single central place! -- shykes
  31. repoName, tag = parsers.ParseRepositoryTag(name)
  32. if tag == "" {
  33. tag = graph.DEFAULTTAG
  34. }
  35. if name == "" {
  36. return fmt.Errorf("Image name can not be blank")
  37. }
  38. img, err := daemon.Repositories().LookupImage(name)
  39. if err != nil {
  40. if r, _ := daemon.Repositories().Get(repoName); r != nil {
  41. return fmt.Errorf("No such image: %s", utils.ImageReference(repoName, tag))
  42. }
  43. return fmt.Errorf("No such image: %s", name)
  44. }
  45. if strings.Contains(img.ID, name) {
  46. repoName = ""
  47. tag = ""
  48. }
  49. byParents, err := daemon.Graph().ByParent()
  50. if err != nil {
  51. return err
  52. }
  53. repos := daemon.Repositories().ByID()[img.ID]
  54. //If delete by id, see if the id belong only to one repository
  55. deleteByID := repoName == ""
  56. if deleteByID {
  57. for _, repoAndTag := range repos {
  58. parsedRepo, parsedTag := parsers.ParseRepositoryTag(repoAndTag)
  59. if repoName == "" || repoName == parsedRepo {
  60. repoName = parsedRepo
  61. if parsedTag != "" {
  62. repoAndTags[repoName] = append(repoAndTags[repoName], parsedTag)
  63. }
  64. } else if repoName != parsedRepo && !force && first {
  65. // the id belongs to multiple repos, like base:latest and user:test,
  66. // in that case return conflict
  67. return fmt.Errorf("Conflict, cannot delete image %s because it is tagged in multiple repositories, use -f to force", name)
  68. } else {
  69. //the id belongs to multiple repos, with -f just delete all
  70. repoName = parsedRepo
  71. if parsedTag != "" {
  72. repoAndTags[repoName] = append(repoAndTags[repoName], parsedTag)
  73. }
  74. }
  75. }
  76. } else {
  77. repoAndTags[repoName] = append(repoAndTags[repoName], tag)
  78. }
  79. if !first && len(repoAndTags) > 0 {
  80. return nil
  81. }
  82. if len(repos) <= 1 || (len(repoAndTags) <= 1 && deleteByID) {
  83. if err := daemon.canDeleteImage(img.ID, force); err != nil {
  84. return err
  85. }
  86. }
  87. // Untag the current image
  88. for repoName, tags := range repoAndTags {
  89. for _, tag := range tags {
  90. tagDeleted, err := daemon.Repositories().Delete(repoName, tag)
  91. if err != nil {
  92. return err
  93. }
  94. if tagDeleted {
  95. *list = append(*list, types.ImageDelete{
  96. Untagged: utils.ImageReference(repoName, tag),
  97. })
  98. daemon.EventsService.Log("untag", img.ID, "")
  99. }
  100. }
  101. }
  102. tags = daemon.Repositories().ByID()[img.ID]
  103. if (len(tags) <= 1 && repoName == "") || len(tags) == 0 {
  104. if len(byParents[img.ID]) == 0 {
  105. if err := daemon.Repositories().DeleteAll(img.ID); err != nil {
  106. return err
  107. }
  108. if err := daemon.Graph().Delete(img.ID); err != nil {
  109. return err
  110. }
  111. *list = append(*list, types.ImageDelete{
  112. Deleted: img.ID,
  113. })
  114. daemon.EventsService.Log("delete", img.ID, "")
  115. if img.Parent != "" && !noprune {
  116. err := daemon.imgDeleteHelper(img.Parent, list, false, force, noprune)
  117. if first {
  118. return err
  119. }
  120. }
  121. }
  122. }
  123. return nil
  124. }
  125. func (daemon *Daemon) canDeleteImage(imgID string, force bool) error {
  126. for _, container := range daemon.List() {
  127. if container.ImageID == "" {
  128. // This technically should never happen, but if the container
  129. // has no ImageID then log the situation and move on.
  130. // If we allowed processing to continue then the code later
  131. // on would fail with a "Prefix can't be empty" error even
  132. // though the bad container has nothing to do with the image
  133. // we're trying to delete.
  134. logrus.Errorf("Container %q has no image associated with it!", container.ID)
  135. continue
  136. }
  137. parent, err := daemon.Repositories().LookupImage(container.ImageID)
  138. if err != nil {
  139. if daemon.Graph().IsNotExist(err, container.ImageID) {
  140. return nil
  141. }
  142. return err
  143. }
  144. if err := daemon.graph.WalkHistory(parent, func(p image.Image) error {
  145. if imgID == p.ID {
  146. if container.IsRunning() {
  147. if force {
  148. return fmt.Errorf("Conflict, cannot force delete %s because the running container %s is using it, stop it and retry", stringid.TruncateID(imgID), stringid.TruncateID(container.ID))
  149. }
  150. return fmt.Errorf("Conflict, cannot delete %s because the running container %s is using it, stop it and use -f to force", stringid.TruncateID(imgID), stringid.TruncateID(container.ID))
  151. } else if !force {
  152. return fmt.Errorf("Conflict, cannot delete %s because the container %s is using it, use -f to force", stringid.TruncateID(imgID), stringid.TruncateID(container.ID))
  153. }
  154. }
  155. return nil
  156. }); err != nil {
  157. return err
  158. }
  159. }
  160. return nil
  161. }