Skip to content

Commit 913fa6b

Browse files
authoredDec 7, 2024··
Merge pull request #45 from keisku/issue44
Add `--show-brackets`
2 parents 4405da3 + 74c6fc3 commit 913fa6b

File tree

5 files changed

+149
-52
lines changed

5 files changed

+149
-52
lines changed
 

‎explore/explainer.go

+13-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package explore
22

33
import (
4-
"errors"
54
"fmt"
65
"io"
76
"strings"
@@ -12,22 +11,27 @@ import (
1211
)
1312

1413
type explainer struct {
15-
gvr schema.GroupVersionResource
16-
openAPIV3Client openapiclient.Client
17-
enablePrintPath bool
14+
gvr schema.GroupVersionResource
15+
openAPIV3Client openapiclient.Client
16+
enablePrintPath bool
17+
enablePrintBrackets bool
1818
}
1919

20-
func (e explainer) explain(w io.Writer, path string) error {
21-
if path == "" {
22-
return errors.New("path must be provided")
20+
func (e explainer) explain(w io.Writer, path path) error {
21+
if path.isEmpty() {
22+
return fmt.Errorf("path must not be empty: %#v", path)
2323
}
24-
fields := strings.Split(path, ".")
24+
fields := strings.Split(path.original, ".")
2525
if len(fields) > 0 {
2626
// Remove resource name
2727
fields = fields[1:]
2828
}
2929
if e.enablePrintPath {
30-
w.Write([]byte(fmt.Sprintf("PATH: %s\n", path)))
30+
if e.enablePrintBrackets {
31+
w.Write([]byte(fmt.Sprintf("PATH: %s\n", path.withBrackets)))
32+
} else {
33+
w.Write([]byte(fmt.Sprintf("PATH: %s\n", path.original)))
34+
}
3135
}
3236
return explainv2.PrintModelDescription(
3337
fields,

‎explore/export_test.go

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package explore
2+
3+
func SetDisablePrintPath(o *Options, b bool) {
4+
o.disablePrintPath = b
5+
}
6+
7+
func SetShowBrackets(o *Options, b bool) {
8+
o.showBrackets = b
9+
}

‎explore/options.go

+20-12
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ type Options struct {
2727
apiVersion string
2828
inputFieldPath string
2929
disablePrintPath bool
30+
showBrackets bool
3031

3132
// After completion
3233
inputFieldPathRegex *regexp.Regexp
@@ -69,6 +70,7 @@ kubectl explore --context=onecontext
6970
}
7071
cmd.Flags().StringVar(&o.apiVersion, "api-version", o.apiVersion, "Get different explanations for particular API version (API group/version)")
7172
cmd.Flags().BoolVar(&o.disablePrintPath, "disable-print-path", o.disablePrintPath, "Disable printing the path to explain")
73+
cmd.Flags().BoolVar(&o.showBrackets, "show-brackets", o.showBrackets, "Enable showing brackets for fields that are arrays")
7274
kubeConfigFlags := defaultConfigFlags().WithWarningPrinter(o.IOStreams)
7375
flags := cmd.PersistentFlags()
7476
kubeConfigFlags.AddFlags(flags)
@@ -194,13 +196,16 @@ func (o *Options) Complete(f cmdutil.Factory, args []string) error {
194196
}
195197

196198
func (o *Options) Run() error {
197-
pathExplainers := make(map[string]explainer)
198-
var paths []string
199+
pathExplainers := make(map[path]explainer)
200+
var paths []path
199201
for _, gvr := range o.gvrs {
200202
visitor := &schemaVisitor{
201-
pathSchema: make(map[string]proto.Schema),
202-
prevPath: strings.ToLower(gvr.Resource),
203-
err: nil,
203+
pathSchema: make(map[path]proto.Schema),
204+
prevPath: path{
205+
original: strings.ToLower(gvr.Resource),
206+
withBrackets: strings.ToLower(gvr.Resource),
207+
},
208+
err: nil,
204209
}
205210
gvk, err := o.mapper.KindFor(gvr)
206211
if err != nil {
@@ -214,14 +219,15 @@ func (o *Options) Run() error {
214219
if visitor.err != nil {
215220
return visitor.err
216221
}
217-
filteredPaths := visitor.listPaths(func(s string) bool {
218-
return o.inputFieldPathRegex.MatchString(s)
222+
filteredPaths := visitor.listPaths(func(s path) bool {
223+
return o.inputFieldPathRegex.MatchString(s.original)
219224
})
220225
for _, p := range filteredPaths {
221226
pathExplainers[p] = explainer{
222-
gvr: gvr,
223-
openAPIV3Client: o.cachedOpenAPIV3Client,
224-
enablePrintPath: !o.disablePrintPath,
227+
gvr: gvr,
228+
openAPIV3Client: o.cachedOpenAPIV3Client,
229+
enablePrintPath: !o.disablePrintPath,
230+
enablePrintBrackets: o.showBrackets,
225231
}
226232
paths = append(paths, p)
227233
}
@@ -232,10 +238,12 @@ func (o *Options) Run() error {
232238
if len(paths) == 1 {
233239
return pathExplainers[paths[0]].explain(o.Out, paths[0])
234240
}
235-
sort.Strings(paths)
241+
sort.SliceStable(paths, func(i, j int) bool {
242+
return paths[i].original < paths[j].original
243+
})
236244
idx, err := fuzzyfinder.Find(
237245
paths,
238-
func(i int) string { return paths[i] },
246+
func(i int) string { return paths[i].original },
239247
fuzzyfinder.WithPreviewWindow(func(i, _, _ int) string {
240248
if i < 0 {
241249
return ""

‎explore/options_test.go

+83-24
Original file line numberDiff line numberDiff line change
@@ -213,49 +213,62 @@ func Test_Run(t *testing.T) {
213213
},
214214
}
215215
tests := []struct {
216-
inputFieldPath string
217-
expectRunError bool
218-
expectKeywords []string
216+
inputFieldPath string
217+
disablePrintPath bool
218+
showBrackets bool
219+
expectRunError bool
220+
expectKeywords []string
221+
unexpectKeywords []string
219222
}{
220223
{
221-
inputFieldPath: "no.*pro",
222-
expectRunError: false,
224+
inputFieldPath: "no.*pro",
225+
disablePrintPath: false,
226+
showBrackets: false,
227+
expectRunError: false,
223228
expectKeywords: []string{
224229
"Node",
225230
"providerID",
226231
"PATH: nodes.spec.providerID",
227232
},
228233
},
229234
{
230-
inputFieldPath: "node.*pro",
231-
expectRunError: false,
235+
inputFieldPath: "node.*pro",
236+
disablePrintPath: false,
237+
showBrackets: false,
238+
expectRunError: false,
232239
expectKeywords: []string{
233240
"Node",
234241
"providerID",
235242
"PATH: nodes.spec.providerID",
236243
},
237244
},
238245
{
239-
inputFieldPath: "nodes.*pro",
240-
expectRunError: false,
246+
inputFieldPath: "nodes.*pro",
247+
disablePrintPath: false,
248+
showBrackets: false,
249+
expectRunError: false,
241250
expectKeywords: []string{
242251
"Node",
243252
"providerID",
244253
"PATH: nodes.spec.providerID",
245254
},
246255
},
247256
{
248-
inputFieldPath: "providerID",
249-
expectRunError: false,
257+
inputFieldPath: "providerID",
258+
disablePrintPath: false,
259+
showBrackets: false,
260+
expectRunError: false,
250261
expectKeywords: []string{
251262
"Node",
252263
"providerID",
253264
"PATH: nodes.spec.providerID",
254265
},
255266
},
256267
{
257-
inputFieldPath: "hpa.*own.*id",
258-
expectRunError: false,
268+
inputFieldPath: "hpa.*own.*id",
269+
disablePrintPath: false,
270+
showBrackets: false,
271+
expectRunError: false,
259272
expectKeywords: []string{
260273
"autoscaling",
261274
"HorizontalPodAutoscaler",
@@ -264,8 +277,10 @@ func Test_Run(t *testing.T) {
264277
},
265278
},
266279
{
267-
inputFieldPath: "horizontalpodautoscalers.*own.*id",
268-
expectRunError: false,
280+
inputFieldPath: "horizontalpodautoscalers.*own.*id",
281+
disablePrintPath: false,
282+
showBrackets: false,
283+
expectRunError: false,
269284
expectKeywords: []string{
270285
"autoscaling",
271286
"HorizontalPodAutoscaler",
@@ -274,8 +289,10 @@ func Test_Run(t *testing.T) {
274289
},
275290
},
276291
{
277-
inputFieldPath: "horizontalpodautoscaler.*own.*id",
278-
expectRunError: false,
292+
inputFieldPath: "horizontalpodautoscaler.*own.*id",
293+
disablePrintPath: false,
294+
showBrackets: false,
295+
expectRunError: false,
279296
expectKeywords: []string{
280297
"autoscaling",
281298
"HorizontalPodAutoscaler",
@@ -284,8 +301,10 @@ func Test_Run(t *testing.T) {
284301
},
285302
},
286303
{
287-
inputFieldPath: "csistoragecapacity.maximumVolumeSize",
288-
expectRunError: false,
304+
inputFieldPath: "csistoragecapacity.maximumVolumeSize",
305+
disablePrintPath: false,
306+
showBrackets: false,
307+
expectRunError: false,
289308
expectKeywords: []string{
290309
"CSIStorageCapacity",
291310
"storage.k8s.io",
@@ -294,8 +313,10 @@ func Test_Run(t *testing.T) {
294313
},
295314
},
296315
{
297-
inputFieldPath: "csistoragecapacities.maximumVolumeSize",
298-
expectRunError: false,
316+
inputFieldPath: "csistoragecapacities.maximumVolumeSize",
317+
disablePrintPath: false,
318+
showBrackets: false,
319+
expectRunError: false,
299320
expectKeywords: []string{
300321
"CSIStorageCapacity",
301322
"storage.k8s.io",
@@ -304,19 +325,52 @@ func Test_Run(t *testing.T) {
304325
},
305326
},
306327
{
307-
inputFieldPath: "CSIStorageCapacity.*VolumeSize",
308-
expectRunError: false,
328+
inputFieldPath: "CSIStorageCapacity.*VolumeSize",
329+
disablePrintPath: false,
330+
showBrackets: false,
331+
expectRunError: false,
309332
expectKeywords: []string{
310333
"CSIStorageCapacity",
311334
"storage.k8s.io",
312335
"v1",
313336
"PATH: csistoragecapacities.maximumVolumeSize",
314337
},
315338
},
339+
{
340+
inputFieldPath: "nodes.status.conditions.type",
341+
disablePrintPath: false,
342+
showBrackets: true,
343+
expectRunError: false,
344+
expectKeywords: []string{
345+
"Node",
346+
"type",
347+
"PATH: nodes.status.conditions[].type",
348+
},
349+
},
350+
{
351+
inputFieldPath: "nodes.status.conditions.type",
352+
disablePrintPath: true,
353+
showBrackets: true,
354+
expectRunError: false,
355+
expectKeywords: []string{
356+
"Node",
357+
"type",
358+
},
359+
unexpectKeywords: []string{
360+
"PATH: nodes.status.conditions[].type",
361+
"PATH: nodes.status.conditions.type",
362+
},
363+
},
316364
}
317365
for _, tt := range tests {
318366
for _, version := range k8sVersions {
319-
t.Run(fmt.Sprintf("version: %s inputFieldPath: %s", version, tt.inputFieldPath), func(t *testing.T) {
367+
t.Run(fmt.Sprintf(
368+
"version: %s inputFieldPath: %s, disablePrintPath: %v, showBrackets: %v",
369+
version,
370+
tt.inputFieldPath,
371+
tt.disablePrintPath,
372+
tt.showBrackets,
373+
), func(t *testing.T) {
320374
fakeServer := fakeServers[version]
321375
fakeDiscoveryClient := discovery.NewDiscoveryClientForConfigOrDie(&rest.Config{Host: fakeServer.HttpServer.URL})
322376
tf := cmdtesting.NewTestFactory()
@@ -338,6 +392,8 @@ func Test_Run(t *testing.T) {
338392
Out: &stdout,
339393
ErrOut: &errout,
340394
})
395+
explore.SetDisablePrintPath(opts, tt.disablePrintPath)
396+
explore.SetShowBrackets(opts, tt.showBrackets)
341397
require.NoError(t, opts.Complete(tf, []string{tt.inputFieldPath}))
342398
err := opts.Run()
343399
if tt.expectRunError {
@@ -348,6 +404,9 @@ func Test_Run(t *testing.T) {
348404
for _, keyword := range tt.expectKeywords {
349405
require.Contains(t, stdout.String(), keyword)
350406
}
407+
for _, keyword := range tt.unexpectKeywords {
408+
require.NotContains(t, stdout.String(), keyword)
409+
}
351410
})
352411
}
353412
}

‎explore/schema_visitor.go

+24-7
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,41 @@ import (
88
"k8s.io/kubectl/pkg/explain"
99
)
1010

11+
type path struct {
12+
original string
13+
withBrackets string
14+
}
15+
16+
func (p path) isEmpty() bool {
17+
return p.original == "" && p.withBrackets == ""
18+
}
19+
1120
type schemaVisitor struct {
12-
prevPath string
13-
pathSchema map[string]proto.Schema
21+
prevPath path
22+
pathSchema map[path]proto.Schema
1423
err error
1524
}
1625

1726
var _ proto.SchemaVisitor = (*schemaVisitor)(nil)
1827

1928
func (v *schemaVisitor) VisitKind(k *proto.Kind) {
2029
keys := k.Keys()
21-
paths := make([]string, len(keys))
30+
paths := make([]path, len(keys))
2231
for i, key := range keys {
23-
paths[i] = strings.Join([]string{v.prevPath, key}, ".")
32+
paths[i] = path{
33+
original: strings.Join([]string{v.prevPath.original, key}, "."),
34+
withBrackets: strings.Join([]string{v.prevPath.withBrackets, key}, "."),
35+
}
2436
}
2537
for i, key := range keys {
2638
schema, err := explain.LookupSchemaForField(k, []string{key})
2739
if err != nil {
2840
v.err = err
2941
return
3042
}
43+
if _, ok := schema.(*proto.Array); ok {
44+
paths[i].withBrackets += "[]"
45+
}
3146
v.pathSchema[paths[i]] = schema
3247
v.prevPath = paths[i]
3348
schema.Accept(v)
@@ -57,13 +72,15 @@ func (v *schemaVisitor) VisitMap(m *proto.Map) {
5772
m.SubType.Accept(v)
5873
}
5974

60-
func (v *schemaVisitor) listPaths(filter func(string) bool) []string {
61-
paths := make([]string, 0, len(v.pathSchema))
75+
func (v *schemaVisitor) listPaths(filter func(path) bool) []path {
76+
paths := make([]path, 0, len(v.pathSchema))
6277
for path := range v.pathSchema {
6378
if filter(path) {
6479
paths = append(paths, path)
6580
}
6681
}
67-
sort.Strings(paths)
82+
sort.SliceStable(paths, func(i, j int) bool {
83+
return paths[i].original < paths[j].original
84+
})
6885
return paths
6986
}

0 commit comments

Comments
 (0)
Please sign in to comment.