Skip to content

Commit 9a16f69

Browse files
Add jsonpath support to the CLI (#721)
1 parent aaf8850 commit 9a16f69

19 files changed

+206
-15
lines changed

cmd/cli/cmd/action/get.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@ import (
1616
func NewGet() *cobra.Command {
1717
var opts action.GetOptions
1818

19-
resourcePrinter := printer.NewForResource(os.Stdout, printer.WithJSON(), printer.WithYAML(), printer.WithTable(action.TableDataOnGet))
19+
resourcePrinter := printer.NewForResource(
20+
os.Stdout,
21+
printer.WithJSON(),
22+
printer.WithJSONPath(),
23+
printer.WithYAML(),
24+
printer.WithTable(action.TableDataOnGet),
25+
)
2026

2127
cmd := &cobra.Command{
2228
Use: "get",

cmd/cli/cmd/hub/implementations/get.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,13 @@ type getOptions struct {
2828
func NewGet() *cobra.Command {
2929
var opts getOptions
3030

31-
resourcePrinter := printer.NewForResource(os.Stdout, printer.WithJSON(), printer.WithYAML(), printer.WithTable(tableDataOnGet))
31+
resourcePrinter := printer.NewForResource(
32+
os.Stdout,
33+
printer.WithJSON(),
34+
printer.WithJSONPath(),
35+
printer.WithYAML(),
36+
printer.WithTable(tableDataOnGet),
37+
)
3238

3339
get := &cobra.Command{
3440
Use: "get",

cmd/cli/cmd/hub/interfaces/get.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,13 @@ const (
3131
func NewGet() *cobra.Command {
3232
var opts getOptions
3333

34-
resourcePrinter := cliprinter.NewForResource(os.Stdout, cliprinter.WithJSON(), cliprinter.WithYAML(), cliprinter.WithTable(tableDataOnGet))
34+
resourcePrinter := cliprinter.NewForResource(
35+
os.Stdout,
36+
cliprinter.WithJSON(),
37+
cliprinter.WithJSONPath(),
38+
cliprinter.WithYAML(),
39+
cliprinter.WithTable(tableDataOnGet),
40+
)
3541

3642
get := &cobra.Command{
3743
Use: "get",

cmd/cli/cmd/policy/get.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@ import (
1212

1313
// NewGet return a cobra.Command for getting the Capact Global policy on a Capact environment.
1414
func NewGet() *cobra.Command {
15-
resourcePrinter := printer.NewForResource(os.Stdout, printer.WithJSON(), printer.WithYAML(), printer.WithDefaultOutputFormat(printer.YAMLFormat))
15+
resourcePrinter := printer.NewForResource(
16+
os.Stdout,
17+
printer.WithJSON(),
18+
printer.WithJSONPath(),
19+
printer.WithYAML(),
20+
printer.WithDefaultOutputFormat(printer.YAMLFormat),
21+
)
1622

1723
cmd := &cobra.Command{
1824
Use: "get",

cmd/cli/cmd/typeinstance/apply.go

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ func NewApply() *cobra.Command {
3131
resourcePrinter := printer.NewForResource(
3232
os.Stdout,
3333
printer.WithJSON(),
34+
printer.WithJSONPath(),
3435
printer.WithYAML(),
3536
printer.WithTable(tableDataOnGet),
3637
)

cmd/cli/cmd/typeinstance/create.go

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ func NewCreate() *cobra.Command {
3232
resourcePrinter := printer.NewForResource(
3333
os.Stdout,
3434
printer.WithJSON(),
35+
printer.WithJSONPath(),
3536
printer.WithYAML(),
3637
printer.WithTable(tableDataOnCreate),
3738
)

cmd/cli/cmd/typeinstance/get.go

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ func NewGet() *cobra.Command {
4646
resourcePrinter := cliprinter.NewForResource(
4747
out,
4848
cliprinter.WithJSON(),
49+
cliprinter.WithJSONPath(),
4950
cliprinter.WithYAML(),
5051
cliprinter.WithTable(tableDataOnGet),
5152
)

cmd/cli/docs/capact_action_get.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ capact action get funny-stallman -ojson
2626
```
2727
-h, --help help for get
2828
-n, --namespace string Kubernetes namespace where the Action was created (default "default")
29-
-o, --output string Output format. One of: json | table | yaml (default "table")
29+
-o, --output string Output format. One of: json | jsonpath | table | yaml (default "table")
30+
-t, --template string JSON path output template (https://kubernetes.io/docs/reference/kubectl/jsonpath)
3031
--timeout duration Timeout for HTTP request (default 30s)
3132
```
3233

cmd/cli/docs/capact_hub_implementation_get.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ capact hub implementations get cap.interface.database.postgresql.install -oyaml
2525

2626
```
2727
-h, --help help for get
28-
-o, --output string Output format. One of: json | table | yaml (default "table")
28+
-o, --output string Output format. One of: json | jsonpath | table | yaml (default "table")
29+
-t, --template string JSON path output template (https://kubernetes.io/docs/reference/kubectl/jsonpath)
2930
--timeout duration Timeout for HTTP request (default 30s)
3031
```
3132

cmd/cli/docs/capact_hub_interface_get.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ capact hub interfaces get cap.interface.database.postgresql.install -ojson
2525

2626
```
2727
-h, --help help for get
28-
-o, --output string Output format. One of: json | table | yaml (default "table")
28+
-o, --output string Output format. One of: json | jsonpath | table | yaml (default "table")
29+
-t, --template string JSON path output template (https://kubernetes.io/docs/reference/kubectl/jsonpath)
2930
--timeout duration Timeout for HTTP request (default 30s)
3031
```
3132

cmd/cli/docs/capact_policy_get.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ capact policy get [flags]
1414

1515
```
1616
-h, --help help for get
17-
-o, --output string Output format. One of: json | yaml (default "yaml")
17+
-o, --output string Output format. One of: json | jsonpath | yaml (default "yaml")
18+
-t, --template string JSON path output template (https://kubernetes.io/docs/reference/kubectl/jsonpath)
1819
--timeout duration Timeout for HTTP request (default 30s)
1920
```
2021

cmd/cli/docs/capact_typeinstance_apply.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ capact typeinstance apply -f /tmp/typeinstances.yaml
2929
```
3030
-f, --from-file strings The TypeInstances input in YAML format (can specify multiple)
3131
-h, --help help for apply
32-
-o, --output string Output format. One of: json | table | yaml (default "table")
32+
-o, --output string Output format. One of: json | jsonpath | table | yaml (default "table")
33+
-t, --template string JSON path output template (https://kubernetes.io/docs/reference/kubectl/jsonpath)
3334
--timeout duration Timeout for HTTP request (default 30s)
3435
```
3536

cmd/cli/docs/capact_typeinstance_create.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ capact typeinstance create -f ./tmp/typeinstances.yaml
4949
```
5050
-f, --from-file strings The TypeInstances input in YAML format (can specify multiple)
5151
-h, --help help for create
52-
-o, --output string Output format. One of: json | table | yaml (default "table")
52+
-o, --output string Output format. One of: json | jsonpath | table | yaml (default "table")
53+
-t, --template string JSON path output template (https://kubernetes.io/docs/reference/kubectl/jsonpath)
5354
--timeout duration Timeout for HTTP request (default 30s)
5455
```
5556

cmd/cli/docs/capact_typeinstance_get.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ capact typeinstance get c49b 4793 -oyaml --export > /tmp/typeinstances.yaml
2727
```
2828
--export Converts TypeInstance to update format.
2929
-h, --help help for get
30-
-o, --output string Output format. One of: json | table | yaml (default "table")
30+
-o, --output string Output format. One of: json | jsonpath | table | yaml (default "table")
31+
-t, --template string JSON path output template (https://kubernetes.io/docs/reference/kubectl/jsonpath)
3132
--timeout duration Timeout for HTTP request (default 30s)
3233
```
3334

docs/investigation/composed-action/REAMDE.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ To run a single Action you need to:
2525
2626
13. Extract the output TypeInstances:
2727
```
28-
capact action get psql-install -ojson | jq -r '.Actions[].output.typeInstances | map(select(.typeRef.path == "cap.type.database.postgresql.config"))'
28+
capact action get psql-install -ojsonpath -t '{.Actions[*].output.typeInstances[?(@.typeRef.path == "cap.type.database.postgresql.config")]}'
2929
```
3030
3131

internal/cli/printer/format.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,19 @@ const (
1616
TableFormat PrintFormat = "table"
1717
// JSONFormat represents JSON data format.
1818
JSONFormat PrintFormat = "json"
19+
// JSONPathFormat represents JSON path data format.
20+
JSONPathFormat PrintFormat = "jsonpath"
1921
// YAMLFormat represents YAML data format.
2022
YAMLFormat PrintFormat = "yaml"
2123
)
2224

2325
// IsValid returns true if PrintFormat is valid.
2426
func (o PrintFormat) IsValid() bool {
2527
switch o {
26-
case TableFormat, JSONFormat, YAMLFormat:
28+
case TableFormat, JSONFormat, JSONPathFormat, YAMLFormat:
2729
return true
2830
}
31+
2932
return false
3033
}
3134

internal/cli/printer/jsonpath.go

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package printer
2+
3+
import (
4+
"io"
5+
6+
"k8s.io/cli-runtime/pkg/printers"
7+
)
8+
9+
// JSONPath prints selected data from JSON.
10+
type JSONPath struct {
11+
printer *printers.JSONPathPrinter
12+
}
13+
14+
// Print executes k8s JSONPath printer to write to given writer selected data from JSON.
15+
func (p *JSONPath) Print(in interface{}, w io.Writer) error {
16+
p.printer.EnableJSONOutput(true)
17+
18+
return p.printer.JSONPath.Execute(w, in)
19+
}

internal/cli/printer/resource.go

+30-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import (
66
"sort"
77
"strings"
88

9+
"github.com/pkg/errors"
910
"github.com/spf13/pflag"
11+
"k8s.io/cli-runtime/pkg/printers"
1012
)
1113

1214
// Printer is an interface that knows how to print objects.
@@ -18,8 +20,9 @@ type Printer interface {
1820
// ResourcePrinter provides functionality to print a given resource in requested format.
1921
// Can be configured with pflag.FlagSet.
2022
type ResourcePrinter struct {
21-
writer io.Writer
22-
output PrintFormat
23+
writer io.Writer
24+
output PrintFormat
25+
template string
2326

2427
printers map[PrintFormat]Printer
2528
}
@@ -49,6 +52,14 @@ func WithJSON() ResourcePrinterOption {
4952
}
5053
}
5154

55+
// WithJSONPath registers JSON path format type.
56+
func WithJSONPath() ResourcePrinterOption {
57+
return func(r *ResourcePrinter) {
58+
// Cannot fully initialize JSONPath because template at this time is unknown
59+
r.printers[JSONPathFormat] = &JSONPath{}
60+
}
61+
}
62+
5263
// WithYAML registers YAML format type.
5364
func WithYAML() ResourcePrinterOption {
5465
return func(r *ResourcePrinter) {
@@ -73,6 +84,10 @@ func WithDefaultOutputFormat(format PrintFormat) ResourcePrinterOption {
7384
// RegisterFlags registers ResourcePrinter terminal flags.
7485
func (r *ResourcePrinter) RegisterFlags(flags *pflag.FlagSet) {
7586
flags.VarP(&r.output, "output", "o", fmt.Sprintf("Output format. One of: %s", r.availablePrinters()))
87+
88+
if _, ok := r.printers[JSONPathFormat]; ok {
89+
flags.StringVarP(&r.template, "template", "t", "", "JSON path output template (https://kubernetes.io/docs/reference/kubectl/jsonpath)")
90+
}
7691
}
7792

7893
// PrintFormat returns default print format type.
@@ -87,6 +102,19 @@ func (r *ResourcePrinter) Print(in interface{}) error {
87102
return fmt.Errorf("printer %q is not available", r.output)
88103
}
89104

105+
if r.output == JSONPathFormat {
106+
if r.template == "" {
107+
return errors.New("JSON path template is not specified")
108+
}
109+
110+
jsonPathPrinter, err := printers.NewJSONPathPrinter(r.template)
111+
if err != nil {
112+
return errors.Wrap(err, "while creating JSON path printer")
113+
}
114+
115+
printer = &JSONPath{printer: jsonPathPrinter}
116+
}
117+
90118
return printer.Print(in, r.writer)
91119
}
92120

internal/cli/printer/resource_test.go

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package printer
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestResourcePrinter_Print_Success(t *testing.T) {
11+
tests := []struct {
12+
name string
13+
printerOption ResourcePrinterOption
14+
printFormat PrintFormat
15+
jsonPathTemplate string // used only for JSON path print format
16+
data map[string]string
17+
expected string
18+
}{
19+
{
20+
name: "resource printer with json should print json when json output format is selected",
21+
printerOption: WithJSON(),
22+
printFormat: JSONFormat,
23+
data: map[string]string{"foo": "bar"},
24+
expected: "{\n \"foo\": \"bar\"\n}",
25+
},
26+
{
27+
name: "resource printer with json path should print part of json when json path output format is selected and template is specified",
28+
printerOption: WithJSONPath(),
29+
printFormat: JSONPathFormat,
30+
jsonPathTemplate: "{.foo}",
31+
data: map[string]string{"foo": "bar"},
32+
expected: "[\n \"bar\"\n]\n",
33+
},
34+
{
35+
name: "resource printer with yaml should print yaml when yaml output format is selected",
36+
printerOption: WithYAML(),
37+
printFormat: YAMLFormat,
38+
data: map[string]string{"foo": "bar"},
39+
expected: "foo: bar\n",
40+
},
41+
{
42+
name: "resource printer with table should print table when table output format is selected",
43+
printerOption: WithTable(func(_ interface{}) (TableData, error) {
44+
return TableData{
45+
Headers: []string{"foo"},
46+
SingleRow: []string{"bar"},
47+
}, nil
48+
}),
49+
printFormat: TableFormat,
50+
expected: " FOO \n-------\n bar \n-------\n",
51+
},
52+
}
53+
54+
for _, test := range tests {
55+
test := test
56+
57+
t.Run(test.name, func(t *testing.T) {
58+
t.Parallel()
59+
60+
var writer bytes.Buffer
61+
resourcePrinter := NewForResource(&writer, test.printerOption)
62+
resourcePrinter.output = test.printFormat
63+
resourcePrinter.template = test.jsonPathTemplate
64+
65+
resourcePrinter.Print(test.data)
66+
67+
assert.EqualValues(t, test.expected, writer.String())
68+
})
69+
}
70+
}
71+
72+
func TestResourcePrinter_Print_Failure(t *testing.T) {
73+
tests := []struct {
74+
name string
75+
printerOption ResourcePrinterOption
76+
printFormat PrintFormat
77+
jsonPathTemplate string // used only for JSON path
78+
}{
79+
{
80+
name: "resource printer should return error when unknown print format is selected",
81+
printerOption: WithJSON(),
82+
printFormat: "unknown print format",
83+
},
84+
{
85+
name: "resource printer with json path should return error when json path print format is selected and template is not specified",
86+
printerOption: WithJSONPath(),
87+
printFormat: JSONPathFormat,
88+
},
89+
}
90+
91+
for _, test := range tests {
92+
test := test
93+
94+
t.Run(test.name, func(t *testing.T) {
95+
t.Parallel()
96+
97+
var writer bytes.Buffer
98+
resourcePrinter := NewForResource(&writer, test.printerOption)
99+
resourcePrinter.output = test.printFormat
100+
resourcePrinter.template = test.jsonPathTemplate
101+
102+
err := resourcePrinter.Print(struct{}{})
103+
104+
assert.Error(t, err)
105+
})
106+
}
107+
}

0 commit comments

Comments
 (0)