244 lines
6.0 KiB
Go
244 lines
6.0 KiB
Go
package images
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
"text/tabwriter"
|
|
"text/template"
|
|
"time"
|
|
|
|
"github.com/containers/libpod/cmd/podmanV2/registry"
|
|
"github.com/containers/libpod/cmd/podmanV2/report"
|
|
"github.com/containers/libpod/pkg/domain/entities"
|
|
jsoniter "github.com/json-iterator/go"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/pflag"
|
|
)
|
|
|
|
type listFlagType struct {
|
|
format string
|
|
history bool
|
|
noHeading bool
|
|
noTrunc bool
|
|
quiet bool
|
|
sort string
|
|
readOnly bool
|
|
digests bool
|
|
}
|
|
|
|
var (
|
|
// Command: podman image _list_
|
|
listCmd = &cobra.Command{
|
|
Use: "list [flag] [IMAGE]",
|
|
Aliases: []string{"ls"},
|
|
Args: cobra.MaximumNArgs(1),
|
|
Short: "List images in local storage",
|
|
Long: "Lists images previously pulled to the system or created on the system.",
|
|
RunE: images,
|
|
Example: `podman image list --format json
|
|
podman image list --sort repository --format "table {{.ID}} {{.Repository}} {{.Tag}}"
|
|
podman image list --filter dangling=true`,
|
|
}
|
|
|
|
// Options to pull data
|
|
listOptions = entities.ImageListOptions{}
|
|
|
|
// Options for presenting data
|
|
listFlag = listFlagType{}
|
|
|
|
sortFields = entities.NewStringSet(
|
|
"created",
|
|
"id",
|
|
"repository",
|
|
"size",
|
|
"tag")
|
|
)
|
|
|
|
func init() {
|
|
registry.Commands = append(registry.Commands, registry.CliCommand{
|
|
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
|
|
Command: listCmd,
|
|
Parent: imageCmd,
|
|
})
|
|
imageListFlagSet(listCmd.Flags())
|
|
}
|
|
|
|
func imageListFlagSet(flags *pflag.FlagSet) {
|
|
flags.BoolVarP(&listOptions.All, "all", "a", false, "Show all images (default hides intermediate images)")
|
|
flags.StringSliceVarP(&listOptions.Filter, "filter", "f", []string{}, "Filter output based on conditions provided (default [])")
|
|
flags.StringVar(&listFlag.format, "format", "", "Change the output format to JSON or a Go template")
|
|
flags.BoolVar(&listFlag.digests, "digests", false, "Show digests")
|
|
flags.BoolVarP(&listFlag.noHeading, "noheading", "n", false, "Do not print column headings")
|
|
flags.BoolVar(&listFlag.noTrunc, "no-trunc", false, "Do not truncate output")
|
|
flags.BoolVar(&listFlag.noTrunc, "notruncate", false, "Do not truncate output")
|
|
flags.BoolVarP(&listFlag.quiet, "quiet", "q", false, "Display only image IDs")
|
|
flags.StringVar(&listFlag.sort, "sort", "created", "Sort by "+sortFields.String())
|
|
flags.BoolVarP(&listFlag.history, "history", "", false, "Display the image name history")
|
|
}
|
|
|
|
func images(cmd *cobra.Command, args []string) error {
|
|
if len(listOptions.Filter) > 0 && len(args) > 0 {
|
|
return errors.New("cannot specify an image and a filter(s)")
|
|
}
|
|
|
|
if len(listOptions.Filter) < 1 && len(args) > 0 {
|
|
listOptions.Filter = append(listOptions.Filter, "reference="+args[0])
|
|
}
|
|
|
|
if cmd.Flag("sort").Changed && !sortFields.Contains(listFlag.sort) {
|
|
return fmt.Errorf("\"%s\" is not a valid field for sorting. Choose from: %s",
|
|
listFlag.sort, sortFields.String())
|
|
}
|
|
|
|
summaries, err := registry.ImageEngine().List(registry.GetContext(), listOptions)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
imageS := summaries
|
|
sort.Slice(imageS, sortFunc(listFlag.sort, imageS))
|
|
|
|
if cmd.Flag("format").Changed && listFlag.format == "json" {
|
|
return writeJSON(imageS)
|
|
} else {
|
|
return writeTemplate(imageS, err)
|
|
}
|
|
}
|
|
|
|
func writeJSON(imageS []*entities.ImageSummary) error {
|
|
type image struct {
|
|
entities.ImageSummary
|
|
Created string
|
|
}
|
|
|
|
imgs := make([]image, 0, len(imageS))
|
|
for _, e := range imageS {
|
|
var h image
|
|
h.ImageSummary = *e
|
|
h.Created = time.Unix(e.Created, 0).Format(time.RFC3339)
|
|
h.RepoTags = nil
|
|
|
|
imgs = append(imgs, h)
|
|
}
|
|
|
|
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
|
enc := json.NewEncoder(os.Stdout)
|
|
return enc.Encode(imgs)
|
|
}
|
|
|
|
func writeTemplate(imageS []*entities.ImageSummary, err error) error {
|
|
type image struct {
|
|
entities.ImageSummary
|
|
Repository string `json:"repository,omitempty"`
|
|
Tag string `json:"tag,omitempty"`
|
|
}
|
|
|
|
imgs := make([]image, 0, len(imageS))
|
|
for _, e := range imageS {
|
|
for _, tag := range e.RepoTags {
|
|
var h image
|
|
h.ImageSummary = *e
|
|
h.Repository, h.Tag = tokenRepoTag(tag)
|
|
imgs = append(imgs, h)
|
|
}
|
|
if e.IsReadOnly() {
|
|
listFlag.readOnly = true
|
|
}
|
|
}
|
|
|
|
hdr, row := imageListFormat(listFlag)
|
|
format := hdr + "{{range . }}" + row + "{{end}}"
|
|
|
|
tmpl := template.Must(template.New("list").Funcs(report.PodmanTemplateFuncs()).Parse(format))
|
|
w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0)
|
|
defer w.Flush()
|
|
return tmpl.Execute(w, imgs)
|
|
}
|
|
|
|
func tokenRepoTag(tag string) (string, string) {
|
|
tokens := strings.SplitN(tag, ":", 2)
|
|
switch len(tokens) {
|
|
case 0:
|
|
return tag, ""
|
|
case 1:
|
|
return tokens[0], ""
|
|
case 2:
|
|
return tokens[0], tokens[1]
|
|
default:
|
|
return "<N/A>", ""
|
|
}
|
|
}
|
|
|
|
func sortFunc(key string, data []*entities.ImageSummary) func(i, j int) bool {
|
|
switch key {
|
|
case "id":
|
|
return func(i, j int) bool {
|
|
return data[i].ID < data[j].ID
|
|
}
|
|
case "repository":
|
|
return func(i, j int) bool {
|
|
return data[i].RepoTags[0] < data[j].RepoTags[0]
|
|
}
|
|
case "size":
|
|
return func(i, j int) bool {
|
|
return data[i].Size < data[j].Size
|
|
}
|
|
case "tag":
|
|
return func(i, j int) bool {
|
|
return data[i].RepoTags[0] < data[j].RepoTags[0]
|
|
}
|
|
default:
|
|
// case "created":
|
|
return func(i, j int) bool {
|
|
return data[i].Created >= data[j].Created
|
|
}
|
|
}
|
|
}
|
|
|
|
func imageListFormat(flags listFlagType) (string, string) {
|
|
if flags.quiet {
|
|
return "", "{{slice .ID 0 12}}\n"
|
|
}
|
|
|
|
// Defaults
|
|
hdr := "REPOSITORY\tTAG"
|
|
row := "{{.Repository}}\t{{if .Tag}}{{.Tag}}{{else}}<none>{{end}}"
|
|
|
|
if flags.digests {
|
|
hdr += "\tDIGEST"
|
|
row += "\t{{.Digest}}"
|
|
}
|
|
|
|
hdr += "\tIMAGE ID"
|
|
if flags.noTrunc {
|
|
row += "\tsha256:{{.ID}}"
|
|
} else {
|
|
row += "\t{{slice .ID 0 12}}"
|
|
}
|
|
|
|
hdr += "\tCREATED\tSIZE"
|
|
row += "\t{{humanDuration .Created}}\t{{humanSize .Size}}"
|
|
|
|
if flags.history {
|
|
hdr += "\tHISTORY"
|
|
row += "\t{{if .History}}{{join .History \", \"}}{{else}}<none>{{end}}"
|
|
}
|
|
|
|
if flags.readOnly {
|
|
hdr += "\tReadOnly"
|
|
row += "\t{{.ReadOnly}}"
|
|
}
|
|
|
|
if flags.noHeading {
|
|
hdr = ""
|
|
} else {
|
|
hdr += "\n"
|
|
}
|
|
|
|
row += "\n"
|
|
return hdr, row
|
|
}
|