2017-06-16 17:24:00 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2017-09-21 19:21:56 +00:00
|
|
|
"fmt"
|
2017-08-15 20:53:17 +00:00
|
|
|
"reflect"
|
|
|
|
"strings"
|
2017-09-21 19:21:56 +00:00
|
|
|
"time"
|
2017-08-15 20:53:17 +00:00
|
|
|
|
2017-09-21 19:21:56 +00:00
|
|
|
"github.com/containers/image/types"
|
2017-06-16 17:24:00 +00:00
|
|
|
"github.com/containers/storage"
|
2017-09-21 19:21:56 +00:00
|
|
|
"github.com/docker/go-units"
|
2017-07-31 18:02:57 +00:00
|
|
|
"github.com/kubernetes-incubator/cri-o/cmd/kpod/formats"
|
2017-09-21 19:21:56 +00:00
|
|
|
"github.com/kubernetes-incubator/cri-o/libpod"
|
|
|
|
"github.com/kubernetes-incubator/cri-o/libpod/common"
|
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"
|
|
|
|
)
|
|
|
|
|
2017-09-21 19:21:56 +00:00
|
|
|
type imagesTemplateParams struct {
|
|
|
|
ID string
|
|
|
|
Name string
|
|
|
|
Digest digest.Digest
|
|
|
|
CreatedAt string
|
|
|
|
Size string
|
|
|
|
}
|
|
|
|
|
|
|
|
type imagesJSONParams struct {
|
|
|
|
ID string `json:"id"`
|
|
|
|
Name []string `json:"names"`
|
|
|
|
Digest digest.Digest `json:"digest"`
|
|
|
|
CreatedAt time.Time `json:"created"`
|
|
|
|
Size int64 `json:"size"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type imagesOptions struct {
|
|
|
|
quiet bool
|
|
|
|
noHeading bool
|
|
|
|
noTrunc bool
|
|
|
|
digests bool
|
|
|
|
format string
|
|
|
|
}
|
|
|
|
|
2017-06-16 17:24:00 +00:00
|
|
|
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-08-14 19:32:00 +00:00
|
|
|
Usage: "Change the output format to JSON or a Go template",
|
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-09-28 18:44:48 +00:00
|
|
|
if err := validateFlags(c, imagesFlags); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-09-21 19:21:56 +00:00
|
|
|
|
|
|
|
runtime, err := getRuntime(c)
|
2017-06-16 17:24:00 +00:00
|
|
|
if err != nil {
|
2017-09-21 19:21:56 +00:00
|
|
|
return errors.Wrapf(err, "Could not get runtime")
|
2017-06-16 17:24:00 +00:00
|
|
|
}
|
2017-09-21 19:21:56 +00:00
|
|
|
defer runtime.Shutdown(false)
|
2017-06-16 17:24:00 +00:00
|
|
|
|
2017-09-21 19:21:56 +00:00
|
|
|
var format string
|
2017-06-16 17:24:00 +00:00
|
|
|
if c.IsSet("format") {
|
2017-09-21 19:21:56 +00:00
|
|
|
format = c.String("format")
|
|
|
|
} else {
|
|
|
|
format = genImagesFormat(c.Bool("quiet"), c.Bool("noheading"), c.Bool("digests"))
|
|
|
|
}
|
|
|
|
|
|
|
|
opts := imagesOptions{
|
|
|
|
quiet: c.Bool("quiet"),
|
|
|
|
noHeading: c.Bool("noheading"),
|
|
|
|
noTrunc: c.Bool("no-trunc"),
|
|
|
|
digests: c.Bool("digests"),
|
|
|
|
format: format,
|
2017-06-16 17:24:00 +00:00
|
|
|
}
|
|
|
|
|
2017-09-21 19:21:56 +00:00
|
|
|
var imageInput string
|
2017-06-16 17:24:00 +00:00
|
|
|
if len(c.Args()) == 1 {
|
2017-09-21 19:21:56 +00:00
|
|
|
imageInput = c.Args().Get(0)
|
|
|
|
}
|
|
|
|
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-09-21 19:21:56 +00:00
|
|
|
params, err := runtime.ParseImageFilter(imageInput, c.String("filter"))
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error parsing filter")
|
2017-06-16 17:24:00 +00:00
|
|
|
}
|
|
|
|
|
2017-09-21 19:21:56 +00:00
|
|
|
// generate the different filters
|
|
|
|
labelFilter := generateImagesFilter(params, "label")
|
|
|
|
beforeImageFilter := generateImagesFilter(params, "before-image")
|
|
|
|
sinceImageFilter := generateImagesFilter(params, "since-image")
|
|
|
|
danglingFilter := generateImagesFilter(params, "dangling")
|
|
|
|
referenceFilter := generateImagesFilter(params, "reference")
|
|
|
|
imageInputFilter := generateImagesFilter(params, "image-input")
|
|
|
|
|
|
|
|
images, err := runtime.GetImages(params, labelFilter, beforeImageFilter, sinceImageFilter, danglingFilter, referenceFilter, imageInputFilter)
|
2017-07-24 01:38:03 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "could not get list of images matching filter")
|
2017-06-16 17:24:00 +00:00
|
|
|
}
|
|
|
|
|
2017-09-21 19:21:56 +00:00
|
|
|
return generateImagesOutput(runtime, images, opts)
|
2017-06-16 17:24:00 +00:00
|
|
|
}
|
|
|
|
|
2017-09-21 19:21:56 +00:00
|
|
|
func genImagesFormat(quiet, noHeading, digests bool) (format string) {
|
2017-08-16 02:23:40 +00:00
|
|
|
if quiet {
|
2017-08-08 13:24:58 +00:00
|
|
|
return formats.IDString
|
2017-08-16 02:23:40 +00:00
|
|
|
}
|
2017-09-21 19:21:56 +00:00
|
|
|
format = "table {{.ID}}\t{{.Name}}\t"
|
|
|
|
if noHeading {
|
|
|
|
format = "{{.ID}}\t{{.Name}}\t"
|
2017-06-16 17:24:00 +00:00
|
|
|
}
|
|
|
|
if digests {
|
2017-09-21 19:21:56 +00:00
|
|
|
format += "{{.Digest}}\t"
|
2017-06-16 17:24:00 +00:00
|
|
|
}
|
2017-09-21 19:21:56 +00:00
|
|
|
format += "{{.CreatedAt}}\t{{.Size}}\t"
|
|
|
|
return
|
|
|
|
}
|
2017-06-16 17:24:00 +00:00
|
|
|
|
2017-09-21 19:21:56 +00:00
|
|
|
// imagesToGeneric creates an empty array of interfaces for output
|
|
|
|
func imagesToGeneric(templParams []imagesTemplateParams, JSONParams []imagesJSONParams) (genericParams []interface{}) {
|
|
|
|
if len(templParams) > 0 {
|
|
|
|
for _, v := range templParams {
|
|
|
|
genericParams = append(genericParams, interface{}(v))
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
for _, v := range JSONParams {
|
|
|
|
genericParams = append(genericParams, interface{}(v))
|
|
|
|
}
|
2017-08-16 02:23:40 +00:00
|
|
|
return
|
2017-06-16 17:24:00 +00:00
|
|
|
}
|
|
|
|
|
2017-09-21 19:21:56 +00:00
|
|
|
// generate the header based on the template provided
|
|
|
|
func (i *imagesTemplateParams) 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
|
|
|
|
}
|
2017-07-31 18:02:57 +00:00
|
|
|
|
2017-09-21 19:21:56 +00:00
|
|
|
// getImagesTemplateOutput returns the images information to be printed in human readable format
|
|
|
|
func getImagesTemplateOutput(runtime *libpod.Runtime, images []*storage.Image, opts imagesOptions) (imagesOutput []imagesTemplateParams) {
|
|
|
|
var (
|
|
|
|
lastID string
|
|
|
|
)
|
2017-07-21 20:43:30 +00:00
|
|
|
for _, img := range images {
|
2017-09-21 19:21:56 +00:00
|
|
|
if opts.quiet && lastID == img.ID {
|
2017-08-16 02:23:40 +00:00
|
|
|
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
|
|
|
|
2017-09-21 19:21:56 +00:00
|
|
|
imageID := img.ID
|
|
|
|
if !opts.noTrunc {
|
|
|
|
imageID = imageID[:idTruncLength]
|
|
|
|
}
|
|
|
|
|
|
|
|
imageName := "<none>"
|
2017-07-25 19:33:41 +00:00
|
|
|
if len(img.Names) > 0 {
|
2017-09-21 19:21:56 +00:00
|
|
|
imageName = img.Names[0]
|
2017-06-16 17:24:00 +00:00
|
|
|
}
|
2017-07-25 19:33:41 +00:00
|
|
|
|
2017-09-21 19:21:56 +00:00
|
|
|
info, imageDigest, size, _ := runtime.InfoAndDigestAndSize(*img)
|
2017-07-27 17:35:38 +00:00
|
|
|
if info != nil {
|
|
|
|
createdTime = info.Created
|
|
|
|
}
|
2017-06-16 17:24:00 +00:00
|
|
|
|
2017-09-21 19:21:56 +00:00
|
|
|
params := imagesTemplateParams{
|
|
|
|
ID: imageID,
|
|
|
|
Name: imageName,
|
2017-08-16 02:23:40 +00:00
|
|
|
Digest: imageDigest,
|
2017-09-21 19:21:56 +00:00
|
|
|
CreatedAt: units.HumanDuration(time.Since((createdTime))) + " ago",
|
|
|
|
Size: units.HumanSize(float64(size)),
|
2017-06-16 17:24:00 +00:00
|
|
|
}
|
2017-09-21 19:21:56 +00:00
|
|
|
imagesOutput = append(imagesOutput, params)
|
2017-06-16 17:24:00 +00:00
|
|
|
}
|
2017-09-21 19:21:56 +00:00
|
|
|
return
|
|
|
|
}
|
2017-06-16 17:24:00 +00:00
|
|
|
|
2017-09-21 19:21:56 +00:00
|
|
|
// getImagesJSONOutput returns the images information in its raw form
|
|
|
|
func getImagesJSONOutput(runtime *libpod.Runtime, images []*storage.Image) (imagesOutput []imagesJSONParams) {
|
|
|
|
for _, img := range images {
|
|
|
|
createdTime := img.Created
|
2017-06-16 17:24:00 +00:00
|
|
|
|
2017-09-21 19:21:56 +00:00
|
|
|
info, imageDigest, size, _ := runtime.InfoAndDigestAndSize(*img)
|
|
|
|
if info != nil {
|
|
|
|
createdTime = info.Created
|
|
|
|
}
|
|
|
|
|
|
|
|
params := imagesJSONParams{
|
|
|
|
ID: img.ID,
|
|
|
|
Name: img.Names,
|
|
|
|
Digest: imageDigest,
|
|
|
|
CreatedAt: createdTime,
|
|
|
|
Size: size,
|
2017-08-22 18:15:58 +00:00
|
|
|
}
|
2017-09-21 19:21:56 +00:00
|
|
|
imagesOutput = append(imagesOutput, params)
|
2017-06-16 17:24:00 +00:00
|
|
|
}
|
2017-09-21 19:21:56 +00:00
|
|
|
return
|
|
|
|
}
|
2017-06-16 17:24:00 +00:00
|
|
|
|
2017-09-21 19:21:56 +00:00
|
|
|
// generateImagesOutput generates the images based on the format provided
|
|
|
|
func generateImagesOutput(runtime *libpod.Runtime, images []*storage.Image, opts imagesOptions) error {
|
|
|
|
if len(images) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
2017-07-31 18:02:57 +00:00
|
|
|
|
2017-09-21 19:21:56 +00:00
|
|
|
var out formats.Writer
|
2017-08-15 20:53:17 +00:00
|
|
|
|
2017-09-21 19:21:56 +00:00
|
|
|
switch opts.format {
|
|
|
|
case formats.JSONString:
|
|
|
|
imagesOutput := getImagesJSONOutput(runtime, images)
|
|
|
|
out = formats.JSONStructArray{Output: imagesToGeneric([]imagesTemplateParams{}, imagesOutput)}
|
|
|
|
default:
|
|
|
|
imagesOutput := getImagesTemplateOutput(runtime, images, opts)
|
|
|
|
out = formats.StdoutTemplateArray{Output: imagesToGeneric(imagesOutput, []imagesJSONParams{}), Template: opts.format, Fields: imagesOutput[0].headerMap()}
|
2017-08-15 20:53:17 +00:00
|
|
|
|
2017-08-16 02:23:40 +00:00
|
|
|
}
|
2017-09-21 19:21:56 +00:00
|
|
|
|
|
|
|
return formats.Writer(out).Out()
|
2017-08-16 02:23:40 +00:00
|
|
|
}
|
|
|
|
|
2017-09-21 19:21:56 +00:00
|
|
|
// generateImagesFilter returns an ImageFilter based on filterType
|
|
|
|
// to add more filters, define a new case and write what the ImageFilter function should do
|
|
|
|
func generateImagesFilter(params *libpod.ImageFilterParams, filterType string) libpod.ImageFilter {
|
|
|
|
switch filterType {
|
|
|
|
case "label":
|
|
|
|
return func(image *storage.Image, info *types.ImageInspectInfo) bool {
|
|
|
|
if params == nil || params.Label == "" {
|
|
|
|
return true
|
|
|
|
}
|
2017-08-15 20:53:17 +00:00
|
|
|
|
2017-09-21 19:21:56 +00:00
|
|
|
pair := strings.SplitN(params.Label, "=", 2)
|
|
|
|
if val, ok := info.Labels[pair[0]]; ok {
|
|
|
|
if len(pair) == 2 && val == pair[1] {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if len(pair) == 1 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
2017-08-15 20:53:17 +00:00
|
|
|
}
|
2017-09-21 19:21:56 +00:00
|
|
|
case "before-image":
|
|
|
|
return func(image *storage.Image, info *types.ImageInspectInfo) bool {
|
|
|
|
if params == nil || params.BeforeImage.IsZero() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return info.Created.Before(params.BeforeImage)
|
|
|
|
}
|
|
|
|
case "since-image":
|
|
|
|
return func(image *storage.Image, info *types.ImageInspectInfo) bool {
|
|
|
|
if params == nil || params.SinceImage.IsZero() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return info.Created.After(params.SinceImage)
|
|
|
|
}
|
|
|
|
case "dangling":
|
|
|
|
return func(image *storage.Image, info *types.ImageInspectInfo) bool {
|
|
|
|
if params == nil || params.Dangling == "" {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if common.IsFalse(params.Dangling) && params.ImageName != "<none>" {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if common.IsTrue(params.Dangling) && params.ImageName == "<none>" {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
case "reference":
|
|
|
|
return func(image *storage.Image, info *types.ImageInspectInfo) bool {
|
|
|
|
if params == nil || params.ReferencePattern == "" {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return libpod.MatchesReference(params.ImageName, params.ReferencePattern)
|
|
|
|
}
|
|
|
|
case "image-input":
|
|
|
|
return func(image *storage.Image, info *types.ImageInspectInfo) bool {
|
|
|
|
if params == nil || params.ImageInput == "" {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return libpod.MatchesReference(params.ImageName, params.ImageInput)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
fmt.Println("invalid filter type", filterType)
|
|
|
|
return nil
|
2017-08-15 20:53:17 +00:00
|
|
|
}
|
|
|
|
}
|