image_delete.go 5.2 KB

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