image_delete.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. package daemon
  2. import (
  3. "fmt"
  4. "strings"
  5. "github.com/docker/docker/engine"
  6. "github.com/docker/docker/graph"
  7. "github.com/docker/docker/image"
  8. "github.com/docker/docker/pkg/parsers"
  9. "github.com/docker/docker/utils"
  10. )
  11. func (daemon *Daemon) ImageDelete(job *engine.Job) engine.Status {
  12. if n := len(job.Args); n != 1 {
  13. return job.Errorf("Usage: %s IMAGE", job.Name)
  14. }
  15. imgs := engine.NewTable("", 0)
  16. if err := daemon.DeleteImage(job.Eng, job.Args[0], imgs, true, job.GetenvBool("force"), job.GetenvBool("noprune")); err != nil {
  17. return job.Error(err)
  18. }
  19. if len(imgs.Data) == 0 {
  20. return job.Errorf("Conflict, %s wasn't deleted", job.Args[0])
  21. }
  22. if _, err := imgs.WriteListTo(job.Stdout); err != nil {
  23. return job.Error(err)
  24. }
  25. return engine.StatusOK
  26. }
  27. // FIXME: make this private and use the job instead
  28. func (daemon *Daemon) DeleteImage(eng *engine.Engine, name string, imgs *engine.Table, first, force, noprune bool) error {
  29. var (
  30. repoName, tag string
  31. tags = []string{}
  32. )
  33. // FIXME: please respect DRY and centralize repo+tag parsing in a single central place! -- shykes
  34. repoName, tag = parsers.ParseRepositoryTag(name)
  35. if tag == "" {
  36. tag = graph.DEFAULTTAG
  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:%s", 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. if repoName == "" {
  56. for _, repoAndTag := range repos {
  57. parsedRepo, parsedTag := parsers.ParseRepositoryTag(repoAndTag)
  58. if repoName == "" || repoName == parsedRepo {
  59. repoName = parsedRepo
  60. if parsedTag != "" {
  61. tags = append(tags, parsedTag)
  62. }
  63. } else if repoName != parsedRepo && !force {
  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. }
  68. }
  69. } else {
  70. tags = append(tags, tag)
  71. }
  72. if !first && len(tags) > 0 {
  73. return nil
  74. }
  75. if len(repos) <= 1 {
  76. if err := daemon.canDeleteImage(img.ID, force); err != nil {
  77. return err
  78. }
  79. }
  80. // Untag the current image
  81. for _, tag := range tags {
  82. tagDeleted, err := daemon.Repositories().Delete(repoName, tag)
  83. if err != nil {
  84. return err
  85. }
  86. if tagDeleted {
  87. out := &engine.Env{}
  88. out.Set("Untagged", repoName+":"+tag)
  89. imgs.Add(out)
  90. eng.Job("log", "untag", img.ID, "").Run()
  91. }
  92. }
  93. tags = daemon.Repositories().ByID()[img.ID]
  94. if (len(tags) <= 1 && repoName == "") || len(tags) == 0 {
  95. if len(byParents[img.ID]) == 0 {
  96. if err := daemon.Repositories().DeleteAll(img.ID); err != nil {
  97. return err
  98. }
  99. if err := daemon.Graph().Delete(img.ID); err != nil {
  100. return err
  101. }
  102. out := &engine.Env{}
  103. out.SetJson("Deleted", img.ID)
  104. imgs.Add(out)
  105. eng.Job("log", "delete", img.ID, "").Run()
  106. if img.Parent != "" && !noprune {
  107. err := daemon.DeleteImage(eng, img.Parent, imgs, false, force, noprune)
  108. if first {
  109. return err
  110. }
  111. }
  112. }
  113. }
  114. return nil
  115. }
  116. func (daemon *Daemon) canDeleteImage(imgID string, force bool) error {
  117. for _, container := range daemon.List() {
  118. parent, err := daemon.Repositories().LookupImage(container.ImageID)
  119. if err != nil {
  120. if daemon.Graph().IsNotExist(err) {
  121. return nil
  122. }
  123. return err
  124. }
  125. if err := parent.WalkHistory(func(p *image.Image) error {
  126. if imgID == p.ID {
  127. if container.IsRunning() {
  128. if force {
  129. return fmt.Errorf("Conflict, cannot force delete %s because the running container %s is using it, stop it and retry", utils.TruncateID(imgID), utils.TruncateID(container.ID))
  130. }
  131. return fmt.Errorf("Conflict, cannot delete %s because the running container %s is using it, stop it and use -f to force", utils.TruncateID(imgID), utils.TruncateID(container.ID))
  132. } else if !force {
  133. return fmt.Errorf("Conflict, cannot delete %s because the container %s is using it, use -f to force", utils.TruncateID(imgID), utils.TruncateID(container.ID))
  134. }
  135. }
  136. return nil
  137. }); err != nil {
  138. return err
  139. }
  140. }
  141. return nil
  142. }