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/podman/formats" "github.com/projectatomic/libpod/libpod" "github.com/projectatomic/libpod/libpod/common" "github.com/projectatomic/libpod/pkg/inspect" "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) format := genImagesFormat(c.String("format"), 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("'podman 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") /* podman does not implement --all for images intermediate images are only generated during the build process. they are children to the image once built. until buildah supports caching builds, it will not generate these intermediate images. */ 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(format string, quiet, noHeading, digests bool) string { if format != "" { // "\t" from the command line is not being recognized as a tab // replacing the string "\t" to a tab character if the user passes in "\t" return strings.Replace(format, `\t`, "\t", -1) } 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 format } // 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 = shortID(img.ID) } repository := "" tag := "" 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 *inspect.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 *inspect.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 *inspect.ImageData) bool { if params == nil || params.SinceImage.IsZero() { return true } return info.Created.After(params.SinceImage) } case "dangling": return func(image *storage.Image, info *inspect.ImageData) bool { if params == nil || params.Dangling == "" { return true } if common.IsFalse(params.Dangling) && params.ImageName != "" { return true } if common.IsTrue(params.Dangling) && params.ImageName == "" { return true } return false } case "reference": return func(image *storage.Image, info *inspect.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 *inspect.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 } }