Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add format flag to the image ls command #12996

Merged
merged 4 commits into from
Dec 23, 2021
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
4 changes: 3 additions & 1 deletion cmd/minikube/cmd/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ var (
dockerFile string
buildEnv []string
buildOpt []string
format string
)

func saveFile(r io.Reader) (string, error) {
Expand Down Expand Up @@ -331,7 +332,7 @@ $ minikube image ls
exit.Error(reason.Usage, "loading profile", err)
}

if err := machine.ListImages(profile); err != nil {
if err := machine.ListImages(profile, format); err != nil {
exit.Error(reason.GuestImageList, "Failed to list images", err)
}
},
Expand Down Expand Up @@ -396,6 +397,7 @@ func init() {
saveImageCmd.Flags().BoolVar(&imgDaemon, "daemon", false, "Cache image to docker daemon")
saveImageCmd.Flags().BoolVar(&imgRemote, "remote", false, "Cache image to remote registry")
imageCmd.AddCommand(saveImageCmd)
listImageCmd.Flags().StringVar(&format, "format", "short", "Format output. One of: short|table|json|yaml")
imageCmd.AddCommand(listImageCmd)
imageCmd.AddCommand(tagImageCmd)
imageCmd.AddCommand(pushImageCmd)
Expand Down
27 changes: 2 additions & 25 deletions pkg/minikube/cruntime/containerd.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,21 +283,8 @@ func (r *Containerd) ImageExists(name string, sha string) bool {
}

// 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
func (r *Containerd) ListImages(ListImagesOptions) ([]ListImage, error) {
return listCRIImages(r.Runner)
}

// LoadImage loads an image into this runtime
Expand Down Expand Up @@ -593,16 +580,6 @@ func containerdImagesPreloaded(runner command.Runner, images []string) bool {
if err != nil {
return false
}
type crictlImages struct {
Images []struct {
ID string `json:"id"`
RepoTags []string `json:"repoTags"`
RepoDigests []string `json:"repoDigests"`
Size string `json:"size"`
UID interface{} `json:"uid"`
Username string `json:"username"`
} `json:"images"`
}

var jsonImages crictlImages
err = json.Unmarshal(rr.Stdout.Bytes(), &jsonImages)
Expand Down
38 changes: 38 additions & 0 deletions pkg/minikube/cruntime/cri.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ type container struct {
Status string
}

type crictlImages struct {
Images []struct {
ID string `json:"id"`
RepoTags []string `json:"repoTags"`
RepoDigests []string `json:"repoDigests"`
Size string `json:"size"`
UID interface{} `json:"uid"`
Username string `json:"username"`
} `json:"images"`
}

// crictlList returns the output of 'crictl ps' in an efficient manner
func crictlList(cr CommandRunner, root string, o ListContainersOptions) (*command.RunResult, error) {
klog.Infof("listing CRI containers in root %s: %+v", root, o)
Expand Down Expand Up @@ -267,6 +278,33 @@ func getCRIInfo(cr CommandRunner) (map[string]interface{}, error) {
return jsonMap, nil
}

// listCRIImages lists images using crictl
func listCRIImages(cr CommandRunner) ([]ListImage, error) {
c := exec.Command("sudo", "crictl", "images", "--output", "json")
rr, err := cr.RunCmd(c)
if err != nil {
return nil, errors.Wrapf(err, "crictl images")
}

var jsonImages crictlImages
err = json.Unmarshal(rr.Stdout.Bytes(), &jsonImages)
if err != nil {
klog.Errorf("failed to unmarshal images, will assume images are not preloaded")
return nil, err
}

images := []ListImage{}
for _, img := range jsonImages.Images {
images = append(images, ListImage{
ID: img.ID,
RepoDigests: img.RepoDigests,
RepoTags: img.RepoTags,
Size: img.Size,
})
}
return images, nil
}

// criContainerLogCmd returns the command to retrieve the log for a container based on ID
func criContainerLogCmd(cr CommandRunner, id string, len int, follow bool) string {
crictl := getCrictlPath(cr)
Expand Down
19 changes: 2 additions & 17 deletions pkg/minikube/cruntime/crio.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,13 +240,8 @@ func (r *CRIO) ImageExists(name string, sha string) bool {
}

// 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
func (r *CRIO) ListImages(ListImagesOptions) ([]ListImage, error) {
return listCRIImages(r.Runner)
}

// LoadImage loads an image into this runtime
Expand Down Expand Up @@ -465,16 +460,6 @@ func crioImagesPreloaded(runner command.Runner, images []string) bool {
if err != nil {
return false
}
type crictlImages struct {
Images []struct {
ID string `json:"id"`
RepoTags []string `json:"repoTags"`
RepoDigests []string `json:"repoDigests"`
Size string `json:"size"`
UID interface{} `json:"uid"`
Username string `json:"username"`
} `json:"images"`
}

var jsonImages crictlImages
err = json.Unmarshal(rr.Stdout.Bytes(), &jsonImages)
Expand Down
9 changes: 8 additions & 1 deletion pkg/minikube/cruntime/cruntime.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ type Manager interface {
// ImageExists takes image name and optionally image sha to check if an image exists
ImageExists(string, string) bool
// ListImages returns a list of images managed by this container runtime
ListImages(ListImagesOptions) ([]string, error)
ListImages(ListImagesOptions) ([]ListImage, error)

// RemoveImage remove image based on name
RemoveImage(string) error
Expand Down Expand Up @@ -168,6 +168,13 @@ type ListContainersOptions struct {
type ListImagesOptions struct {
}

type ListImage struct {
ID string `json:"id" yaml:"id"`
RepoDigests []string `json:"repoDigests" yaml:"repoDigests"`
RepoTags []string `json:"repoTags" yaml:"repoTags"`
Size string `json:"size" yaml:"size"`
}

// ErrContainerRuntimeNotRunning is thrown when container runtime is not running
var ErrContainerRuntimeNotRunning = errors.New("container runtime is not running")

Expand Down
39 changes: 31 additions & 8 deletions pkg/minikube/cruntime/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package cruntime

import (
"encoding/json"
"fmt"
"os"
"os/exec"
Expand All @@ -25,6 +26,7 @@ import (
"time"

"github.com/blang/semver/v4"
units "github.com/docker/go-units"
"github.com/pkg/errors"
"k8s.io/klog/v2"
"k8s.io/minikube/pkg/minikube/assets"
Expand Down Expand Up @@ -183,22 +185,43 @@ func (r *Docker) ImageExists(name string, sha string) bool {
}

// 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}}")
func (r *Docker) ListImages(ListImagesOptions) ([]ListImage, error) {
c := exec.Command("docker", "images", "--no-trunc", "--format", "{{json .}}")
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 {
type dockerImage struct {
ID string `json:"ID"`
Repository string `json:"Repository"`
Tag string `json:"Tag"`
Size string `json:"Size"`
}
images := strings.Split(rr.Stdout.String(), "\n")
result := []ListImage{}
for _, img := range images {
if img == "" {
continue
}
img = addDockerIO(img)
imgs = append(imgs, img)

var jsonImage dockerImage
if err := json.Unmarshal([]byte(img), &jsonImage); err != nil {
return nil, errors.Wrap(err, "Image convert problem")
}
size, err := units.FromHumanSize(jsonImage.Size)
if err != nil {
return nil, errors.Wrap(err, "Image size convert problem")
}

repoTag := fmt.Sprintf("%s:%s", jsonImage.Repository, jsonImage.Tag)
result = append(result, ListImage{
ID: strings.TrimPrefix(jsonImage.ID, "sha256:"),
RepoDigests: []string{},
RepoTags: []string{addDockerIO(repoTag)},
Size: fmt.Sprintf("%d", size),
})
}
return imgs, nil
return result, nil
}

// LoadImage loads an image into this runtime
Expand Down
98 changes: 95 additions & 3 deletions pkg/minikube/machine/cache_images.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,25 @@ limitations under the License.
package machine

import (
"encoding/json"
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"time"

"github.com/docker/docker/client"
"github.com/docker/go-units"
"github.com/docker/machine/libmachine/state"
"github.com/olekukonko/tablewriter"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
"gopkg.in/yaml.v2"
"k8s.io/klog/v2"
"k8s.io/minikube/pkg/minikube/assets"
"k8s.io/minikube/pkg/minikube/bootstrapper"
Expand Down Expand Up @@ -666,7 +671,7 @@ func RemoveImages(images []string, profile *config.Profile) error {
}

// ListImages lists images on all nodes in profile
func ListImages(profile *config.Profile) error {
func ListImages(profile *config.Profile, format string) error {
api, err := NewAPIClient()
if err != nil {
return errors.Wrap(err, "error creating api client")
Expand All @@ -681,6 +686,7 @@ func ListImages(profile *config.Profile) error {
return errors.Wrapf(err, "error loading config for profile :%v", pName)
}

images := map[string]cruntime.ListImage{}
for _, n := range c.Nodes {
m := config.MachineName(*c, n)

Expand Down Expand Up @@ -709,14 +715,100 @@ func ListImages(profile *config.Profile) error {
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")

for _, img := range list {
if _, ok := images[img.ID]; !ok {
images[img.ID] = img
}
}
}
}

uniqueImages := []cruntime.ListImage{}
for _, img := range images {
uniqueImages = append(uniqueImages, img)
}

switch format {
case "table":
var data [][]string
for _, item := range uniqueImages {
imageSize := humanImageSize(item.Size)
id := parseImageID(item.ID)
for _, img := range item.RepoTags {
imageName, tag := parseRepoTag(img)
if imageName == "" {
continue
}
data = append(data, []string{imageName, tag, id, imageSize})
}
}
renderImagesTable(data)
case "json":
json, err := json.Marshal(uniqueImages)
if err != nil {
klog.Warningf("Error marshalling images list: %v", err.Error())
return nil
}
fmt.Printf(string(json) + "\n")
case "yaml":
yaml, err := yaml.Marshal(uniqueImages)
if err != nil {
klog.Warningf("Error marshalling images list: %v", err.Error())
return nil
}
fmt.Printf(string(yaml) + "\n")
default:
res := []string{}
for _, item := range uniqueImages {
res = append(res, item.RepoTags...)
}
sort.Sort(sort.Reverse(sort.StringSlice(res)))
fmt.Printf(strings.Join(res, "\n") + "\n")
}

return nil
}

// parseRepoTag splits input string for two parts: image name and image tag
func parseRepoTag(repoTag string) (string, string) {
idx := strings.LastIndex(repoTag, ":")
if idx == -1 {
return "", ""
}
return repoTag[:idx], repoTag[idx+1:]
}

// parseImageID truncates image id
func parseImageID(id string) string {
maxImageIDLen := 13
if len(id) > maxImageIDLen {
return id[:maxImageIDLen]
}
return id
}

// humanImageSize prints size of image in human readable format
func humanImageSize(imageSize string) string {
f, err := strconv.ParseFloat(imageSize, 32)
if err == nil {
return units.HumanSizeWithPrecision(f, 3)
}
return imageSize
}

// renderImagesTable renders pretty table for images list
func renderImagesTable(images [][]string) {
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Image", "Tag", "Image ID", "Size"})
table.SetAutoFormatHeaders(false)
table.SetBorders(tablewriter.Border{Left: true, Top: true, Right: true, Bottom: true})
table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetCenterSeparator("|")
table.AppendBulk(images)
table.Render()
}

// TagImage tags image in all nodes in profile
func TagImage(profile *config.Profile, source string, target string) error {
api, err := NewAPIClient()
Expand Down
Loading