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