Merge pull request #653 from baude/images_json
cmd/kpod/images.go: Add JSON output option
This commit is contained in:
commit
4311020c36
6 changed files with 155 additions and 60 deletions
54
cmd/kpod/formats/formats.go
Normal file
54
cmd/kpod/formats/formats.go
Normal file
|
@ -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
|
||||||
|
|
||||||
|
}
|
|
@ -2,10 +2,8 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/containers/storage"
|
"github.com/containers/storage"
|
||||||
|
"github.com/kubernetes-incubator/cri-o/cmd/kpod/formats"
|
||||||
libkpodimage "github.com/kubernetes-incubator/cri-o/libkpod/image"
|
libkpodimage "github.com/kubernetes-incubator/cri-o/libkpod/image"
|
||||||
digest "github.com/opencontainers/go-digest"
|
digest "github.com/opencontainers/go-digest"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -13,11 +11,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type imageOutputParams struct {
|
type imageOutputParams struct {
|
||||||
ID string
|
ID string `json:"id"`
|
||||||
Name string
|
Name string `json:"names"`
|
||||||
Digest digest.Digest
|
Digest digest.Digest `json:"digest"`
|
||||||
CreatedAt string
|
CreatedAt string `json:"created"`
|
||||||
Size string
|
Size string `json:"size"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -40,7 +38,7 @@ var (
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "format",
|
Name: "format",
|
||||||
Usage: "pretty-print images using a Go template. will override --quiet",
|
Usage: "Change the output format.",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "filter, f",
|
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 {
|
func imagesCmd(c *cli.Context) error {
|
||||||
config, err := getConfig(c)
|
config, err := getConfig(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -85,11 +124,9 @@ func imagesCmd(c *cli.Context) error {
|
||||||
if c.IsSet("digests") {
|
if c.IsSet("digests") {
|
||||||
digests = c.Bool("digests")
|
digests = c.Bool("digests")
|
||||||
}
|
}
|
||||||
formatString := ""
|
outputFormat := ""
|
||||||
hasTemplate := false
|
|
||||||
if c.IsSet("format") {
|
if c.IsSet("format") {
|
||||||
formatString = c.String("format")
|
outputFormat = c.String("format")
|
||||||
hasTemplate = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
name := ""
|
name := ""
|
||||||
|
@ -113,11 +150,8 @@ func imagesCmd(c *cli.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "could not get list of images matching filter")
|
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) {
|
func outputHeader(truncate, digests bool) {
|
||||||
|
@ -134,7 +168,9 @@ func outputHeader(truncate, digests bool) {
|
||||||
fmt.Printf("%-22s %s\n", "CREATED AT", "SIZE")
|
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 {
|
for _, img := range images {
|
||||||
createdTime := img.Created
|
createdTime := img.Created
|
||||||
|
|
||||||
|
@ -148,12 +184,6 @@ func outputImages(store storage.Store, images []storage.Image, format string, ha
|
||||||
createdTime = info.Created
|
createdTime = info.Created
|
||||||
}
|
}
|
||||||
|
|
||||||
if quiet {
|
|
||||||
fmt.Printf("%-64s\n", img.ID)
|
|
||||||
// We only want to print each id once
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
params := imageOutputParams{
|
params := imageOutputParams{
|
||||||
ID: img.ID,
|
ID: img.ID,
|
||||||
Name: name,
|
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"),
|
CreatedAt: createdTime.Format("Jan 2, 2006 15:04"),
|
||||||
Size: libkpodimage.FormattedSize(size),
|
Size: libkpodimage.FormattedSize(size),
|
||||||
}
|
}
|
||||||
if hasTemplate {
|
imageOutput = append(imageOutput, params)
|
||||||
if err := outputUsingTemplate(format, params); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
outputUsingFormatString(truncate, digests, params)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func outputUsingTemplate(format string, params imageOutputParams) error {
|
var out formats.Writer
|
||||||
tmpl, err := template.New("image").Parse(format)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "Template parsing error")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tmpl.Execute(os.Stdout, params)
|
if outputFormat != "" {
|
||||||
if err != nil {
|
switch outputFormat {
|
||||||
return err
|
case "json":
|
||||||
}
|
out = formats.JSONstruct{Output: toGeneric(imageOutput)}
|
||||||
fmt.Println()
|
default:
|
||||||
return nil
|
// Assuming Go-template
|
||||||
}
|
out = formats.StdoutTemplate{Output: toGeneric(imageOutput), Template: outputFormat}
|
||||||
|
|
||||||
func outputUsingFormatString(truncate, digests bool, params imageOutputParams) {
|
}
|
||||||
if truncate {
|
|
||||||
fmt.Printf("%-20.12s %-56s", params.ID, params.Name)
|
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("%-64s %-56s", params.ID, params.Name)
|
out = stdoutstruct{output: imageOutput, digests: digests, truncate: truncate, quiet: quiet, noheading: noheading}
|
||||||
}
|
}
|
||||||
|
|
||||||
if digests {
|
formats.Writer(out).Out()
|
||||||
fmt.Printf(" %-64s", params.Digest)
|
|
||||||
}
|
return nil
|
||||||
fmt.Printf(" %-22s %s\n", params.CreatedAt, params.Size)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ _kpod_history() {
|
||||||
--human -H
|
--human -H
|
||||||
--no-trunc
|
--no-trunc
|
||||||
--quiet -q
|
--quiet -q
|
||||||
--json
|
|
||||||
"
|
"
|
||||||
_complete_ "$options_with_args" "$boolean_options"
|
_complete_ "$options_with_args" "$boolean_options"
|
||||||
|
|
||||||
|
@ -38,11 +37,11 @@ _kpod_images() {
|
||||||
-n
|
-n
|
||||||
--no-trunc
|
--no-trunc
|
||||||
--digests
|
--digests
|
||||||
--format
|
|
||||||
--filter
|
--filter
|
||||||
-f
|
-f
|
||||||
"
|
"
|
||||||
local options_with_args="
|
local options_with_args="
|
||||||
|
--format
|
||||||
"
|
"
|
||||||
|
|
||||||
local all_options="$options_with_args $boolean_options"
|
local all_options="$options_with_args $boolean_options"
|
||||||
|
|
|
@ -21,9 +21,10 @@ Show image digests
|
||||||
|
|
||||||
Filter output based on conditions provided (default [])
|
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**
|
**--noheading, -n**
|
||||||
|
|
||||||
|
@ -46,6 +47,10 @@ kpod images --quiet
|
||||||
|
|
||||||
kpod images -q --noheading --notruncate
|
kpod images -q --noheading --notruncate
|
||||||
|
|
||||||
|
kpod images --format json
|
||||||
|
|
||||||
|
kpod images --format "{{.ID}}"
|
||||||
|
|
||||||
## SEE ALSO
|
## SEE ALSO
|
||||||
kpod(1)
|
kpod(1)
|
||||||
|
|
||||||
|
|
1
libkpod/common/output_interfaces.go
Normal file
1
libkpod/common/output_interfaces.go
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package common
|
|
@ -201,6 +201,7 @@ function teardown() {
|
||||||
}
|
}
|
||||||
run ${KPOD_BINARY} $KPOD_OPTIONS rmi redis:alpine
|
run ${KPOD_BINARY} $KPOD_OPTIONS rmi redis:alpine
|
||||||
|
|
||||||
|
|
||||||
@test "kpod inspect non-existent container" {
|
@test "kpod inspect non-existent container" {
|
||||||
run ${KPOD_BINARY} $KPOD_OPTIONS inspect 14rcole/non-existent
|
run ${KPOD_BINARY} $KPOD_OPTIONS inspect 14rcole/non-existent
|
||||||
echo "$output"
|
echo "$output"
|
||||||
|
@ -227,3 +228,23 @@ function teardown() {
|
||||||
[ "$status" -eq 0]
|
[ "$status" -eq 0]
|
||||||
run ${KPOD_BINARY} $KPOD_OPTIONS rmi redis:alpine
|
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" ]
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue