04e96d05fc
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>
209 lines
4.8 KiB
Go
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
|
|
}
|