cri-o/cmd/kpod/images.go
Daniel J Walsh 04e96d05fc validateFlags command line options to make sure the user entered a value
When a user enters a CLI with a StringFlags or StringSliceFlags and does not add
a value the CLI mistakently takes the next option and uses it as a value.

This usually ends up with an error like not enough options or others.  Some times
it could also succeed, with weird results.  This patch looks for any values that
begin with a "-" and return an error.

Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
2017-10-04 09:36:29 +00:00

209 lines
4.8 KiB
Go

package main
import (
"reflect"
"strings"
"github.com/containers/storage"
"github.com/kubernetes-incubator/cri-o/cmd/kpod/formats"
libpod "github.com/kubernetes-incubator/cri-o/libpod/images"
digest "github.com/opencontainers/go-digest"
"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",
Usage: "Change the output format to JSON or a Go template",
},
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 {
if err := validateFlags(c, imagesFlags); err != nil {
return err
}
config, err := getConfig(c)
if err != nil {
return errors.Wrapf(err, "Could not get config")
}
store, err := getStore(config)
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")
}
outputFormat := genImagesFormat(quiet, truncate, digests)
if c.IsSet("format") {
outputFormat = c.String("format")
}
name := ""
if len(c.Args()) == 1 {
name = c.Args().Get(0)
} else if len(c.Args()) > 1 {
return errors.New("'kpod images' requires at most 1 argument")
}
var params *libpod.FilterParams
if c.IsSet("filter") {
params, err = libpod.ParseFilter(store, c.String("filter"))
if err != nil {
return errors.Wrapf(err, "error parsing filter")
}
} else {
params = nil
}
imageList, err := libpod.GetImagesMatchingFilter(store, params, name)
if err != nil {
return errors.Wrapf(err, "could not get list of images matching filter")
}
return outputImages(store, imageList, truncate, digests, quiet, outputFormat, noheading)
}
func genImagesFormat(quiet, truncate, digests bool) (format string) {
if quiet {
return formats.IDString
}
if truncate {
format = "table {{ .ID | printf \"%-20.12s\" }} "
} else {
format = "table {{ .ID | printf \"%-64s\" }} "
}
format += "{{ .Name | printf \"%-56s\" }} "
if digests {
format += "{{ .Digest | printf \"%-71s \"}} "
}
format += "{{ .CreatedAt | printf \"%-22s\" }} {{.Size}}"
return
}
func outputImages(store storage.Store, images []storage.Image, truncate, digests, quiet bool, outputFormat string, noheading bool) error {
imageOutput := []imageOutputParams{}
lastID := ""
for _, img := range images {
if quiet && lastID == img.ID {
continue // quiet should not show the same ID multiple times
}
createdTime := img.Created
names := []string{""}
if len(img.Names) > 0 {
names = img.Names
}
info, imageDigest, size, _ := libpod.InfoAndDigestAndSize(store, img)
if info != nil {
createdTime = info.Created
}
params := imageOutputParams{
ID: img.ID,
Name: names,
Digest: imageDigest,
CreatedAt: createdTime.Format("Jan 2, 2006 15:04"),
Size: libpod.FormattedSize(float64(size)),
}
imageOutput = append(imageOutput, params)
}
var out formats.Writer
switch outputFormat {
case formats.JSONString:
out = formats.JSONStructArray{Output: toGeneric(imageOutput)}
default:
if len(imageOutput) == 0 {
out = formats.StdoutTemplateArray{}
} else {
out = formats.StdoutTemplateArray{Output: toGeneric(imageOutput), Template: outputFormat, Fields: imageOutput[0].headerMap()}
}
}
formats.Writer(out).Out()
return nil
}
type imageOutputParams struct {
ID string `json:"id"`
Name []string `json:"names"`
Digest digest.Digest `json:"digest"`
CreatedAt string `json:"created"`
Size string `json:"size"`
}
func toGeneric(params []imageOutputParams) []interface{} {
genericParams := make([]interface{}, len(params))
for i, v := range params {
genericParams[i] = interface{}(v)
}
return genericParams
}
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] = strings.ToUpper(splitCamelCase(value))
}
return values
}