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

import (
	"fmt"
	"os"

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

var (
	rmiCommand     cliconfig.RmiValues
	rmiDescription = "Removes one or more locally stored images."
	_rmiCommand    = &cobra.Command{
		Use:   "rmi",
		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
			return rmiCmd(&rmiCommand)
		},
		Example: `podman rmi imageID
  podman rmi --force alpine
  podman rmi c4dfb1609ee2 93fd78260bd1 c0ed59d05ff7`,
	}
)

func init() {
	rmiCommand.Command = _rmiCommand
	rmiCommand.SetUsageTemplate(UsageTemplate())
	flags := rmiCommand.Flags()
	flags.BoolVarP(&rmiCommand.All, "all", "a", false, "Remove all images")
	flags.BoolVarP(&rmiCommand.Force, "force", "f", false, "Force Removal of the image")
}

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

	ctx := getContext()
	removeAll := c.All
	runtime, err := adapter.GetRuntime(&c.PodmanCommand)
	if err != nil {
		return errors.Wrapf(err, "could not get runtime")
	}
	defer runtime.Shutdown(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) {
		deleted = true
		msg, deleteErr = runtime.RemoveImage(ctx, img, c.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
}