diff --git a/cmd/kpod/formats/formats.go b/cmd/kpod/formats/formats.go new file mode 100644 index 00000000..690df642 --- /dev/null +++ b/cmd/kpod/formats/formats.go @@ -0,0 +1,54 @@ +package formats + +import ( + "encoding/json" + "fmt" + "github.com/pkg/errors" + "os" + "text/template" +) + +// Writer interface for outputs +type Writer interface { + Out() error +} + +// JSONstruct for JSON output +type JSONstruct struct { + Output []interface{} +} + +// StdoutTemplate for Go template output +type StdoutTemplate struct { + Output []interface{} + Template string +} + +// Out method for JSON +func (j JSONstruct) Out() error { + data, err := json.MarshalIndent(j.Output, "", " ") + if err != nil { + return err + } + fmt.Printf("%s\n", data) + return nil +} + +// Out method for Go templates +func (t StdoutTemplate) Out() error { + + tmpl, err := template.New("image").Parse(t.Template) + if err != nil { + return errors.Wrapf(err, "Template parsing error") + } + + for _, img := range t.Output { + err = tmpl.Execute(os.Stdout, img) + if err != nil { + return err + } + fmt.Println() + } + return nil + +} diff --git a/cmd/kpod/images.go b/cmd/kpod/images.go index 1c073748..9f211a98 100644 --- a/cmd/kpod/images.go +++ b/cmd/kpod/images.go @@ -2,10 +2,8 @@ package main import ( "fmt" - "os" - "text/template" - "github.com/containers/storage" + "github.com/kubernetes-incubator/cri-o/cmd/kpod/formats" libkpodimage "github.com/kubernetes-incubator/cri-o/libkpod/image" digest "github.com/opencontainers/go-digest" "github.com/pkg/errors" @@ -13,11 +11,11 @@ import ( ) type imageOutputParams struct { - ID string - Name string - Digest digest.Digest - CreatedAt string - Size string + ID string `json:"id"` + Name string `json:"names"` + Digest digest.Digest `json:"digest"` + CreatedAt string `json:"created"` + Size string `json:"size"` } var ( @@ -40,7 +38,7 @@ var ( }, cli.StringFlag{ Name: "format", - Usage: "pretty-print images using a Go template. will override --quiet", + Usage: "Change the output format.", }, cli.StringFlag{ Name: "filter, f", @@ -59,6 +57,47 @@ var ( } ) +type stdoutstruct struct { + output []imageOutputParams + truncate, digests, quiet, noheading bool +} + +func (so stdoutstruct) Out() error { + if len(so.output) > 0 && !so.noheading && !so.quiet { + outputHeader(so.truncate, so.digests) + } + lastID := "" + for _, img := range so.output { + if so.quiet { + if lastID == img.ID { + continue // quiet should not show the same ID multiple times. + } + fmt.Printf("%-64s\n", img.ID) + continue + } + if so.truncate { + fmt.Printf("%-20.12s %-56s", img.ID, img.Name) + } else { + fmt.Printf("%-64s %-56s", img.ID, img.Name) + } + + if so.digests { + fmt.Printf(" %-64s", img.Digest) + } + fmt.Printf(" %-22s %s\n", img.CreatedAt, img.Size) + + } + return nil +} + +func toGeneric(params []imageOutputParams) []interface{} { + genericParams := make([]interface{}, len(params)) + for i, v := range params { + genericParams[i] = interface{}(v) + } + return genericParams +} + func imagesCmd(c *cli.Context) error { config, err := getConfig(c) if err != nil { @@ -85,11 +124,9 @@ func imagesCmd(c *cli.Context) error { if c.IsSet("digests") { digests = c.Bool("digests") } - formatString := "" - hasTemplate := false + outputFormat := "" if c.IsSet("format") { - formatString = c.String("format") - hasTemplate = true + outputFormat = c.String("format") } name := "" @@ -113,11 +150,8 @@ func imagesCmd(c *cli.Context) error { if err != nil { return errors.Wrapf(err, "could not get list of images matching filter") } - if len(imageList) > 0 && !noheading && !quiet && !hasTemplate { - outputHeader(truncate, digests) - } - return outputImages(store, imageList, formatString, hasTemplate, truncate, digests, quiet) + return outputImages(store, imageList, truncate, digests, quiet, outputFormat, noheading) } func outputHeader(truncate, digests bool) { @@ -134,7 +168,9 @@ func outputHeader(truncate, digests bool) { fmt.Printf("%-22s %s\n", "CREATED AT", "SIZE") } -func outputImages(store storage.Store, images []storage.Image, format string, hasTemplate, truncate, digests, quiet bool) error { +func outputImages(store storage.Store, images []storage.Image, truncate, digests, quiet bool, outputFormat string, noheading bool) error { + imageOutput := []imageOutputParams{} + for _, img := range images { createdTime := img.Created @@ -148,12 +184,6 @@ func outputImages(store storage.Store, images []storage.Image, format string, ha createdTime = info.Created } - if quiet { - fmt.Printf("%-64s\n", img.ID) - // We only want to print each id once - continue - } - params := imageOutputParams{ ID: img.ID, Name: name, @@ -161,40 +191,25 @@ func outputImages(store storage.Store, images []storage.Image, format string, ha CreatedAt: createdTime.Format("Jan 2, 2006 15:04"), Size: libkpodimage.FormattedSize(size), } - if hasTemplate { - if err := outputUsingTemplate(format, params); err != nil { - return err - } - continue + imageOutput = append(imageOutput, params) + } + + var out formats.Writer + + if outputFormat != "" { + switch outputFormat { + case "json": + out = formats.JSONstruct{Output: toGeneric(imageOutput)} + default: + // Assuming Go-template + out = formats.StdoutTemplate{Output: toGeneric(imageOutput), Template: outputFormat} + } - outputUsingFormatString(truncate, digests, params) - } - return nil -} - -func outputUsingTemplate(format string, params imageOutputParams) error { - tmpl, err := template.New("image").Parse(format) - if err != nil { - return errors.Wrapf(err, "Template parsing error") - } - - err = tmpl.Execute(os.Stdout, params) - if err != nil { - return err - } - fmt.Println() - return nil -} - -func outputUsingFormatString(truncate, digests bool, params imageOutputParams) { - if truncate { - fmt.Printf("%-20.12s %-56s", params.ID, params.Name) } else { - fmt.Printf("%-64s %-56s", params.ID, params.Name) + out = stdoutstruct{output: imageOutput, digests: digests, truncate: truncate, quiet: quiet, noheading: noheading} } - if digests { - fmt.Printf(" %-64s", params.Digest) - } - fmt.Printf(" %-22s %s\n", params.CreatedAt, params.Size) + formats.Writer(out).Out() + + return nil } diff --git a/completions/bash/kpod b/completions/bash/kpod index 9bb7e0c1..9597bc6c 100644 --- a/completions/bash/kpod +++ b/completions/bash/kpod @@ -14,7 +14,6 @@ _kpod_history() { --human -H --no-trunc --quiet -q - --json " _complete_ "$options_with_args" "$boolean_options" @@ -38,11 +37,11 @@ _kpod_images() { -n --no-trunc --digests - --format --filter -f " local options_with_args=" + --format " local all_options="$options_with_args $boolean_options" diff --git a/docs/kpod-images.1.md b/docs/kpod-images.1.md index 1bec7fc2..26b743c6 100644 --- a/docs/kpod-images.1.md +++ b/docs/kpod-images.1.md @@ -21,9 +21,10 @@ Show image digests Filter output based on conditions provided (default []) -**--format="TEMPLATE"** +**--format** -Pretty-print images using a Go template. Will override --quiet +Change the default output format. This can be of a supported type like 'json' +or a Go template. **--noheading, -n** @@ -46,6 +47,10 @@ kpod images --quiet kpod images -q --noheading --notruncate +kpod images --format json + +kpod images --format "{{.ID}}" + ## SEE ALSO kpod(1) diff --git a/libkpod/common/output_interfaces.go b/libkpod/common/output_interfaces.go new file mode 100644 index 00000000..805d0c79 --- /dev/null +++ b/libkpod/common/output_interfaces.go @@ -0,0 +1 @@ +package common diff --git a/test/kpod.bats b/test/kpod.bats index 4f6e6ae9..c00a8346 100644 --- a/test/kpod.bats +++ b/test/kpod.bats @@ -201,6 +201,7 @@ function teardown() { } run ${KPOD_BINARY} $KPOD_OPTIONS rmi redis:alpine + @test "kpod inspect non-existent container" { run ${KPOD_BINARY} $KPOD_OPTIONS inspect 14rcole/non-existent echo "$output" @@ -227,3 +228,23 @@ function teardown() { [ "$status" -eq 0] run ${KPOD_BINARY} $KPOD_OPTIONS rmi redis:alpine } + +@test "kpod images" { + run ${KPOD_BINARY} $KPOD_OPTIONS pull debian:6.0.10 + run ${KPOD_BINARY} $KPOD_OPTIONS images + [ "$status" -eq 0 ] +} + +@test "kpod images test valid json" { + run ${KPOD_BINARY} $KPOD_OPTIONS pull debian:6.0.10 + run ${KPOD_BINARY} $KPOD_OPTIONS images --format json + echo "$output" | python -m json.tool + [ "$status" -eq 0 ] +} + +@test "kpod images check name json output" { + run ${KPOD_BINARY} $KPOD_OPTIONS pull debian:6.0.10 + run ${KPOD_BINARY} $KPOD_OPTIONS images --format json + name=$(echo $output | python -c 'import sys; import json; print(json.loads(sys.stdin.read())[0])["names"][0]') + [ "$name" == "docker.io/library/debian:6.0.10" ] +}