Browse code

Fix rmi by ID untagging image on error

Do not untag image if it would later get a hard conflict because of running containers.

Fixes #18873

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>

Tonis Tiigi authored on 2016/01/13 03:55:34
Showing 2 changed files
... ...
@@ -102,6 +102,10 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
102 102
 		// remove that reference.
103 103
 		// FIXME: Is this the behavior we want?
104 104
 		if len(repoRefs) == 1 {
105
+			if conflict := daemon.checkImageDeleteConflict(imgID, force, true); conflict != nil {
106
+				return nil, conflict
107
+			}
108
+
105 109
 			parsedRef, err := daemon.removeImageRef(repoRefs[0])
106 110
 			if err != nil {
107 111
 				return nil, err
... ...
@@ -215,7 +219,7 @@ func (idc *imageDeleteConflict) Error() string {
215 215
 func (daemon *Daemon) imageDeleteHelper(imgID image.ID, records *[]types.ImageDelete, force, prune, quiet bool) error {
216 216
 	// First, determine if this image has any conflicts. Ignore soft conflicts
217 217
 	// if force is true.
218
-	if conflict := daemon.checkImageDeleteConflict(imgID, force); conflict != nil {
218
+	if conflict := daemon.checkImageDeleteConflict(imgID, force, false); conflict != nil {
219 219
 		if quiet && (!daemon.imageIsDangling(imgID) || conflict.used) {
220 220
 			// Ignore conflicts UNLESS the image is "dangling" or not being used in
221 221
 			// which case we want the user to know.
... ...
@@ -267,7 +271,7 @@ func (daemon *Daemon) imageDeleteHelper(imgID image.ID, records *[]types.ImageDe
267 267
 // using the image. A soft conflict is any tags/digest referencing the given
268 268
 // image or any stopped container using the image. If ignoreSoftConflicts is
269 269
 // true, this function will not check for soft conflict conditions.
270
-func (daemon *Daemon) checkImageDeleteConflict(imgID image.ID, ignoreSoftConflicts bool) *imageDeleteConflict {
270
+func (daemon *Daemon) checkImageDeleteConflict(imgID image.ID, ignoreSoftConflicts bool, ignoreRefConflict bool) *imageDeleteConflict {
271 271
 	// Check for hard conflicts first.
272 272
 	if conflict := daemon.checkImageDeleteHardConflict(imgID); conflict != nil {
273 273
 		return conflict
... ...
@@ -279,7 +283,7 @@ func (daemon *Daemon) checkImageDeleteConflict(imgID image.ID, ignoreSoftConflic
279 279
 		return nil
280 280
 	}
281 281
 
282
-	return daemon.checkImageDeleteSoftConflict(imgID)
282
+	return daemon.checkImageDeleteSoftConflict(imgID, ignoreRefConflict)
283 283
 }
284 284
 
285 285
 func (daemon *Daemon) checkImageDeleteHardConflict(imgID image.ID) *imageDeleteConflict {
... ...
@@ -312,9 +316,9 @@ func (daemon *Daemon) checkImageDeleteHardConflict(imgID image.ID) *imageDeleteC
312 312
 	return nil
313 313
 }
314 314
 
315
-func (daemon *Daemon) checkImageDeleteSoftConflict(imgID image.ID) *imageDeleteConflict {
315
+func (daemon *Daemon) checkImageDeleteSoftConflict(imgID image.ID, ignoreRefConflict bool) *imageDeleteConflict {
316 316
 	// Check if any repository tags/digest reference this image.
317
-	if len(daemon.referenceStore.References(imgID)) > 0 {
317
+	if !ignoreRefConflict && len(daemon.referenceStore.References(imgID)) > 0 {
318 318
 		return &imageDeleteConflict{
319 319
 			imgID:   imgID,
320 320
 			message: "image is referenced in one or more repositories",
... ...
@@ -337,3 +337,20 @@ func (s *DockerSuite) TestRmiWithParentInUse(c *check.C) {
337 337
 
338 338
 	dockerCmd(c, "rmi", imageID)
339 339
 }
340
+
341
+// #18873
342
+func (s *DockerSuite) TestRmiByIDHardConflict(c *check.C) {
343
+	testRequires(c, DaemonIsLinux)
344
+	dockerCmd(c, "create", "busybox")
345
+
346
+	imgID, err := inspectField("busybox:latest", "Id")
347
+	c.Assert(err, checker.IsNil)
348
+
349
+	_, _, err = dockerCmdWithError("rmi", imgID[:12])
350
+	c.Assert(err, checker.NotNil)
351
+
352
+	// check that tag was not removed
353
+	imgID2, err := inspectField("busybox:latest", "Id")
354
+	c.Assert(err, checker.IsNil)
355
+	c.Assert(imgID, checker.Equals, imgID2)
356
+}