Skip to content

Commit

Permalink
Merge pull request #575 from fuweid/me-add-template-output
Browse files Browse the repository at this point in the history
cmd: add go-template option for inspect commands
  • Loading branch information
k8s-ci-robot authored Feb 20, 2020
2 parents 82de8ee + e602578 commit 86dfb0f
Show file tree
Hide file tree
Showing 16 changed files with 337 additions and 47 deletions.
17 changes: 11 additions & 6 deletions cmd/crictl/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,13 +341,17 @@ var containerStatusCommand = &cli.Command{
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "Output format, One of: json|yaml|table",
Usage: "Output format, One of: json|yaml|go-template|table",
},
&cli.BoolFlag{
Name: "quiet",
Aliases: []string{"q"},
Usage: "Do not show verbose information",
},
&cli.StringFlag{
Name: "template",
Usage: "The template string is only used when output is go-template; The Template format is golang template",
},
},
Action: func(context *cli.Context) error {
if context.NArg() == 0 {
Expand All @@ -361,7 +365,7 @@ var containerStatusCommand = &cli.Command{

for i := 0; i < context.NArg(); i++ {
containerID := context.Args().Get(i)
err := ContainerStatus(runtimeClient, containerID, context.String("output"), context.Bool("quiet"))
err := ContainerStatus(runtimeClient, containerID, context.String("output"), context.String("template"), context.Bool("quiet"))
if err != nil {
return fmt.Errorf("Getting the status of the container %q failed: %v", containerID, err)
}
Expand Down Expand Up @@ -419,6 +423,7 @@ var listContainersCommand = &cli.Command{
Name: "output",
Aliases: []string{"o"},
Usage: "Output format, One of: json|yaml|table",
Value: "table",
},
&cli.BoolFlag{
Name: "all",
Expand Down Expand Up @@ -726,7 +731,7 @@ func marshalContainerStatus(cs *pb.ContainerStatus) (string, error) {

// ContainerStatus sends a ContainerStatusRequest to the server, and parses
// the returned ContainerStatusResponse.
func ContainerStatus(client pb.RuntimeServiceClient, ID, output string, quiet bool) error {
func ContainerStatus(client pb.RuntimeServiceClient, ID, output string, tmplStr string, quiet bool) error {
verbose := !(quiet)
if output == "" { // default to json output
output = "json"
Expand All @@ -751,8 +756,8 @@ func ContainerStatus(client pb.RuntimeServiceClient, ID, output string, quiet bo
}

switch output {
case "json", "yaml":
return outputStatusInfo(status, r.Info, output)
case "json", "yaml", "go-template":
return outputStatusInfo(status, r.Info, output, tmplStr)
case "table": // table output is after this switch block
default:
return fmt.Errorf("output option cannot be %s", output)
Expand Down Expand Up @@ -858,7 +863,7 @@ func ListContainers(runtimeClient pb.RuntimeServiceClient, imageClient pb.ImageS
return outputProtobufObjAsJSON(r)
case "yaml":
return outputProtobufObjAsYAML(r)
case "table", "":
case "table":
// continue; output will be generated after the switch block ends.
default:
return fmt.Errorf("unsupported output format %q", opts.output)
Expand Down
22 changes: 16 additions & 6 deletions cmd/crictl/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,13 +218,17 @@ var imageStatusCommand = &cli.Command{
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "Output format, One of: json|yaml|table",
Usage: "Output format, One of: json|yaml|go-template|table",
},
&cli.BoolFlag{
Name: "quiet",
Aliases: []string{"q"},
Usage: "Do not show verbose information",
},
&cli.StringFlag{
Name: "template",
Usage: "The template string is only used when output is go-template; The Template format is golang template",
},
},
Action: func(context *cli.Context) error {
if context.NArg() == 0 {
Expand All @@ -241,6 +245,7 @@ var imageStatusCommand = &cli.Command{
if output == "" { // default to json output
output = "json"
}
tmplStr := context.String("template")
for i := 0; i < context.NArg(); i++ {
id := context.Args().Get(i)

Expand All @@ -258,8 +263,8 @@ var imageStatusCommand = &cli.Command{
return fmt.Errorf("failed to marshal status to json for %q: %v", id, err)
}
switch output {
case "json", "yaml":
if err := outputStatusInfo(status, r.Info, output); err != nil {
case "json", "yaml", "go-template":
if err := outputStatusInfo(status, r.Info, output, tmplStr); err != nil {
return fmt.Errorf("failed to output status for %q: %v", id, err)
}
continue
Expand Down Expand Up @@ -417,7 +422,11 @@ var imageFsInfoCommand = &cli.Command{
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "Output format, One of: json|yaml|table",
Usage: "Output format, One of: json|yaml|go-template|table",
},
&cli.StringFlag{
Name: "template",
Usage: "The template string is only used when output is go-template; The Template format is golang template",
},
},
Action: func(context *cli.Context) error {
Expand All @@ -431,6 +440,7 @@ var imageFsInfoCommand = &cli.Command{
if output == "" { // default to json output
output = "json"
}
tmplStr := context.String("template")

r, err := ImageFsInfo(imageClient)
if err != nil {
Expand All @@ -443,8 +453,8 @@ var imageFsInfoCommand = &cli.Command{
}

switch output {
case "json", "yaml":
if err := outputStatusInfo(status, nil, output); err != nil {
case "json", "yaml", "go-template":
if err := outputStatusInfo(status, nil, output, tmplStr); err != nil {
return fmt.Errorf("failed to output image filesystem info %v", err)
}
continue
Expand Down
8 changes: 6 additions & 2 deletions cmd/crictl/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,17 @@ var runtimeStatusCommand = &cli.Command{
Name: "output",
Aliases: []string{"o"},
Value: "json",
Usage: "Output format, One of: json|yaml",
Usage: "Output format, One of: json|yaml|go-template",
},
&cli.BoolFlag{
Name: "quiet",
Aliases: []string{"q"},
Usage: "Do not show verbose information",
},
&cli.StringFlag{
Name: "template",
Usage: "The template string is only used when output is go-template; The Template format is golang template",
},
},
Action: func(context *cli.Context) error {
runtimeClient, runtimeConn, err := getRuntimeClient(context)
Expand Down Expand Up @@ -72,5 +76,5 @@ func Info(cliContext *cli.Context, client pb.RuntimeServiceClient) error {
if err != nil {
return err
}
return outputStatusInfo(status, r.Info, cliContext.String("output"))
return outputStatusInfo(status, r.Info, cliContext.String("output"), cliContext.String("template"))
}
17 changes: 11 additions & 6 deletions cmd/crictl/sandbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,13 +191,17 @@ var podStatusCommand = &cli.Command{
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "Output format, One of: json|yaml|table",
Usage: "Output format, One of: json|yaml|go-template|table",
},
&cli.BoolFlag{
Name: "quiet",
Aliases: []string{"q"},
Usage: "Do not show verbose information",
},
&cli.StringFlag{
Name: "template",
Usage: "The template string is only used when output is go-template; The Template format is golang template",
},
},
Action: func(context *cli.Context) error {
if context.NArg() == 0 {
Expand All @@ -211,7 +215,7 @@ var podStatusCommand = &cli.Command{
for i := 0; i < context.NArg(); i++ {
id := context.Args().Get(i)

err := PodSandboxStatus(runtimeClient, id, context.String("output"), context.Bool("quiet"))
err := PodSandboxStatus(runtimeClient, id, context.String("output"), context.Bool("quiet"), context.String("template"))
if err != nil {
return fmt.Errorf("getting the pod sandbox status for %q failed: %v", id, err)
}
Expand Down Expand Up @@ -264,6 +268,7 @@ var listPodCommand = &cli.Command{
Name: "output",
Aliases: []string{"o"},
Usage: "Output format, One of: json|yaml|table",
Value: "table",
},
&cli.BoolFlag{
Name: "latest",
Expand Down Expand Up @@ -380,7 +385,7 @@ func marshalPodSandboxStatus(ps *pb.PodSandboxStatus) (string, error) {

// PodSandboxStatus sends a PodSandboxStatusRequest to the server, and parses
// the returned PodSandboxStatusResponse.
func PodSandboxStatus(client pb.RuntimeServiceClient, ID, output string, quiet bool) error {
func PodSandboxStatus(client pb.RuntimeServiceClient, ID, output string, quiet bool, tmplStr string) error {
verbose := !(quiet)
if output == "" { // default to json output
output = "json"
Expand All @@ -405,8 +410,8 @@ func PodSandboxStatus(client pb.RuntimeServiceClient, ID, output string, quiet b
return err
}
switch output {
case "json", "yaml":
return outputStatusInfo(status, r.Info, output)
case "json", "yaml", "go-template":
return outputStatusInfo(status, r.Info, output, tmplStr)
case "table": // table output is after this switch block
default:
return fmt.Errorf("output option cannot be %s", output)
Expand Down Expand Up @@ -495,7 +500,7 @@ func ListPodSandboxes(client pb.RuntimeServiceClient, opts listOptions) error {
return outputProtobufObjAsJSON(r)
case "yaml":
return outputProtobufObjAsYAML(r)
case "table", "":
case "table":
// continue; output will be generated after the switch block ends.
default:
return fmt.Errorf("unsupported output format %q", opts.output)
Expand Down
71 changes: 71 additions & 0 deletions cmd/crictl/templates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
"bytes"
"encoding/json"
"strings"
"text/template"

"github.com/pkg/errors"
)

func builtinTmplFuncs() template.FuncMap {
return template.FuncMap{
"json": jsonBuiltinTmplFunc,
"title": strings.Title,
"lower": strings.ToLower,
"upper": strings.ToUpper,
}
}

// jsonBuiltinTmplFunc allows to jsonify result of template execution.
func jsonBuiltinTmplFunc(v interface{}) string {
o := new(bytes.Buffer)
enc := json.NewEncoder(o)
// FIXME(fuweid): should we panic?
enc.Encode(v)
return o.String()
}

// tmplExecuteRawJSON executes the template with interface{} with decoded by
// rawJSON string.
func tmplExecuteRawJSON(tmplStr string, rawJSON string) (string, error) {
dec := json.NewDecoder(
bytes.NewReader([]byte(rawJSON)),
)
dec.UseNumber()

var raw interface{}
if err := dec.Decode(&raw); err != nil {
return "", errors.Wrapf(err, "failed to decode json")
}

var o = new(bytes.Buffer)
tmpl, err := template.New("tmplExecuteRawJSON").Funcs(builtinTmplFuncs()).Parse(tmplStr)
if err != nil {
return "", errors.Wrapf(err, "failed to generate go-template")
}

// return error if key doesn't exist
tmpl = tmpl.Option("missingkey=error")
if err := tmpl.Execute(o, raw); err != nil {
return "", errors.Wrapf(err, "failed to template data")
}
return o.String(), nil
}
66 changes: 66 additions & 0 deletions cmd/crictl/templates_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
"testing"
)

func TestTmplExecuteRawJSON(t *testing.T) {
testcases := []struct {
rawJSON string
tmplStr string
expected string
hasErr bool
}{
{
rawJSON: `{"ImageID": "abc", "Tag": "latest", "Size": 123456}`,
tmplStr: "{{.Size}}",
expected: "123456",
},
{
rawJSON: `{"ImageID": "abcd", "Tag": "v1.0", "Size": 123456}`,
tmplStr: "{{.Size}} , {{.ImageID}}",
expected: "123456 , abcd",
},
{
rawJSON: `{"ImageID": "aBcd", "Tag": "v1.0", "Size": 123456}`,
tmplStr: "{{title .ImageID}} {{lower .ImageID}} {{upper .ImageID}}",
expected: "ABcd abcd ABCD",
},
{
rawJSON: `{"ImageID": "aBcd", "Tag": "v1.0", "Size": 123456}`,
tmplStr: "{{ .ImageName }}",
hasErr: true, // missing key
},
}

for _, tc := range testcases {
got, err := tmplExecuteRawJSON(tc.tmplStr, tc.rawJSON)
if (err != nil) != tc.hasErr {
t.Errorf("expected hasErr=%v, but got error=%v", tc.hasErr, err)
}

if tc.hasErr {
continue
}

if got != tc.expected {
t.Errorf("expected result=%v, but got=%v", tc.expected, got)
}
}
}
8 changes: 7 additions & 1 deletion cmd/crictl/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ func outputProtobufObjAsYAML(obj proto.Message) error {
return nil
}

func outputStatusInfo(status string, info map[string]string, format string) error {
func outputStatusInfo(status string, info map[string]string, format string, tmplStr string) error {
// Sort all keys
keys := []string{}
for k := range info {
Expand Down Expand Up @@ -242,6 +242,12 @@ func outputStatusInfo(status string, info map[string]string, format string) erro
return err
}
fmt.Println(output.String())
case "go-template":
output, err := tmplExecuteRawJSON(tmplStr, jsonInfo)
if err != nil {
return err
}
fmt.Println(output)
default:
fmt.Printf("Don't support %q format\n", format)
}
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
github.com/opencontainers/go-digest v1.0.0-rc1
github.com/opencontainers/selinux v1.3.1-0.20190929122143-5215b1806f52
github.com/pborman/uuid v1.2.0
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.4.2
github.com/urfave/cli/v2 v2.0.0
golang.org/x/net v0.0.0-20191204025024-5ee1b9f4859a
Expand Down
Loading

0 comments on commit 86dfb0f

Please sign in to comment.