Merge pull request #12996 from presztak/listimages_formats

Add format flag to the `image ls` command
pull/13234/head
Steven Powell 2021-12-23 12:31:59 -08:00 committed by GitHub
commit b6e2ee2128
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 216 additions and 69 deletions

View File

@ -54,6 +54,7 @@ var (
dockerFile string
buildEnv []string
buildOpt []string
format string
)
func saveFile(r io.Reader) (string, error) {
@ -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)
}
},
@ -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)

View File

@ -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
@ -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)

View File

@ -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)
@ -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)

View File

@ -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
@ -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)

View File

@ -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
@ -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")

View File

@ -17,6 +17,7 @@ limitations under the License.
package cruntime
import (
"encoding/json"
"fmt"
"os"
"os/exec"
@ -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"
@ -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

View File

@ -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"
@ -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")
@ -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)
@ -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()

View File

@ -196,6 +196,12 @@ $ minikube image ls
```
### Options
```
--format string Format output. One of: short|table|json|yaml (default "short")
```
### Options inherited from parent commands
```

View File

@ -245,22 +245,13 @@ func tagAndLoadImage(ctx context.Context, t *testing.T, profile, taggedImage str
checkImageExists(ctx, t, profile, taggedImage)
}
// validateImageCommands runs tests on all the `minikube image` commands, ex. `minikube image load`, `minikube image list`, etc.
func validateImageCommands(ctx context.Context, t *testing.T, profile string) {
// docs(skip): Skips on `none` driver as image loading is not supported
if NoneDriver() {
t.Skip("image commands are not available on the none driver")
}
// docs(skip): Skips on GitHub Actions and macOS as this test case requires a running docker daemon
if GithubActionRunner() && runtime.GOOS == "darwin" {
t.Skip("skipping on darwin github action runners, as this test requires a running docker daemon")
}
// runImageList is a helper function to run 'image ls' command test.
func runImageList(ctx context.Context, t *testing.T, profile, testName, format string, expectedResult []string) {
// docs: Make sure image listing works by `minikube image ls`
t.Run("ImageList", func(t *testing.T) {
t.Run(testName, func(t *testing.T) {
MaybeParallel(t)
rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "image", "ls"))
rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "image", "ls", "--format", format))
if err != nil {
t.Fatalf("listing image with minikube: %v\n%s", err, rr.Output())
}
@ -272,12 +263,29 @@ func validateImageCommands(ctx context.Context, t *testing.T, profile string) {
}
list := rr.Output()
for _, theImage := range []string{"k8s.gcr.io/pause", "docker.io/kubernetesui/dashboard"} {
for _, theImage := range expectedResult {
if !strings.Contains(list, theImage) {
t.Fatalf("expected %s to be listed with minikube but the image is not there", theImage)
}
}
})
}
// validateImageCommands runs tests on all the `minikube image` commands, ex. `minikube image load`, `minikube image list`, etc.
func validateImageCommands(ctx context.Context, t *testing.T, profile string) {
// docs(skip): Skips on `none` driver as image loading is not supported
if NoneDriver() {
t.Skip("image commands are not available on the none driver")
}
// docs(skip): Skips on GitHub Actions and macOS as this test case requires a running docker daemon
if GithubActionRunner() && runtime.GOOS == "darwin" {
t.Skip("skipping on darwin github action runners, as this test requires a running docker daemon")
}
runImageList(ctx, t, profile, "ImageListShort", "short", []string{"k8s.gcr.io/pause", "docker.io/kubernetesui/dashboard"})
runImageList(ctx, t, profile, "ImageListTable", "table", []string{"| k8s.gcr.io/pause", "| docker.io/kubernetesui/dashboard"})
runImageList(ctx, t, profile, "ImageListJson", "json", []string{"[\"k8s.gcr.io/pause", "[\"docker.io/kubernetesui/dashboard"})
runImageList(ctx, t, profile, "ImageListYaml", "yaml", []string{"- k8s.gcr.io/pause", "- docker.io/kubernetesui/dashboard"})
// docs: Make sure image building works by `minikube image build`
t.Run("ImageBuild", func(t *testing.T) {

View File

@ -296,6 +296,7 @@
"For more information, see: {{.url}}": "",
"Force environment to be configured for a specified shell: [fish, cmd, powershell, tcsh, bash, zsh], default is auto-detect": "",
"Force minikube to perform possibly dangerous operations": "minikube zwingen, möglicherweise gefährliche Operationen durchzuführen",
"Format output. One of: short|table|json|yaml": "",
"Format to print stdout in. Options include: [text,json]": "",
"Found docker, but the docker service isn't running. Try restarting the docker service.": "",
"Found driver(s) but none were healthy. See above for suggestions how to fix installed drivers.": "",

View File

@ -305,6 +305,7 @@
"For more information, see: {{.url}}": "",
"Force environment to be configured for a specified shell: [fish, cmd, powershell, tcsh, bash, zsh], default is auto-detect": "",
"Force minikube to perform possibly dangerous operations": "Permite forzar minikube para que realice operaciones potencialmente peligrosas",
"Format output. One of: short|table|json|yaml": "",
"Format to print stdout in. Options include: [text,json]": "",
"Found docker, but the docker service isn't running. Try restarting the docker service.": "",
"Found driver(s) but none were healthy. See above for suggestions how to fix installed drivers.": "",

View File

@ -285,6 +285,7 @@
"For more information, see: {{.url}}": "Pour plus d'informations, voir : {{.url}}",
"Force environment to be configured for a specified shell: [fish, cmd, powershell, tcsh, bash, zsh], default is auto-detect": "Forcer l'environnement à être configuré pour un shell spécifié : [fish, cmd, powershell, tcsh, bash, zsh], la valeur par défaut est la détection automatique",
"Force minikube to perform possibly dangerous operations": "Oblige minikube à réaliser des opérations possiblement dangereuses.",
"Format output. One of: short|table|json|yaml": "",
"Format to print stdout in. Options include: [text,json]": "Format dans lequel imprimer la sortie standard. Les options incluent : [text,json]",
"Found docker, but the docker service isn't running. Try restarting the docker service.": "Docker trouvé, mais le service docker ne fonctionne pas. Essayez de redémarrer le service Docker.",
"Found driver(s) but none were healthy. See above for suggestions how to fix installed drivers.": "Pilote(s) trouvé(s) mais aucun n'était en fonctionnement. Voir ci-dessus pour des suggestions sur la façon de réparer les pilotes installés.",

View File

@ -293,6 +293,7 @@
"For more information, see: {{.url}}": "",
"Force environment to be configured for a specified shell: [fish, cmd, powershell, tcsh, bash, zsh], default is auto-detect": "",
"Force minikube to perform possibly dangerous operations": "minikube で危険な可能性のある操作を強制的に実行します",
"Format output. One of: short|table|json|yaml": "",
"Format to print stdout in. Options include: [text,json]": "",
"Found docker, but the docker service isn't running. Try restarting the docker service.": "",
"Found driver(s) but none were healthy. See above for suggestions how to fix installed drivers.": "",

View File

@ -320,6 +320,7 @@
"For more information, see: {{.url}}": "",
"Force environment to be configured for a specified shell: [fish, cmd, powershell, tcsh, bash, zsh], default is auto-detect": "",
"Force minikube to perform possibly dangerous operations": "",
"Format output. One of: short|table|json|yaml": "",
"Format to print stdout in. Options include: [text,json]": "",
"Found docker, but the docker service isn't running. Try restarting the docker service.": "",
"Found driver(s) but none were healthy. See above for suggestions how to fix installed drivers.": "",

View File

@ -306,6 +306,7 @@
"For more information, see: {{.url}}": "",
"Force environment to be configured for a specified shell: [fish, cmd, powershell, tcsh, bash, zsh], default is auto-detect": "",
"Force minikube to perform possibly dangerous operations": "Wymuś wykonanie potencjalnie niebezpiecznych operacji",
"Format output. One of: short|table|json|yaml": "",
"Format to print stdout in. Options include: [text,json]": "",
"Found docker, but the docker service isn't running. Try restarting the docker service.": "",
"Found driver(s) but none were healthy. See above for suggestions how to fix installed drivers.": "",

View File

@ -277,6 +277,7 @@
"For more information, see: {{.url}}": "",
"Force environment to be configured for a specified shell: [fish, cmd, powershell, tcsh, bash, zsh], default is auto-detect": "",
"Force minikube to perform possibly dangerous operations": "",
"Format output. One of: short|table|json|yaml": "",
"Format to print stdout in. Options include: [text,json]": "",
"Found docker, but the docker service isn't running. Try restarting the docker service.": "",
"Found driver(s) but none were healthy. See above for suggestions how to fix installed drivers.": "",

View File

@ -277,6 +277,7 @@
"For more information, see: {{.url}}": "",
"Force environment to be configured for a specified shell: [fish, cmd, powershell, tcsh, bash, zsh], default is auto-detect": "",
"Force minikube to perform possibly dangerous operations": "",
"Format output. One of: short|table|json|yaml": "",
"Format to print stdout in. Options include: [text,json]": "",
"Found docker, but the docker service isn't running. Try restarting the docker service.": "",
"Found driver(s) but none were healthy. See above for suggestions how to fix installed drivers.": "",

View File

@ -374,6 +374,7 @@
"For more information, see: {{.url}}": "",
"Force environment to be configured for a specified shell: [fish, cmd, powershell, tcsh, bash, zsh], default is auto-detect": "强制为指定的 shell 配置环境:[fish, cmd, powershell, tcsh, bash, zsh],默认为 auto-detect",
"Force minikube to perform possibly dangerous operations": "强制 minikube 执行可能有风险的操作",
"Format output. One of: short|table|json|yaml": "",
"Format to print stdout in. Options include: [text,json]": "",
"Found docker, but the docker service isn't running. Try restarting the docker service.": "",
"Found driver(s) but none were healthy. See above for suggestions how to fix installed drivers.": "",