Browse code

Update rmi logic for canonical references

Updates the rmi code to treat canonical references as related to tagged references from the same repository during deletion.
Canonical references with a different repository name will be treated as separate references.
Updates the remove by ID logic to still remove an image if there is a single tag reference and only canonical references to the same repository remaining.

Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)

Derek McGowan authored on 2016/06/17 08:06:47
Showing 1 changed files
... ...
@@ -104,27 +104,34 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
104 104
 
105 105
 		repoRefs = daemon.referenceStore.References(imgID)
106 106
 
107
-		// If this is a tag reference and all the remaining references
108
-		// to this image are digest references, delete the remaining
109
-		// references so that they don't prevent removal of the image.
107
+		// If a tag reference was removed and the only remaining
108
+		// references to the same repository are digest references,
109
+		// then clean up those digest references.
110 110
 		if _, isCanonical := parsedRef.(reference.Canonical); !isCanonical {
111
-			foundTagRef := false
111
+			foundRepoTagRef := false
112 112
 			for _, repoRef := range repoRefs {
113
-				if _, repoRefIsCanonical := repoRef.(reference.Canonical); !repoRefIsCanonical {
114
-					foundTagRef = true
113
+				if _, repoRefIsCanonical := repoRef.(reference.Canonical); !repoRefIsCanonical && parsedRef.Name() == repoRef.Name() {
114
+					foundRepoTagRef = true
115 115
 					break
116 116
 				}
117 117
 			}
118
-			if !foundTagRef {
118
+			if !foundRepoTagRef {
119
+				// Remove canonical references from same repository
120
+				remainingRefs := []reference.Named{}
119 121
 				for _, repoRef := range repoRefs {
120
-					if _, err := daemon.removeImageRef(repoRef); err != nil {
121
-						return records, err
122
-					}
122
+					if _, repoRefIsCanonical := repoRef.(reference.Canonical); repoRefIsCanonical && parsedRef.Name() == repoRef.Name() {
123
+						if _, err := daemon.removeImageRef(repoRef); err != nil {
124
+							return records, err
125
+						}
126
+
127
+						untaggedRecord := types.ImageDelete{Untagged: repoRef.String()}
128
+						records = append(records, untaggedRecord)
129
+					} else {
130
+						remainingRefs = append(remainingRefs, repoRef)
123 131
 
124
-					untaggedRecord := types.ImageDelete{Untagged: repoRef.String()}
125
-					records = append(records, untaggedRecord)
132
+					}
126 133
 				}
127
-				repoRefs = []reference.Named{}
134
+				repoRefs = remainingRefs
128 135
 			}
129 136
 		}
130 137
 
... ...
@@ -135,11 +142,10 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
135 135
 
136 136
 		removedRepositoryRef = true
137 137
 	} else {
138
-		// If an ID reference was given AND there is exactly one
139
-		// repository reference to the image then we will want to
140
-		// remove that reference.
141
-		// FIXME: Is this the behavior we want?
142
-		if len(repoRefs) == 1 {
138
+		// If an ID reference was given AND there is at most one tag
139
+		// reference to the image AND all references are within one
140
+		// repository, then remove all references.
141
+		if isSingleReference(repoRefs) {
143 142
 			c := conflictHard
144 143
 			if !force {
145 144
 				c |= conflictSoft &^ conflictActiveReference
... ...
@@ -148,21 +154,48 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
148 148
 				return nil, conflict
149 149
 			}
150 150
 
151
-			parsedRef, err := daemon.removeImageRef(repoRefs[0])
152
-			if err != nil {
153
-				return nil, err
154
-			}
151
+			for _, repoRef := range repoRefs {
152
+				parsedRef, err := daemon.removeImageRef(repoRef)
153
+				if err != nil {
154
+					return nil, err
155
+				}
155 156
 
156
-			untaggedRecord := types.ImageDelete{Untagged: parsedRef.String()}
157
+				untaggedRecord := types.ImageDelete{Untagged: parsedRef.String()}
157 158
 
158
-			daemon.LogImageEvent(imgID.String(), imgID.String(), "untag")
159
-			records = append(records, untaggedRecord)
159
+				daemon.LogImageEvent(imgID.String(), imgID.String(), "untag")
160
+				records = append(records, untaggedRecord)
161
+			}
160 162
 		}
161 163
 	}
162 164
 
163 165
 	return records, daemon.imageDeleteHelper(imgID, &records, force, prune, removedRepositoryRef)
164 166
 }
165 167
 
168
+// isSingleReference returns true when all references are from one repository
169
+// and there is at most one tag. Returns false for empty input.
170
+func isSingleReference(repoRefs []reference.Named) bool {
171
+	if len(repoRefs) <= 1 {
172
+		return len(repoRefs) == 1
173
+	}
174
+	var singleRef reference.Named
175
+	canonicalRefs := map[string]struct{}{}
176
+	for _, repoRef := range repoRefs {
177
+		if _, isCanonical := repoRef.(reference.Canonical); isCanonical {
178
+			canonicalRefs[repoRef.Name()] = struct{}{}
179
+		} else if singleRef == nil {
180
+			singleRef = repoRef
181
+		} else {
182
+			return false
183
+		}
184
+	}
185
+	if singleRef == nil {
186
+		// Just use first canonical ref
187
+		singleRef = repoRefs[0]
188
+	}
189
+	_, ok := canonicalRefs[singleRef.Name()]
190
+	return len(canonicalRefs) == 1 && ok
191
+}
192
+
166 193
 // isImageIDPrefix returns whether the given possiblePrefix is a prefix of the
167 194
 // given imageID.
168 195
 func isImageIDPrefix(imageID, possiblePrefix string) bool {