Skip to content

Commit

Permalink
support different formats for 'image ls' command
Browse files Browse the repository at this point in the history
  • Loading branch information
presztak committed Nov 21, 2021
1 parent 670be8a commit bd0953d
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 54 deletions.
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 @@ -111,7 +111,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 @@ -166,6 +166,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

0 comments on commit bd0953d

Please sign in to comment.