aboutsummaryrefslogtreecommitdiff
path: root/cmd/podman/rmi.go
blob: caaa8984dd5c67cc8898bbaf6d1e9c32fafa879a (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
154
155
156
package main

import (
	"fmt"
	"os"

	"github.com/containers/libpod/cmd/podman/cliconfig"
	"github.com/containers/libpod/pkg/adapter"
	"github.com/containers/storage"
	"github.com/pkg/errors"
	"github.com/spf13/cobra"
)

var (
	rmiCommand     cliconfig.RmiValues
	rmiDescription = "Removes one or more previously pulled or locally created images."
	_rmiCommand    = cobra.Command{
		Use:   "rmi [flags] IMAGE [IMAGE...]",
		Short: "Removes one or more images from local storage",
		Long:  rmiDescription,
		RunE: func(cmd *cobra.Command, args []string) error {
			rmiCommand.InputArgs = args
			rmiCommand.GlobalFlags = MainGlobalOpts
			rmiCommand.Remote = remoteclient
			return rmiCmd(&rmiCommand)
		},
		Example: `podman rmi imageID
  podman rmi --force alpine
  podman rmi c4dfb1609ee2 93fd78260bd1 c0ed59d05ff7`,
	}
)

func rmiInit(command *cliconfig.RmiValues) {
	command.SetHelpTemplate(HelpTemplate())
	command.SetUsageTemplate(UsageTemplate())
	flags := command.Flags()
	flags.BoolVarP(&command.All, "all", "a", false, "Remove all images")
	flags.BoolVarP(&command.Force, "force", "f", false, "Force Removal of the image")
}

func init() {
	rmiCommand.Command = &_rmiCommand
	rmiInit(&rmiCommand)
}

func rmiCmd(c *cliconfig.RmiValues) error {
	var (
		lastError  error
		failureCnt int
	)

	ctx := getContext()
	removeAll := c.All
	runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand)
	if err != nil {
		return errors.Wrapf(err, "could not get runtime")
	}
	defer runtime.DeferredShutdown(false)

	args := c.InputArgs
	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) {
		response, err := runtime.RemoveImage(ctx, img, c.Force)
		if err != nil {
			if errors.Cause(err) == 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 !adapter.IsImageNotFound(err) {
				exitCode = 2
				failureCnt++
			}
			if lastError != nil {
				fmt.Fprintln(os.Stderr, lastError)
			}
			lastError = err
			return
		}
		// Iterate if any images tags were deleted
		for _, i := range response.Untagged {
			fmt.Printf("Untagged: %s\n", i)
		}
		// Make sure an image was deleted (and not just untagged); else print it
		if len(response.Deleted) > 0 {
			fmt.Printf("Deleted: %s\n", response.Deleted)
		}
	}

	if removeAll {
		var imagesToDelete []*adapter.ContainerImage
		imagesToDelete, err = runtime.GetRWImages()
		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(ctx)
				if err != nil {
					return err
				}
				if isParent {
					continue
				}
				removeImage(i)
			}
			lastNumberofImages = len(imagesToDelete)
			imagesToDelete, err = runtime.GetRWImages()
			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 {
				if lastError != nil {
					if !adapter.IsImageNotFound(lastError) {
						failureCnt++
					}
					fmt.Fprintln(os.Stderr, lastError)
				}
				lastError = err
				continue
			}
			removeImage(newImage)
		}
	}

	if adapter.IsImageNotFound(lastError) && failureCnt == 0 {
		exitCode = 1
	}

	return lastError
}