2017-06-16 17:24:00 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2017-08-15 20:53:17 +00:00
|
|
|
"reflect"
|
|
|
|
"strings"
|
|
|
|
|
2017-06-16 17:24:00 +00:00
|
|
|
"github.com/containers/storage"
|
2017-07-31 18:02:57 +00:00
|
|
|
"github.com/kubernetes-incubator/cri-o/cmd/kpod/formats"
|
2017-07-24 01:38:03 +00:00
|
|
|
libkpodimage "github.com/kubernetes-incubator/cri-o/libkpod/image"
|
2017-07-25 19:33:41 +00:00
|
|
|
digest "github.com/opencontainers/go-digest"
|
2017-06-16 17:24:00 +00:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/urfave/cli"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
imagesFlags = []cli.Flag{
|
|
|
|
cli.BoolFlag{
|
|
|
|
Name: "quiet, q",
|
|
|
|
Usage: "display only image IDs",
|
|
|
|
},
|
|
|
|
cli.BoolFlag{
|
|
|
|
Name: "noheading, n",
|
|
|
|
Usage: "do not print column headings",
|
|
|
|
},
|
|
|
|
cli.BoolFlag{
|
|
|
|
Name: "no-trunc, notruncate",
|
|
|
|
Usage: "do not truncate output",
|
|
|
|
},
|
|
|
|
cli.BoolFlag{
|
|
|
|
Name: "digests",
|
|
|
|
Usage: "show digests",
|
|
|
|
},
|
|
|
|
cli.StringFlag{
|
|
|
|
Name: "format",
|
2017-07-31 18:02:57 +00:00
|
|
|
Usage: "Change the output format.",
|
2017-06-16 17:24:00 +00:00
|
|
|
},
|
|
|
|
cli.StringFlag{
|
|
|
|
Name: "filter, f",
|
|
|
|
Usage: "filter output based on conditions provided (default [])",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
imagesDescription = "lists locally stored images."
|
|
|
|
imagesCommand = cli.Command{
|
|
|
|
Name: "images",
|
|
|
|
Usage: "list images in local storage",
|
|
|
|
Description: imagesDescription,
|
|
|
|
Flags: imagesFlags,
|
|
|
|
Action: imagesCmd,
|
|
|
|
ArgsUsage: "",
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
func imagesCmd(c *cli.Context) error {
|
2017-07-27 17:18:07 +00:00
|
|
|
config, err := getConfig(c)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "Could not get config")
|
|
|
|
}
|
|
|
|
store, err := getStore(config)
|
2017-06-16 17:24:00 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
quiet := false
|
|
|
|
if c.IsSet("quiet") {
|
|
|
|
quiet = c.Bool("quiet")
|
|
|
|
}
|
|
|
|
noheading := false
|
|
|
|
if c.IsSet("noheading") {
|
|
|
|
noheading = c.Bool("noheading")
|
|
|
|
}
|
|
|
|
truncate := true
|
|
|
|
if c.IsSet("no-trunc") {
|
|
|
|
truncate = !c.Bool("no-trunc")
|
|
|
|
}
|
|
|
|
digests := false
|
|
|
|
if c.IsSet("digests") {
|
|
|
|
digests = c.Bool("digests")
|
|
|
|
}
|
2017-08-16 02:23:40 +00:00
|
|
|
outputFormat := genImagesFormat(quiet, truncate, digests)
|
2017-06-16 17:24:00 +00:00
|
|
|
if c.IsSet("format") {
|
2017-07-31 18:02:57 +00:00
|
|
|
outputFormat = c.String("format")
|
2017-06-16 17:24:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
name := ""
|
|
|
|
if len(c.Args()) == 1 {
|
|
|
|
name = c.Args().Get(0)
|
|
|
|
} else if len(c.Args()) > 1 {
|
2017-08-16 02:23:40 +00:00
|
|
|
return errors.New("'kpod images' requires at most 1 argument")
|
2017-06-16 17:24:00 +00:00
|
|
|
}
|
|
|
|
|
2017-07-24 01:38:03 +00:00
|
|
|
var params *libkpodimage.FilterParams
|
2017-06-16 17:24:00 +00:00
|
|
|
if c.IsSet("filter") {
|
2017-07-24 01:38:03 +00:00
|
|
|
params, err = libkpodimage.ParseFilter(store, c.String("filter"))
|
2017-06-16 17:24:00 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error parsing filter")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
params = nil
|
|
|
|
}
|
|
|
|
|
2017-07-24 01:38:03 +00:00
|
|
|
imageList, err := libkpodimage.GetImagesMatchingFilter(store, params, name)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "could not get list of images matching filter")
|
2017-06-16 17:24:00 +00:00
|
|
|
}
|
|
|
|
|
2017-07-31 18:02:57 +00:00
|
|
|
return outputImages(store, imageList, truncate, digests, quiet, outputFormat, noheading)
|
2017-06-16 17:24:00 +00:00
|
|
|
}
|
|
|
|
|
2017-08-16 02:23:40 +00:00
|
|
|
func genImagesFormat(quiet, truncate, digests bool) (format string) {
|
|
|
|
if quiet {
|
|
|
|
return "{{.ID}}"
|
|
|
|
}
|
2017-06-16 17:24:00 +00:00
|
|
|
if truncate {
|
2017-08-16 02:23:40 +00:00
|
|
|
format = "table {{ .ID | printf \"%-20.12s\" }} "
|
2017-06-16 17:24:00 +00:00
|
|
|
} else {
|
2017-08-16 02:23:40 +00:00
|
|
|
format = "table {{ .ID | printf \"%-64s\" }} "
|
2017-06-16 17:24:00 +00:00
|
|
|
}
|
2017-08-16 02:23:40 +00:00
|
|
|
format += "{{ .Name | printf \"%-56s\" }} "
|
2017-06-16 17:24:00 +00:00
|
|
|
|
|
|
|
if digests {
|
2017-08-16 02:23:40 +00:00
|
|
|
format += "{{ .Digest | printf \"%-71s \"}} "
|
2017-06-16 17:24:00 +00:00
|
|
|
}
|
|
|
|
|
2017-08-16 02:23:40 +00:00
|
|
|
format += "{{ .CreatedAt | printf \"%-22s\" }} {{.Size}}"
|
|
|
|
return
|
2017-06-16 17:24:00 +00:00
|
|
|
}
|
|
|
|
|
2017-07-31 18:02:57 +00:00
|
|
|
func outputImages(store storage.Store, images []storage.Image, truncate, digests, quiet bool, outputFormat string, noheading bool) error {
|
|
|
|
imageOutput := []imageOutputParams{}
|
|
|
|
|
2017-08-16 02:23:40 +00:00
|
|
|
lastID := ""
|
2017-07-21 20:43:30 +00:00
|
|
|
for _, img := range images {
|
2017-08-16 02:23:40 +00:00
|
|
|
if quiet && lastID == img.ID {
|
|
|
|
continue // quiet should not show the same ID multiple times
|
|
|
|
}
|
2017-07-27 17:35:38 +00:00
|
|
|
createdTime := img.Created
|
2017-07-25 19:33:41 +00:00
|
|
|
|
|
|
|
name := ""
|
|
|
|
if len(img.Names) > 0 {
|
|
|
|
name = img.Names[0]
|
2017-06-16 17:24:00 +00:00
|
|
|
}
|
2017-07-25 19:33:41 +00:00
|
|
|
|
2017-08-16 02:23:40 +00:00
|
|
|
info, imageDigest, size, _ := libkpodimage.InfoAndDigestAndSize(store, img)
|
2017-07-27 17:35:38 +00:00
|
|
|
if info != nil {
|
|
|
|
createdTime = info.Created
|
|
|
|
}
|
2017-06-16 17:24:00 +00:00
|
|
|
|
2017-07-24 01:38:03 +00:00
|
|
|
params := imageOutputParams{
|
|
|
|
ID: img.ID,
|
2017-07-25 19:33:41 +00:00
|
|
|
Name: name,
|
2017-08-16 02:23:40 +00:00
|
|
|
Digest: imageDigest,
|
2017-07-27 17:35:38 +00:00
|
|
|
CreatedAt: createdTime.Format("Jan 2, 2006 15:04"),
|
2017-07-24 01:38:03 +00:00
|
|
|
Size: libkpodimage.FormattedSize(size),
|
2017-06-16 17:24:00 +00:00
|
|
|
}
|
2017-07-31 18:02:57 +00:00
|
|
|
imageOutput = append(imageOutput, params)
|
2017-06-16 17:24:00 +00:00
|
|
|
}
|
|
|
|
|
2017-07-31 18:02:57 +00:00
|
|
|
var out formats.Writer
|
2017-06-16 17:24:00 +00:00
|
|
|
|
2017-08-16 02:23:40 +00:00
|
|
|
switch outputFormat {
|
|
|
|
case "json":
|
|
|
|
out = formats.JSONstruct{Output: toGeneric(imageOutput)}
|
|
|
|
default:
|
|
|
|
out = formats.StdoutTemplate{Output: toGeneric(imageOutput), Template: outputFormat, Fields: imageOutput[0].headerMap()}
|
2017-06-16 17:24:00 +00:00
|
|
|
}
|
|
|
|
|
2017-07-31 18:02:57 +00:00
|
|
|
formats.Writer(out).Out()
|
|
|
|
|
|
|
|
return nil
|
2017-06-16 17:24:00 +00:00
|
|
|
}
|
2017-08-15 20:53:17 +00:00
|
|
|
|
|
|
|
type imageOutputParams struct {
|
|
|
|
ID string `json:"id"`
|
|
|
|
Name string `json:"names"`
|
|
|
|
Digest digest.Digest `json:"digest"`
|
|
|
|
CreatedAt string `json:"created"`
|
|
|
|
Size string `json:"size"`
|
|
|
|
}
|
|
|
|
|
2017-08-16 02:23:40 +00:00
|
|
|
func toGeneric(params []imageOutputParams) []interface{} {
|
|
|
|
genericParams := make([]interface{}, len(params))
|
|
|
|
for i, v := range params {
|
|
|
|
genericParams[i] = interface{}(v)
|
|
|
|
}
|
|
|
|
return genericParams
|
|
|
|
}
|
|
|
|
|
2017-08-15 20:53:17 +00:00
|
|
|
func (i *imageOutputParams) headerMap() map[string]string {
|
|
|
|
v := reflect.Indirect(reflect.ValueOf(i))
|
|
|
|
values := make(map[string]string)
|
|
|
|
|
|
|
|
for i := 0; i < v.NumField(); i++ {
|
|
|
|
key := v.Type().Field(i).Name
|
|
|
|
value := key
|
|
|
|
if value == "ID" || value == "Name" {
|
|
|
|
value = "Image" + value
|
|
|
|
}
|
|
|
|
values[key] = fmt.Sprintf("%s ", strings.ToUpper(splitCamelCase(value)))
|
|
|
|
}
|
|
|
|
return values
|
|
|
|
}
|