github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/cmd/podmanV2/images/list.go (about)

     1  package images
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"sort"
     8  	"strings"
     9  	"text/tabwriter"
    10  	"text/template"
    11  	"time"
    12  	"unicode"
    13  
    14  	"github.com/containers/libpod/cmd/podmanV2/registry"
    15  	"github.com/containers/libpod/pkg/domain/entities"
    16  	"github.com/docker/go-units"
    17  	jsoniter "github.com/json-iterator/go"
    18  	"github.com/spf13/cobra"
    19  	"github.com/spf13/pflag"
    20  )
    21  
    22  type listFlagType struct {
    23  	format    string
    24  	history   bool
    25  	noHeading bool
    26  	noTrunc   bool
    27  	quiet     bool
    28  	sort      string
    29  	readOnly  bool
    30  	digests   bool
    31  }
    32  
    33  var (
    34  	// Command: podman image _list_
    35  	listCmd = &cobra.Command{
    36  		Use:     "list [flag] [IMAGE]",
    37  		Aliases: []string{"ls"},
    38  		Args:    cobra.MaximumNArgs(1),
    39  		Short:   "List images in local storage",
    40  		Long:    "Lists images previously pulled to the system or created on the system.",
    41  		RunE:    images,
    42  		Example: `podman image list --format json
    43    podman image list --sort repository --format "table {{.ID}} {{.Repository}} {{.Tag}}"
    44    podman image list --filter dangling=true`,
    45  	}
    46  
    47  	// Options to pull data
    48  	listOptions = entities.ImageListOptions{}
    49  
    50  	// Options for presenting data
    51  	listFlag = listFlagType{}
    52  
    53  	sortFields = entities.NewStringSet(
    54  		"created",
    55  		"id",
    56  		"repository",
    57  		"size",
    58  		"tag")
    59  )
    60  
    61  func init() {
    62  	registry.Commands = append(registry.Commands, registry.CliCommand{
    63  		Mode:    []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
    64  		Command: listCmd,
    65  		Parent:  imageCmd,
    66  	})
    67  	imageListFlagSet(listCmd.Flags())
    68  }
    69  
    70  func imageListFlagSet(flags *pflag.FlagSet) {
    71  	flags.BoolVarP(&listOptions.All, "all", "a", false, "Show all images (default hides intermediate images)")
    72  	flags.StringSliceVarP(&listOptions.Filter, "filter", "f", []string{}, "Filter output based on conditions provided (default [])")
    73  	flags.StringVar(&listFlag.format, "format", "", "Change the output format to JSON or a Go template")
    74  	flags.BoolVar(&listFlag.digests, "digests", false, "Show digests")
    75  	flags.BoolVarP(&listFlag.noHeading, "noheading", "n", false, "Do not print column headings")
    76  	flags.BoolVar(&listFlag.noTrunc, "no-trunc", false, "Do not truncate output")
    77  	flags.BoolVar(&listFlag.noTrunc, "notruncate", false, "Do not truncate output")
    78  	flags.BoolVarP(&listFlag.quiet, "quiet", "q", false, "Display only image IDs")
    79  	flags.StringVar(&listFlag.sort, "sort", "created", "Sort by "+sortFields.String())
    80  	flags.BoolVarP(&listFlag.history, "history", "", false, "Display the image name history")
    81  }
    82  
    83  func images(cmd *cobra.Command, args []string) error {
    84  	if len(listOptions.Filter) > 0 && len(args) > 0 {
    85  		return errors.New("cannot specify an image and a filter(s)")
    86  	}
    87  
    88  	if len(listOptions.Filter) < 1 && len(args) > 0 {
    89  		listOptions.Filter = append(listOptions.Filter, "reference="+args[0])
    90  	}
    91  
    92  	if cmd.Flag("sort").Changed && !sortFields.Contains(listFlag.sort) {
    93  		return fmt.Errorf("\"%s\" is not a valid field for sorting. Choose from: %s",
    94  			listFlag.sort, sortFields.String())
    95  	}
    96  
    97  	summaries, err := registry.ImageEngine().List(registry.GetContext(), listOptions)
    98  	if err != nil {
    99  		return err
   100  	}
   101  
   102  	imageS := summaries
   103  	sort.Slice(imageS, sortFunc(listFlag.sort, imageS))
   104  
   105  	if cmd.Flag("format").Changed && listFlag.format == "json" {
   106  		return writeJSON(imageS)
   107  	} else {
   108  		return writeTemplate(imageS, err)
   109  	}
   110  }
   111  
   112  func writeJSON(imageS []*entities.ImageSummary) error {
   113  	type image struct {
   114  		entities.ImageSummary
   115  		Created string
   116  	}
   117  
   118  	imgs := make([]image, 0, len(imageS))
   119  	for _, e := range imageS {
   120  		var h image
   121  		h.ImageSummary = *e
   122  		h.Created = time.Unix(e.Created, 0).Format(time.RFC3339)
   123  		h.RepoTags = nil
   124  
   125  		imgs = append(imgs, h)
   126  	}
   127  
   128  	json := jsoniter.ConfigCompatibleWithStandardLibrary
   129  	enc := json.NewEncoder(os.Stdout)
   130  	return enc.Encode(imgs)
   131  }
   132  
   133  func writeTemplate(imageS []*entities.ImageSummary, err error) error {
   134  	var (
   135  		hdr, row string
   136  	)
   137  	imgs := make([]imageReporter, 0, len(imageS))
   138  	for _, e := range imageS {
   139  		for _, tag := range e.RepoTags {
   140  			var h imageReporter
   141  			h.ImageSummary = *e
   142  			h.Repository, h.Tag = tokenRepoTag(tag)
   143  			imgs = append(imgs, h)
   144  		}
   145  		if e.IsReadOnly() {
   146  			listFlag.readOnly = true
   147  		}
   148  	}
   149  	if len(listFlag.format) < 1 {
   150  		hdr, row = imageListFormat(listFlag)
   151  	} else {
   152  		row = listFlag.format
   153  		if !strings.HasSuffix(row, "\n") {
   154  			row += "\n"
   155  		}
   156  	}
   157  	format := hdr + "{{range . }}" + row + "{{end}}"
   158  	tmpl := template.Must(template.New("list").Parse(format))
   159  	w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0)
   160  	defer w.Flush()
   161  	return tmpl.Execute(w, imgs)
   162  }
   163  
   164  func tokenRepoTag(tag string) (string, string) {
   165  	tokens := strings.SplitN(tag, ":", 2)
   166  	switch len(tokens) {
   167  	case 0:
   168  		return tag, ""
   169  	case 1:
   170  		return tokens[0], ""
   171  	case 2:
   172  		return tokens[0], tokens[1]
   173  	default:
   174  		return "<N/A>", ""
   175  	}
   176  }
   177  
   178  func sortFunc(key string, data []*entities.ImageSummary) func(i, j int) bool {
   179  	switch key {
   180  	case "id":
   181  		return func(i, j int) bool {
   182  			return data[i].ID < data[j].ID
   183  		}
   184  	case "repository":
   185  		return func(i, j int) bool {
   186  			return data[i].RepoTags[0] < data[j].RepoTags[0]
   187  		}
   188  	case "size":
   189  		return func(i, j int) bool {
   190  			return data[i].Size < data[j].Size
   191  		}
   192  	case "tag":
   193  		return func(i, j int) bool {
   194  			return data[i].RepoTags[0] < data[j].RepoTags[0]
   195  		}
   196  	default:
   197  		// case "created":
   198  		return func(i, j int) bool {
   199  			return data[i].Created >= data[j].Created
   200  		}
   201  	}
   202  }
   203  
   204  func imageListFormat(flags listFlagType) (string, string) {
   205  	if flags.quiet {
   206  		return "", "{{.ID}}\n"
   207  	}
   208  
   209  	// Defaults
   210  	hdr := "REPOSITORY\tTAG"
   211  	row := "{{.Repository}}\t{{if .Tag}}{{.Tag}}{{else}}<none>{{end}}"
   212  
   213  	if flags.digests {
   214  		hdr += "\tDIGEST"
   215  		row += "\t{{.Digest}}"
   216  	}
   217  
   218  	hdr += "\tIMAGE ID"
   219  	if flags.noTrunc {
   220  		row += "\tsha256:{{.ID}}"
   221  	} else {
   222  		row += "\t{{.ID}}"
   223  	}
   224  
   225  	hdr += "\tCREATED\tSIZE"
   226  	row += "\t{{.Created}}\t{{.Size}}"
   227  
   228  	if flags.history {
   229  		hdr += "\tHISTORY"
   230  		row += "\t{{if .History}}{{.History}}{{else}}<none>{{end}}"
   231  	}
   232  
   233  	if flags.readOnly {
   234  		hdr += "\tReadOnly"
   235  		row += "\t{{.ReadOnly}}"
   236  	}
   237  
   238  	if flags.noHeading {
   239  		hdr = ""
   240  	} else {
   241  		hdr += "\n"
   242  	}
   243  
   244  	row += "\n"
   245  	return hdr, row
   246  }
   247  
   248  type imageReporter struct {
   249  	Repository string `json:"repository,omitempty"`
   250  	Tag        string `json:"tag,omitempty"`
   251  	entities.ImageSummary
   252  }
   253  
   254  func (i imageReporter) ID() string {
   255  	if !listFlag.noTrunc && len(i.ImageSummary.ID) >= 12 {
   256  		return i.ImageSummary.ID[0:12]
   257  	}
   258  	return i.ImageSummary.ID
   259  }
   260  
   261  func (i imageReporter) Created() string {
   262  	return units.HumanDuration(time.Since(time.Unix(i.ImageSummary.Created, 0))) + " ago"
   263  }
   264  
   265  func (i imageReporter) Size() string {
   266  	s := units.HumanSizeWithPrecision(float64(i.ImageSummary.Size), 3)
   267  	j := strings.LastIndexFunc(s, unicode.IsNumber)
   268  	return s[:j+1] + " " + s[j+1:]
   269  }
   270  
   271  func (i imageReporter) History() string {
   272  	return strings.Join(i.ImageSummary.History, ", ")
   273  }