From 35d8d4aa52ddd946bf6ab1d2df22aed3dd5cf3e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20F=20Bj=C3=B6rklund?= Date: Tue, 6 Apr 2021 22:40:06 +0200 Subject: [PATCH] Add ListImages function and image list command --- cmd/minikube/cmd/image.go | 19 ++++++++++ pkg/minikube/cruntime/containerd.go | 18 +++++++++ pkg/minikube/cruntime/crio.go | 10 +++++ pkg/minikube/cruntime/cruntime.go | 6 +++ pkg/minikube/cruntime/docker.go | 42 +++++++++++++++++++++ pkg/minikube/machine/cache_images.go | 52 ++++++++++++++++++++++++++ pkg/minikube/reason/reason.go | 1 + site/content/en/docs/commands/image.md | 46 +++++++++++++++++++++++ 8 files changed, 194 insertions(+) diff --git a/cmd/minikube/cmd/image.go b/cmd/minikube/cmd/image.go index a379d61511..d9baa0f5b2 100644 --- a/cmd/minikube/cmd/image.go +++ b/cmd/minikube/cmd/image.go @@ -145,9 +145,28 @@ $ minikube image unload image busybox }, } +var listImageCmd = &cobra.Command{ + Use: "list", + Short: "List images", + Example: ` +$ minikube image list +`, + Aliases: []string{"ls"}, + 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.ListImages(profile); err != nil { + exit.Error(reason.GuestImageList, "Failed to list images", 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") + imageCmd.AddCommand(listImageCmd) } diff --git a/pkg/minikube/cruntime/containerd.go b/pkg/minikube/cruntime/containerd.go index 9f2bf02df5..c811f40f3f 100644 --- a/pkg/minikube/cruntime/containerd.go +++ b/pkg/minikube/cruntime/containerd.go @@ -239,6 +239,24 @@ func (r *Containerd) ImageExists(name string, sha string) bool { return true } +// ListImages lists images managed by this container runtime +func (r *Containerd) ListImages(ListImagesOptions) ([]string, error) { + c := exec.Command("sudo", "ctr", "-n=k8s.io", "images", "list", "--quiet") + rr, err := r.Runner.RunCmd(c) + if err != nil { + return nil, errors.Wrapf(err, "ctr images list") + } + all := strings.Split(rr.Stdout.String(), "\n") + imgs := []string{} + for _, img := range all { + if img == "" || strings.Contains(img, "sha256:") { + continue + } + imgs = append(imgs, img) + } + return imgs, nil +} + // LoadImage loads an image into this runtime func (r *Containerd) LoadImage(path string) error { klog.Infof("Loading image: %s", path) diff --git a/pkg/minikube/cruntime/crio.go b/pkg/minikube/cruntime/crio.go index a8568f8e43..4c1eee2c27 100644 --- a/pkg/minikube/cruntime/crio.go +++ b/pkg/minikube/cruntime/crio.go @@ -167,6 +167,16 @@ func (r *CRIO) ImageExists(name string, sha string) bool { return true } +// ListImages returns a list of images managed by this container runtime +func (r *CRIO) ListImages(ListImagesOptions) ([]string, error) { + c := exec.Command("sudo", "podman", "images", "--format", "{{.Repository}}:{{.Tag}}") + rr, err := r.Runner.RunCmd(c) + if err != nil { + return nil, errors.Wrapf(err, "podman images") + } + return strings.Split(strings.TrimSpace(rr.Stdout.String()), "\n"), nil +} + // LoadImage loads an image into this runtime func (r *CRIO) LoadImage(path string) error { klog.Infof("Loading image: %s", path) diff --git a/pkg/minikube/cruntime/cruntime.go b/pkg/minikube/cruntime/cruntime.go index 4f81a0f932..1cd352fe61 100644 --- a/pkg/minikube/cruntime/cruntime.go +++ b/pkg/minikube/cruntime/cruntime.go @@ -98,6 +98,8 @@ type Manager interface { // ImageExists takes image name and image sha checks if an it exists ImageExists(string, string) bool + // ListImages returns a list of images managed by this container runtime + ListImages(ListImagesOptions) ([]string, error) // RemoveImage remove image based on name RemoveImage(string) error @@ -148,6 +150,10 @@ type ListContainersOptions struct { Namespaces []string } +// ListImageOptions are the options to use for listing images +type ListImagesOptions struct { +} + // ErrContainerRuntimeNotRunning is thrown when container runtime is not running var ErrContainerRuntimeNotRunning = errors.New("container runtime is not running") diff --git a/pkg/minikube/cruntime/docker.go b/pkg/minikube/cruntime/docker.go index ae02c01c38..50b1f76289 100644 --- a/pkg/minikube/cruntime/docker.go +++ b/pkg/minikube/cruntime/docker.go @@ -162,6 +162,25 @@ func (r *Docker) ImageExists(name string, sha string) bool { return true } +// ListImages returns a list of images managed by this container runtime +func (r *Docker) ListImages(ListImagesOptions) ([]string, error) { + c := exec.Command("docker", "images", "--format", "{{.Repository}}:{{.Tag}}") + rr, err := r.Runner.RunCmd(c) + if err != nil { + return nil, errors.Wrapf(err, "docker images") + } + short := strings.Split(rr.Stdout.String(), "\n") + imgs := []string{} + for _, img := range short { + if img == "" { + continue + } + img = addDockerIO(img) + imgs = append(imgs, img) + } + return imgs, nil +} + // LoadImage loads an image into this runtime func (r *Docker) LoadImage(path string) error { klog.Infof("Loading image: %s", path) @@ -446,6 +465,29 @@ func dockerImagesPreloaded(runner command.Runner, images []string) bool { return true } +// Add docker.io prefix +func addDockerIO(name string) string { + var reg, usr, img string + p := strings.SplitN(name, "/", 2) + if len(p) > 1 && strings.Contains(p[0], ".") { + reg = p[0] + img = p[1] + } else { + reg = "docker.io" + img = name + p = strings.SplitN(img, "/", 2) + if len(p) > 1 { + usr = p[0] + img = p[1] + } else { + usr = "library" + img = name + } + return reg + "/" + usr + "/" + img + } + return reg + "/" + img +} + // Remove docker.io prefix since it won't be included in images names // when we call 'docker images' func trimDockerIO(name string) string { diff --git a/pkg/minikube/machine/cache_images.go b/pkg/minikube/machine/cache_images.go index bfdb975cf2..caea9a212c 100644 --- a/pkg/minikube/machine/cache_images.go +++ b/pkg/minikube/machine/cache_images.go @@ -21,6 +21,7 @@ import ( "os" "path" "path/filepath" + "sort" "strings" "sync" "time" @@ -372,3 +373,54 @@ func RemoveImages(images []string, profile *config.Profile) error { klog.Infof("failed removing from: %s", strings.Join(failed, " ")) return nil } + +func ListImages(profile *config.Profile) error { + api, err := NewAPIClient() + if err != nil { + return errors.Wrap(err, "error creating api client") + } + defer api.Close() + + 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 + } + cr, err := cruntime.New(cruntime.Config{Type: c.KubernetesConfig.ContainerRuntime, Runner: runner}) + if err != nil { + return errors.Wrap(err, "error creating container runtime") + } + list, err := cr.ListImages(cruntime.ListImagesOptions{}) + if err != nil { + klog.Warningf("Failed to list images for profile %s %v", pName, err.Error()) + continue + } + sort.Sort(sort.Reverse(sort.StringSlice(list))) + fmt.Printf(strings.Join(list, "\n") + "\n") + } + } + + return nil +} diff --git a/pkg/minikube/reason/reason.go b/pkg/minikube/reason/reason.go index 40dc02acb7..46f559fddd 100644 --- a/pkg/minikube/reason/reason.go +++ b/pkg/minikube/reason/reason.go @@ -246,6 +246,7 @@ var ( GuestCert = Kind{ID: "GUEST_CERT", ExitCode: ExGuestError} GuestCpConfig = Kind{ID: "GUEST_CP_CONFIG", ExitCode: ExGuestConfig} GuestDeletion = Kind{ID: "GUEST_DELETION", ExitCode: ExGuestError} + GuestImageList = Kind{ID: "GUEST_IMAGE_LIST", 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} diff --git a/site/content/en/docs/commands/image.md b/site/content/en/docs/commands/image.md index d4a019adfe..ac0d42bdb4 100644 --- a/site/content/en/docs/commands/image.md +++ b/site/content/en/docs/commands/image.md @@ -70,6 +70,52 @@ minikube image help [command] [flags] --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging ``` +## minikube image list + +List images + +### Synopsis + +List images + +```shell +minikube image list [flags] +``` + +### Aliases + +[ls] + +### Examples + +``` + +$ minikube image list + +``` + +### Options inherited from parent commands + +``` + --add_dir_header If true, adds the file directory to the header of the log messages + --alsologtostderr log to standard error as well as files + -b, --bootstrapper string The name of the cluster bootstrapper that will set up the Kubernetes cluster. (default "kubeadm") + -h, --help + --log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0) + --log_dir string If non-empty, write log files in this directory + --log_file string If non-empty, use this log file + --log_file_max_size uint Defines the maximum size a log file can grow to. Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800) + --logtostderr log to standard error instead of files + --one_output If true, only write logs to their native severity level (vs also writing to each lower severity level) + -p, --profile string The name of the minikube VM being used. This can be set to allow having multiple instances of minikube independently. (default "minikube") + --skip_headers If true, avoid header prefixes in the log messages + --skip_log_headers If true, avoid headers when opening log files + --stderrthreshold severity logs at or above this threshold go to stderr (default 2) + --user string Specifies the user executing the operation. Useful for auditing operations executed by 3rd party tools. Defaults to the operating system username. + -v, --v Level number for the log level verbosity + --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging +``` + ## minikube image load Load a image into minikube