Skip to content

Commit

Permalink
Merge pull request #10924 from tharun208/feat/remove_image_command
Browse files Browse the repository at this point in the history
add minikube image rm command
  • Loading branch information
medyagh authored Mar 29, 2021
2 parents 02bd568 + f022bab commit d1b560e
Show file tree
Hide file tree
Showing 12 changed files with 312 additions and 23 deletions.
27 changes: 24 additions & 3 deletions cmd/minikube/cmd/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,8 @@ import (

// imageCmd represents the image command
var imageCmd = &cobra.Command{
Use: "image",
Short: "Load a local image into minikube",
Long: "Load a local image into minikube",
Use: "image COMMAND",
Short: "Manage images",
}

var (
Expand Down Expand Up @@ -125,8 +124,30 @@ var loadImageCmd = &cobra.Command{
},
}

var removeImageCmd = &cobra.Command{
Use: "rm IMAGE [IMAGE...]",
Short: "Remove one or more images",
Example: `
$ minikube image rm image busybox
$ minikube image unload image busybox
`,
Args: cobra.MinimumNArgs(1),
Aliases: []string{"unload"},
Run: func(cmd *cobra.Command, args []string) {
profile, err := config.LoadProfile(viper.GetString(config.ProfileName))
if err != nil {
exit.Error(reason.Usage, "loading profile", err)
}
if err := machine.RemoveImages(args, profile); err != nil {
exit.Error(reason.GuestImageRemove, "Failed to remove image", err)
}
},
}

func init() {
imageCmd.AddCommand(loadImageCmd)
imageCmd.AddCommand(removeImageCmd)
loadImageCmd.Flags().BoolVar(&imgDaemon, "daemon", false, "Cache image from docker daemon")
loadImageCmd.Flags().BoolVar(&imgRemote, "remote", false, "Cache image from remote registry")
}
5 changes: 5 additions & 0 deletions pkg/generate/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string)
buf.WriteString(fmt.Sprintf("```shell\n%s\n```\n\n", cmd.UseLine()))
}

if len(cmd.Aliases) > 0 {
buf.WriteString("### Aliases\n\n")
buf.WriteString(fmt.Sprintf("%s\n\n", cmd.Aliases))
}

if len(cmd.Example) > 0 {
buf.WriteString("### Examples\n\n")
buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.Example))
Expand Down
5 changes: 5 additions & 0 deletions pkg/minikube/cruntime/containerd.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,11 @@ func (r *Containerd) LoadImage(path string) error {
return nil
}

// RemoveImage removes a image
func (r *Containerd) RemoveImage(name string) error {
return removeCRIImage(r.Runner, name)
}

// CGroupDriver returns cgroup driver ("cgroupfs" or "systemd")
func (r *Containerd) CGroupDriver() (string, error) {
info, err := getCRIInfo(r.Runner)
Expand Down
13 changes: 13 additions & 0 deletions pkg/minikube/cruntime/cri.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,19 @@ func killCRIContainers(cr CommandRunner, ids []string) error {
return nil
}

// removeCRIImage remove image using crictl
func removeCRIImage(cr CommandRunner, name string) error {
klog.Infof("Removing image: %s", name)

crictl := getCrictlPath(cr)
args := append([]string{crictl, "rmi"}, name)
c := exec.Command("sudo", args...)
if _, err := cr.RunCmd(c); err != nil {
return errors.Wrap(err, "crictl")
}
return nil
}

// stopCRIContainers stops containers using crictl
func stopCRIContainers(cr CommandRunner, ids []string) error {
if len(ids) == 0 {
Expand Down
5 changes: 5 additions & 0 deletions pkg/minikube/cruntime/crio.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,11 @@ func (r *CRIO) LoadImage(path string) error {
return nil
}

// RemoveImage removes a image
func (r *CRIO) RemoveImage(name string) error {
return removeCRIImage(r.Runner, name)
}

// CGroupDriver returns cgroup driver ("cgroupfs" or "systemd")
func (r *CRIO) CGroupDriver() (string, error) {
c := exec.Command("crio", "config")
Expand Down
3 changes: 3 additions & 0 deletions pkg/minikube/cruntime/cruntime.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ type Manager interface {
// ImageExists takes image name and image sha checks if an it exists
ImageExists(string, string) bool

// RemoveImage remove image based on name
RemoveImage(string) error

// ListContainers returns a list of managed by this container runtime
ListContainers(ListOptions) ([]string, error)
// KillContainers removes containers based on ID
Expand Down
57 changes: 49 additions & 8 deletions pkg/minikube/cruntime/cruntime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,19 @@ func TestImageExists(t *testing.T) {
sha string
want bool
}{
{"docker", "missing", "0000000000000000000000000000000000000000000000000000000000000000", false},
{"docker", "image", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", true},
{"crio", "missing", "0000000000000000000000000000000000000000000000000000000000000000", false},
{"crio", "image", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", true},
{"docker", "missing-image", "0000000000000000000000000000000000000000000000000000000000000000", false},
{"docker", "available-image", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", true},
{"crio", "missing-image", "0000000000000000000000000000000000000000000000000000000000000000", false},
{"crio", "available-image", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", true},
}
for _, tc := range tests {
runner := NewFakeRunner(t)
runner.images = map[string]string{
"available-image": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
}
t.Run(tc.runtime, func(t *testing.T) {
r, err := New(Config{Type: tc.runtime, Runner: NewFakeRunner(t)})

r, err := New(Config{Type: tc.runtime, Runner: runner})
if err != nil {
t.Fatalf("New(%s): %v", tc.runtime, err)
}
Expand Down Expand Up @@ -157,6 +162,7 @@ type FakeRunner struct {
cmds []string
services map[string]serviceState
containers map[string]string
images map[string]string
t *testing.T
}

Expand All @@ -167,6 +173,7 @@ func NewFakeRunner(t *testing.T) *FakeRunner {
cmds: []string{},
t: t,
containers: map[string]string{},
images: map[string]string{},
}
}

Expand Down Expand Up @@ -277,10 +284,23 @@ func (f *FakeRunner) dockerRm(args []string) (string, error) {

func (f *FakeRunner) dockerInspect(args []string) (string, error) {
if args[1] == "--format" && args[2] == "{{.Id}}" {
if args[3] == "missing" {
image, ok := f.images[args[3]]
if !ok {
return "", &exec.ExitError{Stderr: []byte("Error: No such object: missing")}
}
return "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", nil
return "sha256:" + image, nil
}
return "", nil
}

func (f *FakeRunner) dockerRmi(args []string) (string, error) {
// Skip "-f" argument
for _, id := range args[1:] {
f.t.Logf("fake docker: Removing id %q", id)
if f.images[id] == "" {
return "", fmt.Errorf("no such image")
}
delete(f.images, id)
}
return "", nil
}
Expand Down Expand Up @@ -308,6 +328,9 @@ func (f *FakeRunner) docker(args []string, _ bool) (string, error) {
return f.dockerInspect(args[1:])
}

case "rmi":
return f.dockerRmi(args)

case "inspect":
return f.dockerInspect(args)

Expand Down Expand Up @@ -417,7 +440,14 @@ func (f *FakeRunner) crictl(args []string, _ bool) (string, error) {
delete(f.containers, id)

}

case "rmi":
for _, id := range args[1:] {
f.t.Logf("fake crictl: Removing id %q", id)
if f.images[id] == "" {
return "", fmt.Errorf("no such image")
}
delete(f.images, id)
}
}
return "", nil
}
Expand Down Expand Up @@ -660,6 +690,9 @@ func TestContainerFunctions(t *testing.T) {
"fgh1": prefix + "coredns",
"xyz2": prefix + "storage",
}
runner.images = map[string]string{
"image1": "latest",
}
cr, err := New(Config{Type: tc.runtime, Runner: runner})
if err != nil {
t.Fatalf("New(%s): %v", tc.runtime, err)
Expand Down Expand Up @@ -709,6 +742,14 @@ func TestContainerFunctions(t *testing.T) {
if len(got) > 0 {
t.Errorf("ListContainers(apiserver) = %v, want 0 items", got)
}

// Remove a image
if err := cr.RemoveImage("image1"); err != nil {
t.Fatalf("RemoveImage: %v", err)
}
if len(runner.images) > 0 {
t.Errorf("RemoveImage = %v, want 0 items", len(runner.images))
}
})
}
}
13 changes: 13 additions & 0 deletions pkg/minikube/cruntime/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,19 @@ func (r *Docker) LoadImage(path string) error {
return nil
}

// RemoveImage removes a image
func (r *Docker) RemoveImage(name string) error {
klog.Infof("Removing image: %s", name)
if r.UseCRI {
return removeCRIImage(r.Runner, name)
}
c := exec.Command("docker", "rmi", name)
if _, err := r.Runner.RunCmd(c); err != nil {
return errors.Wrap(err, "remove image docker.")
}
return nil
}

// CGroupDriver returns cgroup driver ("cgroupfs" or "systemd")
func (r *Docker) CGroupDriver() (string, error) {
// Note: the server daemon has to be running, for this call to return successfully
Expand Down
80 changes: 80 additions & 0 deletions pkg/minikube/machine/cache_images.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,3 +292,83 @@ func transferAndLoadImage(cr command.Runner, k8s config.KubernetesConfig, src st
klog.Infof("Transferred and loaded %s from cache", src)
return nil
}

// removeImages removes images from the container run time
func removeImages(cruntime cruntime.Manager, images []string) error {
klog.Infof("RemovingImages start: %s", images)
start := time.Now()

defer func() {
klog.Infof("RemovingImages completed in %s", time.Since(start))
}()

var g errgroup.Group

for _, image := range images {
image := image
g.Go(func() error {
return cruntime.RemoveImage(image)
})
}
if err := g.Wait(); err != nil {
return errors.Wrap(err, "error removing images")
}
klog.Infoln("Successfully removed images")
return nil
}

func RemoveImages(images []string, profile *config.Profile) error {
api, err := NewAPIClient()
if err != nil {
return errors.Wrap(err, "error creating api client")
}
defer api.Close()

succeeded := []string{}
failed := []string{}

pName := profile.Name

c, err := config.Load(pName)
if err != nil {
klog.Errorf("Failed to load profile %q: %v", pName, err)
return errors.Wrapf(err, "error loading config for profile :%v", pName)
}

for _, n := range c.Nodes {
m := config.MachineName(*c, n)

status, err := Status(api, m)
if err != nil {
klog.Warningf("error getting status for %s: %v", m, err)
continue
}

if status == state.Running.String() {
h, err := api.Load(m)
if err != nil {
klog.Warningf("Failed to load machine %q: %v", m, err)
continue
}
runner, err := CommandRunner(h)
if err != nil {
return err
}
cruntime, err := cruntime.New(cruntime.Config{Type: c.KubernetesConfig.ContainerRuntime, Runner: runner})
if err != nil {
return errors.Wrap(err, "error creating container runtime")
}
err = removeImages(cruntime, images)
if err != nil {
failed = append(failed, m)
klog.Warningf("Failed to remove images for profile %s %v", pName, err.Error())
continue
}
succeeded = append(succeeded, m)
}
}

klog.Infof("succeeded removing from: %s", strings.Join(succeeded, " "))
klog.Infof("failed removing from: %s", strings.Join(failed, " "))
return nil
}
1 change: 1 addition & 0 deletions pkg/minikube/reason/reason.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ var (
GuestCpConfig = Kind{ID: "GUEST_CP_CONFIG", ExitCode: ExGuestConfig}
GuestDeletion = Kind{ID: "GUEST_DELETION", ExitCode: ExGuestError}
GuestImageLoad = Kind{ID: "GUEST_IMAGE_LOAD", ExitCode: ExGuestError}
GuestImageRemove = Kind{ID: "GUEST_IMAGE_REMOVE", ExitCode: ExGuestError}
GuestLoadHost = Kind{ID: "GUEST_LOAD_HOST", ExitCode: ExGuestError}
GuestMount = Kind{ID: "GUEST_MOUNT", ExitCode: ExGuestError}
GuestMountConflict = Kind{ID: "GUEST_MOUNT_CONFLICT", ExitCode: ExGuestConflict}
Expand Down
Loading

0 comments on commit d1b560e

Please sign in to comment.