mirror of
https://github.com/containers/podman.git
synced 2025-06-01 17:17:47 +08:00

kpod inspect now uses the new libpod container state and closely matches the output of docker inspect some aspects of it are still WIP as the libpod container state is still being worked on Signed-off-by: umohnani8 <umohnani@redhat.com>
338 lines
8.8 KiB
Go
338 lines
8.8 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/containers/storage"
|
|
"github.com/docker/go-units"
|
|
digest "github.com/opencontainers/go-digest"
|
|
"github.com/pkg/errors"
|
|
"github.com/projectatomic/libpod/cmd/kpod/formats"
|
|
"github.com/projectatomic/libpod/libpod"
|
|
"github.com/projectatomic/libpod/libpod/common"
|
|
"github.com/urfave/cli"
|
|
)
|
|
|
|
type imagesTemplateParams struct {
|
|
Repository string
|
|
Tag string
|
|
ID string
|
|
Digest digest.Digest
|
|
Created string
|
|
Size string
|
|
}
|
|
|
|
type imagesJSONParams struct {
|
|
ID string `json:"id"`
|
|
Name []string `json:"names"`
|
|
Digest digest.Digest `json:"digest"`
|
|
Created time.Time `json:"created"`
|
|
Size int64 `json:"size"`
|
|
}
|
|
|
|
type imagesOptions struct {
|
|
quiet bool
|
|
noHeading bool
|
|
noTrunc bool
|
|
digests bool
|
|
format string
|
|
}
|
|
|
|
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: "",
|
|
UseShortOptionHandling: true,
|
|
}
|
|
)
|
|
|
|
func imagesCmd(c *cli.Context) error {
|
|
if err := validateFlags(c, imagesFlags); err != nil {
|
|
return err
|
|
}
|
|
|
|
runtime, err := getRuntime(c)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "Could not get runtime")
|
|
}
|
|
defer runtime.Shutdown(false)
|
|
|
|
var format string
|
|
if c.IsSet("format") {
|
|
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,
|
|
}
|
|
|
|
var imageInput string
|
|
if len(c.Args()) == 1 {
|
|
imageInput = c.Args().Get(0)
|
|
}
|
|
if len(c.Args()) > 1 {
|
|
return errors.New("'kpod images' requires at most 1 argument")
|
|
}
|
|
|
|
params, err := runtime.ParseImageFilter(imageInput, c.String("filter"))
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error parsing filter")
|
|
}
|
|
|
|
// 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)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "could not get list of images matching filter")
|
|
}
|
|
|
|
return generateImagesOutput(runtime, images, opts)
|
|
}
|
|
|
|
func genImagesFormat(quiet, noHeading, digests bool) (format string) {
|
|
if quiet {
|
|
return formats.IDString
|
|
}
|
|
format = "table {{.Repository}}\t{{.Tag}}\t"
|
|
if noHeading {
|
|
format = "{{.Repository}}\t{{.Tag}}\t"
|
|
}
|
|
if digests {
|
|
format += "{{.Digest}}\t"
|
|
}
|
|
format += "{{.ID}}\t{{.Created}}\t{{.Size}}\t"
|
|
return
|
|
}
|
|
|
|
// 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))
|
|
}
|
|
return
|
|
}
|
|
|
|
// 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 = "Image" + value
|
|
}
|
|
values[key] = strings.ToUpper(splitCamelCase(value))
|
|
}
|
|
return values
|
|
}
|
|
|
|
// 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
|
|
)
|
|
for _, img := range images {
|
|
if opts.quiet && lastID == img.ID {
|
|
continue // quiet should not show the same ID multiple times
|
|
}
|
|
createdTime := img.Created
|
|
|
|
imageID := "sha256:" + img.ID
|
|
if !opts.noTrunc {
|
|
imageID = img.ID[:idTruncLength]
|
|
}
|
|
|
|
repository := "<none>"
|
|
tag := "<none>"
|
|
if len(img.Names) > 0 {
|
|
arr := strings.Split(img.Names[0], ":")
|
|
repository = arr[0]
|
|
if len(arr) == 2 {
|
|
tag = arr[1]
|
|
}
|
|
}
|
|
|
|
imgData, _ := runtime.GetImageInspectInfo(*img)
|
|
if imgData != nil {
|
|
createdTime = *imgData.Created
|
|
}
|
|
|
|
params := imagesTemplateParams{
|
|
Repository: repository,
|
|
Tag: tag,
|
|
ID: imageID,
|
|
Digest: imgData.Digest,
|
|
Created: units.HumanDuration(time.Since((createdTime))) + " ago",
|
|
Size: units.HumanSizeWithPrecision(float64(imgData.Size), 3),
|
|
}
|
|
imagesOutput = append(imagesOutput, params)
|
|
}
|
|
return
|
|
}
|
|
|
|
// 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
|
|
|
|
imgData, _ := runtime.GetImageInspectInfo(*img)
|
|
if imgData != nil {
|
|
createdTime = *imgData.Created
|
|
}
|
|
|
|
params := imagesJSONParams{
|
|
ID: img.ID,
|
|
Name: img.Names,
|
|
Digest: imgData.Digest,
|
|
Created: createdTime,
|
|
Size: imgData.Size,
|
|
}
|
|
imagesOutput = append(imagesOutput, params)
|
|
}
|
|
return
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
var out formats.Writer
|
|
|
|
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()}
|
|
|
|
}
|
|
|
|
return formats.Writer(out).Out()
|
|
}
|
|
|
|
// 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 *libpod.ImageData) bool {
|
|
if params == nil || params.Label == "" {
|
|
return true
|
|
}
|
|
|
|
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
|
|
}
|
|
case "before-image":
|
|
return func(image *storage.Image, info *libpod.ImageData) bool {
|
|
if params == nil || params.BeforeImage.IsZero() {
|
|
return true
|
|
}
|
|
return info.Created.Before(params.BeforeImage)
|
|
}
|
|
case "since-image":
|
|
return func(image *storage.Image, info *libpod.ImageData) bool {
|
|
if params == nil || params.SinceImage.IsZero() {
|
|
return true
|
|
}
|
|
return info.Created.After(params.SinceImage)
|
|
}
|
|
case "dangling":
|
|
return func(image *storage.Image, info *libpod.ImageData) 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 *libpod.ImageData) bool {
|
|
if params == nil || params.ReferencePattern == "" {
|
|
return true
|
|
}
|
|
return libpod.MatchesReference(params.ImageName, params.ReferencePattern)
|
|
}
|
|
case "image-input":
|
|
return func(image *storage.Image, info *libpod.ImageData) bool {
|
|
if params == nil || params.ImageInput == "" {
|
|
return true
|
|
}
|
|
return libpod.MatchesReference(params.ImageName, params.ImageInput)
|
|
}
|
|
default:
|
|
fmt.Println("invalid filter type", filterType)
|
|
return nil
|
|
}
|
|
}
|