Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -246,6 +246,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