summaryrefslogtreecommitdiff
path: root/cmd/podman/rmi.go
blob: 58a78a924ed040c5087613f5582def9025393ca9 (plain)
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
package main

import (
	"fmt"
	"os"

	"github.com/containers/libpod/libpod/adapter"
	"github.com/containers/storage"
	"github.com/pkg/errors"
	"github.com/urfave/cli"
)

var (
	rmiDescription = "Removes one or more locally stored images."
	rmiFlags       = []cli.Flag{
		cli.BoolFlag{
			Name:  "all, a",
			Usage: "remove all images",
		},
		cli.BoolFlag{
			Name:  "force, f",
			Usage: "force removal of the image",
		},
	}
	rmiCommand = cli.Command{
		Name:                   "rmi",
		Usage:                  "Removes one or more images from local storage",
		Description:            rmiDescription,
		Action:                 rmiCmd,
		ArgsUsage:              "IMAGE-NAME-OR-ID [...]",
		Flags:                  sortFlags(rmiFlags),
		UseShortOptionHandling: true,
		OnUsageError:           usageErrorHandler,
	}
	rmImageCommand = cli.Command{
		Name:                   "rm",
		Usage:                  "removes one or more images from local storage",
		Description:            rmiDescription,
		Action:                 rmiCmd,
		ArgsUsage:              "IMAGE-NAME-OR-ID [...]",
		Flags:                  rmiFlags,
		UseShortOptionHandling: true,
		OnUsageError:           usageErrorHandler,
	}
)

func rmiCmd(c *cli.Context) error {
	var (
		lastError error
		deleted   bool
		deleteErr error
		msg       string
	)

	ctx := getContext()
	if err := validateFlags(c, rmiFlags); err != nil {
		return err
	}
	removeAll := c.Bool("all")
	runtime, err := adapter.GetRuntime(c)
	if err != nil {
		return errors.Wrapf(err, "could not get runtime")
	}
	defer runtime.Runtime.Shutdown(false)

	args := c.Args()
	if len(args) == 0 && !removeAll {
		return errors.Errorf("image name or ID must be specified")
	}
	if len(args) > 0 && removeAll {
		return errors.Errorf("when using the --all switch, you may not pass any images names or IDs")
	}

	images := args[:]

	removeImage := func(img *adapter.ContainerImage) {
		deleted = true
		msg, deleteErr = runtime.RemoveImage(ctx, img, c.Bool("force"))
		if deleteErr != nil {
			if errors.Cause(deleteErr) == storage.ErrImageUsedByContainer {
				fmt.Printf("A container associated with containers/storage, i.e. via Buildah, CRI-O, etc., may be associated with this image: %-12.12s\n", img.ID())
			}
			if lastError != nil {
				fmt.Fprintln(os.Stderr, lastError)
			}
			lastError = deleteErr
		} else {
			fmt.Println(msg)
		}
	}

	if removeAll {
		var imagesToDelete []*adapter.ContainerImage
		imagesToDelete, err = runtime.GetImages()
		if err != nil {
			return errors.Wrapf(err, "unable to query local images")
		}
		lastNumberofImages := 0
		for len(imagesToDelete) > 0 {
			if lastNumberofImages == len(imagesToDelete) {
				return errors.New("unable to delete all images; re-run the rmi command again.")
			}
			for _, i := range imagesToDelete {
				isParent, err := i.IsParent()
				if err != nil {
					return err
				}
				if isParent {
					continue
				}
				removeImage(i)
			}
			lastNumberofImages = len(imagesToDelete)
			imagesToDelete, err = runtime.GetImages()
			if err != nil {
				return err
			}
			// If no images are left to delete or there is just one image left and it cannot be deleted,
			// lets break out and display the error
			if len(imagesToDelete) == 0 || (lastNumberofImages == 1 && lastError != nil) {
				break
			}
		}
	} else {
		// Create image.image objects for deletion from user input.
		// Note that we have to query the storage one-by-one to
		// always get the latest state for each image.  Otherwise, we
		// run inconsistency issues, for instance, with repoTags.
		// See https://github.com/containers/libpod/issues/930 as
		// an exemplary inconsistency issue.
		for _, i := range images {
			newImage, err := runtime.NewImageFromLocal(i)
			if err != nil {
				fmt.Fprintln(os.Stderr, err)
				continue
			}
			removeImage(newImage)
		}
	}

	// If the user calls remove all and there are none, it should not be a
	// non-zero exit
	if !deleted && removeAll {
		return nil
	}
	// the user tries to remove images that do not exist, that should be a
	// non-zero exit
	if !deleted {
		return errors.Errorf("no valid images to delete")
	}

	return lastError
}