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