Skip to content

Commit 8c496a1

Browse files
author
Mrunal Patel
authored
Merge pull request cri-o#773 from 14rcole/kpod-format-table
Kpod format
2 parents 6ca462a + ba07bfb commit 8c496a1

File tree

10 files changed

+331
-1104
lines changed

10 files changed

+331
-1104
lines changed

cmd/kpod/common.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package main
22

33
import (
4+
"strings"
5+
46
is "github.com/containers/image/storage"
57
"github.com/containers/storage"
8+
"github.com/fatih/camelcase"
69
"github.com/kubernetes-incubator/cri-o/libkpod"
710
"github.com/sirupsen/logrus"
811
"github.com/urfave/cli"
@@ -53,3 +56,8 @@ func getConfig(c *cli.Context) (*libkpod.Config, error) {
5356

5457
return config, nil
5558
}
59+
60+
func splitCamelCase(src string) string {
61+
entries := camelcase.Split(src)
62+
return strings.Join(entries, " ")
63+
}

cmd/kpod/formats/formats.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ package formats
33
import (
44
"encoding/json"
55
"fmt"
6-
"github.com/pkg/errors"
76
"os"
7+
"strings"
88
"text/template"
9+
10+
"github.com/pkg/errors"
911
)
1012

1113
// Writer interface for outputs
@@ -22,6 +24,7 @@ type JSONstruct struct {
2224
type StdoutTemplate struct {
2325
Output []interface{}
2426
Template string
27+
Fields map[string]string
2528
}
2629

2730
// Out method for JSON
@@ -36,14 +39,26 @@ func (j JSONstruct) Out() error {
3639

3740
// Out method for Go templates
3841
func (t StdoutTemplate) Out() error {
39-
40-
tmpl, err := template.New("image").Parse(t.Template)
42+
if strings.HasPrefix(t.Template, "table") {
43+
t.Template = strings.TrimSpace(t.Template[5:])
44+
headerTmpl, err := template.New("header").Funcs(headerFunctions).Parse(t.Template)
45+
if err != nil {
46+
return errors.Wrapf(err, "Template parsing error")
47+
}
48+
err = headerTmpl.Execute(os.Stdout, t.Fields)
49+
if err != nil {
50+
return err
51+
}
52+
fmt.Println()
53+
}
54+
tmpl, err := template.New("image").Funcs(basicFunctions).Parse(t.Template)
4155
if err != nil {
4256
return errors.Wrapf(err, "Template parsing error")
4357
}
4458

4559
for _, img := range t.Output {
46-
err = tmpl.Execute(os.Stdout, img)
60+
basicTmpl := tmpl.Funcs(basicFunctions)
61+
err = basicTmpl.Execute(os.Stdout, img)
4762
if err != nil {
4863
return err
4964
}

cmd/kpod/formats/templates.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package formats
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"strings"
7+
"text/template"
8+
)
9+
10+
// basicFunctions are the set of initial
11+
// functions provided to every template.
12+
var basicFunctions = template.FuncMap{
13+
"json": func(v interface{}) string {
14+
buf := &bytes.Buffer{}
15+
enc := json.NewEncoder(buf)
16+
enc.SetEscapeHTML(false)
17+
_ = enc.Encode(v)
18+
// Remove the trailing new line added by the encoder
19+
return strings.TrimSpace(buf.String())
20+
},
21+
"split": strings.Split,
22+
"join": strings.Join,
23+
"title": strings.Title,
24+
"lower": strings.ToLower,
25+
"upper": strings.ToUpper,
26+
"pad": padWithSpace,
27+
"truncate": truncateWithLength,
28+
}
29+
30+
// HeaderFunctions are used to created headers of a table.
31+
// This is a replacement of basicFunctions for header generation
32+
// because we want the header to remain intact.
33+
// Some functions like `split` are irrevelant so not added.
34+
var headerFunctions = template.FuncMap{
35+
"json": func(v string) string {
36+
return v
37+
},
38+
"title": func(v string) string {
39+
return v
40+
},
41+
"lower": func(v string) string {
42+
return v
43+
},
44+
"upper": func(v string) string {
45+
return v
46+
},
47+
"truncate": func(v string, l int) string {
48+
return v
49+
},
50+
}
51+
52+
// Parse creates a new anonymous template with the basic functions
53+
// and parses the given format.
54+
func Parse(format string) (*template.Template, error) {
55+
return NewParse("", format)
56+
}
57+
58+
// NewParse creates a new tagged template with the basic functions
59+
// and parses the given format.
60+
func NewParse(tag, format string) (*template.Template, error) {
61+
return template.New(tag).Funcs(basicFunctions).Parse(format)
62+
}
63+
64+
// padWithSpace adds whitespace to the input if the input is non-empty
65+
func padWithSpace(source string, prefix, suffix int) string {
66+
if source == "" {
67+
return source
68+
}
69+
return strings.Repeat(" ", prefix) + source + strings.Repeat(" ", suffix)
70+
}
71+
72+
// truncateWithLength truncates the source string up to the length provided by the input
73+
func truncateWithLength(source string, length int) string {
74+
if len(source) < length {
75+
return source
76+
}
77+
return source[:length]
78+
}

cmd/kpod/images.go

Lines changed: 57 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package main
22

33
import (
44
"fmt"
5+
"reflect"
6+
"strings"
7+
58
"github.com/containers/storage"
69
"github.com/kubernetes-incubator/cri-o/cmd/kpod/formats"
710
libkpodimage "github.com/kubernetes-incubator/cri-o/libkpod/image"
@@ -10,14 +13,6 @@ import (
1013
"github.com/urfave/cli"
1114
)
1215

13-
type imageOutputParams struct {
14-
ID string `json:"id"`
15-
Name string `json:"names"`
16-
Digest digest.Digest `json:"digest"`
17-
CreatedAt string `json:"created"`
18-
Size string `json:"size"`
19-
}
20-
2116
var (
2217
imagesFlags = []cli.Flag{
2318
cli.BoolFlag{
@@ -57,47 +52,6 @@ var (
5752
}
5853
)
5954

60-
type stdoutstruct struct {
61-
output []imageOutputParams
62-
truncate, digests, quiet, noheading bool
63-
}
64-
65-
func (so stdoutstruct) Out() error {
66-
if len(so.output) > 0 && !so.noheading && !so.quiet {
67-
outputHeader(so.truncate, so.digests)
68-
}
69-
lastID := ""
70-
for _, img := range so.output {
71-
if so.quiet {
72-
if lastID == img.ID {
73-
continue // quiet should not show the same ID multiple times.
74-
}
75-
fmt.Printf("%-64s\n", img.ID)
76-
continue
77-
}
78-
if so.truncate {
79-
fmt.Printf("%-20.12s %-56s", img.ID, img.Name)
80-
} else {
81-
fmt.Printf("%-64s %-56s", img.ID, img.Name)
82-
}
83-
84-
if so.digests {
85-
fmt.Printf(" %-64s", img.Digest)
86-
}
87-
fmt.Printf(" %-22s %s\n", img.CreatedAt, img.Size)
88-
89-
}
90-
return nil
91-
}
92-
93-
func toGeneric(params []imageOutputParams) []interface{} {
94-
genericParams := make([]interface{}, len(params))
95-
for i, v := range params {
96-
genericParams[i] = interface{}(v)
97-
}
98-
return genericParams
99-
}
100-
10155
func imagesCmd(c *cli.Context) error {
10256
config, err := getConfig(c)
10357
if err != nil {
@@ -124,7 +78,7 @@ func imagesCmd(c *cli.Context) error {
12478
if c.IsSet("digests") {
12579
digests = c.Bool("digests")
12680
}
127-
outputFormat := ""
81+
outputFormat := genImagesFormat(quiet, truncate, digests)
12882
if c.IsSet("format") {
12983
outputFormat = c.String("format")
13084
}
@@ -133,7 +87,7 @@ func imagesCmd(c *cli.Context) error {
13387
if len(c.Args()) == 1 {
13488
name = c.Args().Get(0)
13589
} else if len(c.Args()) > 1 {
136-
return errors.New("'buildah images' requires at most 1 argument")
90+
return errors.New("'kpod images' requires at most 1 argument")
13791
}
13892

13993
var params *libkpodimage.FilterParams
@@ -154,40 +108,49 @@ func imagesCmd(c *cli.Context) error {
154108
return outputImages(store, imageList, truncate, digests, quiet, outputFormat, noheading)
155109
}
156110

157-
func outputHeader(truncate, digests bool) {
111+
func genImagesFormat(quiet, truncate, digests bool) (format string) {
112+
if quiet {
113+
return "{{.ID}}"
114+
}
158115
if truncate {
159-
fmt.Printf("%-20s %-56s ", "IMAGE ID", "IMAGE NAME")
116+
format = "table {{ .ID | printf \"%-20.12s\" }} "
160117
} else {
161-
fmt.Printf("%-64s %-56s ", "IMAGE ID", "IMAGE NAME")
118+
format = "table {{ .ID | printf \"%-64s\" }} "
162119
}
120+
format += "{{ .Name | printf \"%-56s\" }} "
163121

164122
if digests {
165-
fmt.Printf("%-71s ", "DIGEST")
123+
format += "{{ .Digest | printf \"%-71s \"}} "
166124
}
167125

168-
fmt.Printf("%-22s %s\n", "CREATED AT", "SIZE")
126+
format += "{{ .CreatedAt | printf \"%-22s\" }} {{.Size}}"
127+
return
169128
}
170129

171130
func outputImages(store storage.Store, images []storage.Image, truncate, digests, quiet bool, outputFormat string, noheading bool) error {
172131
imageOutput := []imageOutputParams{}
173132

133+
lastID := ""
174134
for _, img := range images {
135+
if quiet && lastID == img.ID {
136+
continue // quiet should not show the same ID multiple times
137+
}
175138
createdTime := img.Created
176139

177140
name := ""
178141
if len(img.Names) > 0 {
179142
name = img.Names[0]
180143
}
181144

182-
info, digest, size, _ := libkpodimage.InfoAndDigestAndSize(store, img)
145+
info, imageDigest, size, _ := libkpodimage.InfoAndDigestAndSize(store, img)
183146
if info != nil {
184147
createdTime = info.Created
185148
}
186149

187150
params := imageOutputParams{
188151
ID: img.ID,
189152
Name: name,
190-
Digest: digest,
153+
Digest: imageDigest,
191154
CreatedAt: createdTime.Format("Jan 2, 2006 15:04"),
192155
Size: libkpodimage.FormattedSize(size),
193156
}
@@ -196,20 +159,45 @@ func outputImages(store storage.Store, images []storage.Image, truncate, digests
196159

197160
var out formats.Writer
198161

199-
if outputFormat != "" {
200-
switch outputFormat {
201-
case "json":
202-
out = formats.JSONstruct{Output: toGeneric(imageOutput)}
203-
default:
204-
// Assuming Go-template
205-
out = formats.StdoutTemplate{Output: toGeneric(imageOutput), Template: outputFormat}
206-
207-
}
208-
} else {
209-
out = stdoutstruct{output: imageOutput, digests: digests, truncate: truncate, quiet: quiet, noheading: noheading}
162+
switch outputFormat {
163+
case "json":
164+
out = formats.JSONstruct{Output: toGeneric(imageOutput)}
165+
default:
166+
out = formats.StdoutTemplate{Output: toGeneric(imageOutput), Template: outputFormat, Fields: imageOutput[0].headerMap()}
210167
}
211168

212169
formats.Writer(out).Out()
213170

214171
return nil
215172
}
173+
174+
type imageOutputParams struct {
175+
ID string `json:"id"`
176+
Name string `json:"names"`
177+
Digest digest.Digest `json:"digest"`
178+
CreatedAt string `json:"created"`
179+
Size string `json:"size"`
180+
}
181+
182+
func toGeneric(params []imageOutputParams) []interface{} {
183+
genericParams := make([]interface{}, len(params))
184+
for i, v := range params {
185+
genericParams[i] = interface{}(v)
186+
}
187+
return genericParams
188+
}
189+
190+
func (i *imageOutputParams) headerMap() map[string]string {
191+
v := reflect.Indirect(reflect.ValueOf(i))
192+
values := make(map[string]string)
193+
194+
for i := 0; i < v.NumField(); i++ {
195+
key := v.Type().Field(i).Name
196+
value := key
197+
if value == "ID" || value == "Name" {
198+
value = "Image" + value
199+
}
200+
values[key] = fmt.Sprintf("%s ", strings.ToUpper(splitCamelCase(value)))
201+
}
202+
return values
203+
}

vendor.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,4 @@ github.com/beorn7/perks 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9
8080
github.com/hpcloud/tail v1.0.0
8181
gopkg.in/fsnotify.v1 v1.4.2
8282
gopkg.in/tomb.v1 v1
83+
github.com/fatih/camelcase f6a740d52f961c60348ebb109adde9f4635d7540

0 commit comments

Comments
 (0)