1package images // import "github.com/docker/docker/daemon/images"
2
3import (
4	"fmt"
5	"strings"
6	"time"
7
8	"github.com/docker/distribution/reference"
9	"github.com/docker/docker/api/types"
10	"github.com/docker/docker/container"
11	"github.com/docker/docker/errdefs"
12	"github.com/docker/docker/image"
13	"github.com/docker/docker/pkg/stringid"
14	"github.com/docker/docker/pkg/system"
15	"github.com/pkg/errors"
16)
17
18type conflictType int
19
20const (
21	conflictDependentChild conflictType = 1 << iota
22	conflictRunningContainer
23	conflictActiveReference
24	conflictStoppedContainer
25	conflictHard = conflictDependentChild | conflictRunningContainer
26	conflictSoft = conflictActiveReference | conflictStoppedContainer
27)
28
29// ImageDelete deletes the image referenced by the given imageRef from this
30// daemon. The given imageRef can be an image ID, ID prefix, or a repository
31// reference (with an optional tag or digest, defaulting to the tag name
32// "latest"). There is differing behavior depending on whether the given
33// imageRef is a repository reference or not.
34//
35// If the given imageRef is a repository reference then that repository
36// reference will be removed. However, if there exists any containers which
37// were created using the same image reference then the repository reference
38// cannot be removed unless either there are other repository references to the
39// same image or force is true. Following removal of the repository reference,
40// the referenced image itself will attempt to be deleted as described below
41// but quietly, meaning any image delete conflicts will cause the image to not
42// be deleted and the conflict will not be reported.
43//
44// There may be conflicts preventing deletion of an image and these conflicts
45// are divided into two categories grouped by their severity:
46//
47// Hard Conflict:
48// 	- a pull or build using the image.
49// 	- any descendant image.
50// 	- any running container using the image.
51//
52// Soft Conflict:
53// 	- any stopped container using the image.
54// 	- any repository tag or digest references to the image.
55//
56// The image cannot be removed if there are any hard conflicts and can be
57// removed if there are soft conflicts only if force is true.
58//
59// If prune is true, ancestor images will each attempt to be deleted quietly,
60// meaning any delete conflicts will cause the image to not be deleted and the
61// conflict will not be reported.
62//
63func (i *ImageService) ImageDelete(imageRef string, force, prune bool) ([]types.ImageDeleteResponseItem, error) {
64	start := time.Now()
65	records := []types.ImageDeleteResponseItem{}
66
67	img, err := i.GetImage(imageRef)
68	if err != nil {
69		return nil, err
70	}
71	if !system.IsOSSupported(img.OperatingSystem()) {
72		return nil, errors.Errorf("unable to delete image: %q", system.ErrNotSupportedOperatingSystem)
73	}
74
75	imgID := img.ID()
76	repoRefs := i.referenceStore.References(imgID.Digest())
77
78	using := func(c *container.Container) bool {
79		return c.ImageID == imgID
80	}
81
82	var removedRepositoryRef bool
83	if !isImageIDPrefix(imgID.String(), imageRef) {
84		// A repository reference was given and should be removed
85		// first. We can only remove this reference if either force is
86		// true, there are multiple repository references to this
87		// image, or there are no containers using the given reference.
88		if !force && isSingleReference(repoRefs) {
89			if container := i.containers.First(using); container != nil {
90				// If we removed the repository reference then
91				// this image would remain "dangling" and since
92				// we really want to avoid that the client must
93				// explicitly force its removal.
94				err := errors.Errorf("conflict: unable to remove repository reference %q (must force) - container %s is using its referenced image %s", imageRef, stringid.TruncateID(container.ID), stringid.TruncateID(imgID.String()))
95				return nil, errdefs.Conflict(err)
96			}
97		}
98
99		parsedRef, err := reference.ParseNormalizedNamed(imageRef)
100		if err != nil {
101			return nil, err
102		}
103
104		parsedRef, err = i.removeImageRef(parsedRef)
105		if err != nil {
106			return nil, err
107		}
108
109		untaggedRecord := types.ImageDeleteResponseItem{Untagged: reference.FamiliarString(parsedRef)}
110
111		i.LogImageEvent(imgID.String(), imgID.String(), "untag")
112		records = append(records, untaggedRecord)
113
114		repoRefs = i.referenceStore.References(imgID.Digest())
115
116		// If a tag reference was removed and the only remaining
117		// references to the same repository are digest references,
118		// then clean up those digest references.
119		if _, isCanonical := parsedRef.(reference.Canonical); !isCanonical {
120			foundRepoTagRef := false
121			for _, repoRef := range repoRefs {
122				if _, repoRefIsCanonical := repoRef.(reference.Canonical); !repoRefIsCanonical && parsedRef.Name() == repoRef.Name() {
123					foundRepoTagRef = true
124					break
125				}
126			}
127			if !foundRepoTagRef {
128				// Remove canonical references from same repository
129				var remainingRefs []reference.Named
130				for _, repoRef := range repoRefs {
131					if _, repoRefIsCanonical := repoRef.(reference.Canonical); repoRefIsCanonical && parsedRef.Name() == repoRef.Name() {
132						if _, err := i.removeImageRef(repoRef); err != nil {
133							return records, err
134						}
135
136						untaggedRecord := types.ImageDeleteResponseItem{Untagged: reference.FamiliarString(repoRef)}
137						records = append(records, untaggedRecord)
138					} else {
139						remainingRefs = append(remainingRefs, repoRef)
140
141					}
142				}
143				repoRefs = remainingRefs
144			}
145		}
146
147		// If it has remaining references then the untag finished the remove
148		if len(repoRefs) > 0 {
149			return records, nil
150		}
151
152		removedRepositoryRef = true
153	} else {
154		// If an ID reference was given AND there is at most one tag
155		// reference to the image AND all references are within one
156		// repository, then remove all references.
157		if isSingleReference(repoRefs) {
158			c := conflictHard
159			if !force {
160				c |= conflictSoft &^ conflictActiveReference
161			}
162			if conflict := i.checkImageDeleteConflict(imgID, c); conflict != nil {
163				return nil, conflict
164			}
165
166			for _, repoRef := range repoRefs {
167				parsedRef, err := i.removeImageRef(repoRef)
168				if err != nil {
169					return nil, err
170				}
171
172				untaggedRecord := types.ImageDeleteResponseItem{Untagged: reference.FamiliarString(parsedRef)}
173
174				i.LogImageEvent(imgID.String(), imgID.String(), "untag")
175				records = append(records, untaggedRecord)
176			}
177		}
178	}
179
180	if err := i.imageDeleteHelper(imgID, &records, force, prune, removedRepositoryRef); err != nil {
181		return nil, err
182	}
183
184	imageActions.WithValues("delete").UpdateSince(start)
185
186	return records, nil
187}
188
189// isSingleReference returns true when all references are from one repository
190// and there is at most one tag. Returns false for empty input.
191func isSingleReference(repoRefs []reference.Named) bool {
192	if len(repoRefs) <= 1 {
193		return len(repoRefs) == 1
194	}
195	var singleRef reference.Named
196	canonicalRefs := map[string]struct{}{}
197	for _, repoRef := range repoRefs {
198		if _, isCanonical := repoRef.(reference.Canonical); isCanonical {
199			canonicalRefs[repoRef.Name()] = struct{}{}
200		} else if singleRef == nil {
201			singleRef = repoRef
202		} else {
203			return false
204		}
205	}
206	if singleRef == nil {
207		// Just use first canonical ref
208		singleRef = repoRefs[0]
209	}
210	_, ok := canonicalRefs[singleRef.Name()]
211	return len(canonicalRefs) == 1 && ok
212}
213
214// isImageIDPrefix returns whether the given possiblePrefix is a prefix of the
215// given imageID.
216func isImageIDPrefix(imageID, possiblePrefix string) bool {
217	if strings.HasPrefix(imageID, possiblePrefix) {
218		return true
219	}
220
221	if i := strings.IndexRune(imageID, ':'); i >= 0 {
222		return strings.HasPrefix(imageID[i+1:], possiblePrefix)
223	}
224
225	return false
226}
227
228// removeImageRef attempts to parse and remove the given image reference from
229// this daemon's store of repository tag/digest references. The given
230// repositoryRef must not be an image ID but a repository name followed by an
231// optional tag or digest reference. If tag or digest is omitted, the default
232// tag is used. Returns the resolved image reference and an error.
233func (i *ImageService) removeImageRef(ref reference.Named) (reference.Named, error) {
234	ref = reference.TagNameOnly(ref)
235
236	// Ignore the boolean value returned, as far as we're concerned, this
237	// is an idempotent operation and it's okay if the reference didn't
238	// exist in the first place.
239	_, err := i.referenceStore.Delete(ref)
240
241	return ref, err
242}
243
244// removeAllReferencesToImageID attempts to remove every reference to the given
245// imgID from this daemon's store of repository tag/digest references. Returns
246// on the first encountered error. Removed references are logged to this
247// daemon's event service. An "Untagged" types.ImageDeleteResponseItem is added to the
248// given list of records.
249func (i *ImageService) removeAllReferencesToImageID(imgID image.ID, records *[]types.ImageDeleteResponseItem) error {
250	imageRefs := i.referenceStore.References(imgID.Digest())
251
252	for _, imageRef := range imageRefs {
253		parsedRef, err := i.removeImageRef(imageRef)
254		if err != nil {
255			return err
256		}
257
258		untaggedRecord := types.ImageDeleteResponseItem{Untagged: reference.FamiliarString(parsedRef)}
259
260		i.LogImageEvent(imgID.String(), imgID.String(), "untag")
261		*records = append(*records, untaggedRecord)
262	}
263
264	return nil
265}
266
267// ImageDeleteConflict holds a soft or hard conflict and an associated error.
268// Implements the error interface.
269type imageDeleteConflict struct {
270	hard    bool
271	used    bool
272	imgID   image.ID
273	message string
274}
275
276func (idc *imageDeleteConflict) Error() string {
277	var forceMsg string
278	if idc.hard {
279		forceMsg = "cannot be forced"
280	} else {
281		forceMsg = "must be forced"
282	}
283
284	return fmt.Sprintf("conflict: unable to delete %s (%s) - %s", stringid.TruncateID(idc.imgID.String()), forceMsg, idc.message)
285}
286
287func (idc *imageDeleteConflict) Conflict() {}
288
289// imageDeleteHelper attempts to delete the given image from this daemon. If
290// the image has any hard delete conflicts (child images or running containers
291// using the image) then it cannot be deleted. If the image has any soft delete
292// conflicts (any tags/digests referencing the image or any stopped container
293// using the image) then it can only be deleted if force is true. If the delete
294// succeeds and prune is true, the parent images are also deleted if they do
295// not have any soft or hard delete conflicts themselves. Any deleted images
296// and untagged references are appended to the given records. If any error or
297// conflict is encountered, it will be returned immediately without deleting
298// the image. If quiet is true, any encountered conflicts will be ignored and
299// the function will return nil immediately without deleting the image.
300func (i *ImageService) imageDeleteHelper(imgID image.ID, records *[]types.ImageDeleteResponseItem, force, prune, quiet bool) error {
301	// First, determine if this image has any conflicts. Ignore soft conflicts
302	// if force is true.
303	c := conflictHard
304	if !force {
305		c |= conflictSoft
306	}
307	if conflict := i.checkImageDeleteConflict(imgID, c); conflict != nil {
308		if quiet && (!i.imageIsDangling(imgID) || conflict.used) {
309			// Ignore conflicts UNLESS the image is "dangling" or not being used in
310			// which case we want the user to know.
311			return nil
312		}
313
314		// There was a conflict and it's either a hard conflict OR we are not
315		// forcing deletion on soft conflicts.
316		return conflict
317	}
318
319	parent, err := i.imageStore.GetParent(imgID)
320	if err != nil {
321		// There may be no parent
322		parent = ""
323	}
324
325	// Delete all repository tag/digest references to this image.
326	if err := i.removeAllReferencesToImageID(imgID, records); err != nil {
327		return err
328	}
329
330	removedLayers, err := i.imageStore.Delete(imgID)
331	if err != nil {
332		return err
333	}
334
335	i.LogImageEvent(imgID.String(), imgID.String(), "delete")
336	*records = append(*records, types.ImageDeleteResponseItem{Deleted: imgID.String()})
337	for _, removedLayer := range removedLayers {
338		*records = append(*records, types.ImageDeleteResponseItem{Deleted: removedLayer.ChainID.String()})
339	}
340
341	if !prune || parent == "" {
342		return nil
343	}
344
345	// We need to prune the parent image. This means delete it if there are
346	// no tags/digests referencing it and there are no containers using it (
347	// either running or stopped).
348	// Do not force prunings, but do so quietly (stopping on any encountered
349	// conflicts).
350	return i.imageDeleteHelper(parent, records, false, true, true)
351}
352
353// checkImageDeleteConflict determines whether there are any conflicts
354// preventing deletion of the given image from this daemon. A hard conflict is
355// any image which has the given image as a parent or any running container
356// using the image. A soft conflict is any tags/digest referencing the given
357// image or any stopped container using the image. If ignoreSoftConflicts is
358// true, this function will not check for soft conflict conditions.
359func (i *ImageService) checkImageDeleteConflict(imgID image.ID, mask conflictType) *imageDeleteConflict {
360	// Check if the image has any descendant images.
361	if mask&conflictDependentChild != 0 && len(i.imageStore.Children(imgID)) > 0 {
362		return &imageDeleteConflict{
363			hard:    true,
364			imgID:   imgID,
365			message: "image has dependent child images",
366		}
367	}
368
369	if mask&conflictRunningContainer != 0 {
370		// Check if any running container is using the image.
371		running := func(c *container.Container) bool {
372			return c.IsRunning() && c.ImageID == imgID
373		}
374		if container := i.containers.First(running); container != nil {
375			return &imageDeleteConflict{
376				imgID:   imgID,
377				hard:    true,
378				used:    true,
379				message: fmt.Sprintf("image is being used by running container %s", stringid.TruncateID(container.ID)),
380			}
381		}
382	}
383
384	// Check if any repository tags/digest reference this image.
385	if mask&conflictActiveReference != 0 && len(i.referenceStore.References(imgID.Digest())) > 0 {
386		return &imageDeleteConflict{
387			imgID:   imgID,
388			message: "image is referenced in multiple repositories",
389		}
390	}
391
392	if mask&conflictStoppedContainer != 0 {
393		// Check if any stopped containers reference this image.
394		stopped := func(c *container.Container) bool {
395			return !c.IsRunning() && c.ImageID == imgID
396		}
397		if container := i.containers.First(stopped); container != nil {
398			return &imageDeleteConflict{
399				imgID:   imgID,
400				used:    true,
401				message: fmt.Sprintf("image is being used by stopped container %s", stringid.TruncateID(container.ID)),
402			}
403		}
404	}
405
406	return nil
407}
408
409// imageIsDangling returns whether the given image is "dangling" which means
410// that there are no repository references to the given image and it has no
411// child images.
412func (i *ImageService) imageIsDangling(imgID image.ID) bool {
413	return !(len(i.referenceStore.References(imgID.Digest())) > 0 || len(i.imageStore.Children(imgID)) > 0)
414}
415