Skip to content

Commit eaca17f

Browse files
authored
Merge branch 'master' into skip-forbidden-resources
2 parents fcbe860 + 8c32ac5 commit eaca17f

File tree

6 files changed

+220
-419
lines changed

6 files changed

+220
-419
lines changed

cmd/kubectl-tree/apis.go

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func findAPIs(client discovery.DiscoveryInterface) (*resourceMap, error) {
4646
start := time.Now()
4747
resList, err := client.ServerPreferredResources()
4848
if err != nil {
49-
return nil, fmt.Errorf("failed to fetch api groups from kubernetes: %w", err)
49+
klog.V(1).Infof("failed to fetch api groups from kubernetes: %v\n", err)
5050
}
5151
klog.V(2).Infof("queried api discovery in %v", time.Since(start))
5252
klog.V(3).Infof("found %d items (groups) in server-preferred APIResourceList", len(resList))
@@ -100,9 +100,16 @@ func apiNames(a metav1.APIResource, gv schema.GroupVersion) []string {
100100
// TODO(ahmetb): sometimes SingularName is empty (e.g. Deployment), use lowercase Kind as fallback - investigate why
101101
singularName = strings.ToLower(a.Kind)
102102
}
103+
names := []string{singularName}
104+
103105
pluralName := a.Name
106+
if singularName != pluralName {
107+
names = append(names, pluralName)
108+
}
109+
104110
shortNames := a.ShortNames
105-
names := append([]string{singularName, pluralName}, shortNames...)
111+
names = append(names, shortNames...)
112+
106113
for _, n := range names {
107114
fmtBare := n // e.g. deployment
108115
fmtWithGroup := strings.Join([]string{n, gv.Group}, ".") // e.g. deployment.apps
@@ -113,3 +120,20 @@ func apiNames(a metav1.APIResource, gv schema.GroupVersion) []string {
113120
}
114121
return out
115122
}
123+
124+
func figureOutKindName(args []string) (string, string, error) {
125+
if l := len(args); l == 0 || l > 2 {
126+
return "", "", fmt.Errorf("accepts between 1 and 2 arg(s), received %d", l)
127+
}
128+
if len(args) == 2 {
129+
return args[0], args[1], nil
130+
}
131+
seg := strings.Split(args[0], "/")
132+
if len(seg) < 2 {
133+
return "", "", fmt.Errorf("specify the kubernetes object in KIND NAME or KIND/NAME form")
134+
}
135+
if len(seg) > 2 {
136+
return "", "", fmt.Errorf("arguments in KIND/NAME form may not have more than one slash")
137+
}
138+
return seg[0], seg[1], nil
139+
}

cmd/kubectl-tree/rootcmd.go

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
66
You may obtain a copy of the License at
77
8-
http://www.apache.org/licenses/LICENSE-2.0
8+
http://www.apache.org/licenses/LICENSE-2.0
99
1010
Unless required by applicable law or agreed to in writing, software
1111
distributed under the License is distributed on an "AS IS" BASIS,
@@ -22,17 +22,21 @@ import (
2222
"os"
2323
"strings"
2424

25+
"github.com/fatih/color"
26+
"github.com/pkg/errors"
2527
"github.com/spf13/cobra"
2628
"github.com/spf13/pflag"
2729
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2830
"k8s.io/cli-runtime/pkg/genericclioptions"
2931
"k8s.io/client-go/dynamic"
3032
_ "k8s.io/client-go/plugin/pkg/client/auth" // combined authprovider import
33+
"k8s.io/client-go/rest"
3134
"k8s.io/klog"
3235
)
3336

3437
const (
3538
allNamespacesFlag = "all-namespaces"
39+
colorFlag = "color"
3640
)
3741

3842
var cf *genericclioptions.ConfigFlags
@@ -47,7 +51,7 @@ var rootCmd = &cobra.Command{
4751
Short: "Show sub-resources of the Kubernetes object",
4852
Example: " kubectl tree deployment my-app\n" +
4953
" kubectl tree kservice.v1.serving.knative.dev my-app", // TODO add more examples about disambiguation etc
50-
Args: cobra.MinimumNArgs(2),
54+
Args: cobra.RangeArgs(1, 2),
5155
RunE: run,
5256
Version: versionString(),
5357
}
@@ -63,16 +67,28 @@ func versionString() string {
6367
}
6468

6569
func run(command *cobra.Command, args []string) error {
66-
6770
allNs, err := command.Flags().GetBool(allNamespacesFlag)
6871
if err != nil {
6972
allNs = false
7073
}
7174

75+
colorArg, err := command.Flags().GetString(colorFlag)
76+
if err != nil {
77+
return err
78+
}
79+
if colorArg == "always" {
80+
color.NoColor = false
81+
} else if colorArg == "never" {
82+
color.NoColor = true
83+
} else if colorArg != "auto" {
84+
return errors.Errorf("invalid value for --%s", colorFlag)
85+
}
86+
7287
restConfig, err := cf.ToRESTConfig()
7388
if err != nil {
7489
return err
7590
}
91+
restConfig.WarningHandler = rest.NoWarnings{}
7692
restConfig.QPS = 1000
7793
restConfig.Burst = 1000
7894
dyn, err := dynamic.NewForConfig(restConfig)
@@ -90,7 +106,10 @@ func run(command *cobra.Command, args []string) error {
90106
}
91107
klog.V(3).Info("completed querying APIs list")
92108

93-
kind, name := args[0], args[1]
109+
kind, name, err := figureOutKindName(args)
110+
if err != nil {
111+
return err
112+
}
94113
klog.V(3).Infof("parsed kind=%v name=%v", kind, name)
95114

96115
var api apiResource
@@ -160,6 +179,7 @@ func init() {
160179
cf = genericclioptions.NewConfigFlags(true)
161180

162181
rootCmd.Flags().BoolP(allNamespacesFlag, "A", false, "query all objects in all API groups, both namespaced and non-namespaced")
182+
rootCmd.Flags().StringP(colorFlag, "c", "auto", "Enable or disable color output. This can be 'always', 'never', or 'auto' (default = use color only if using tty). The flag is overridden by the NO_COLOR env variable if set.")
163183

164184
cf.AddFlags(rootCmd.Flags())
165185
if err := flag.Set("logtostderr", "true"); err != nil {

cmd/kubectl-tree/status.go

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,45 +5,50 @@ import (
55

66
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
77
"k8s.io/klog"
8+
"sigs.k8s.io/cli-utils/pkg/kstatus/status"
89
)
910

1011
type ReadyStatus string // True False Unknown or ""
1112
type Reason string
1213

13-
func extractStatus(obj unstructured.Unstructured) (ReadyStatus, Reason) {
14+
func extractStatus(obj unstructured.Unstructured) (ReadyStatus, Reason, status.Status) {
1415
jsonVal, _ := json.Marshal(obj.Object["status"])
1516
klog.V(6).Infof("status for object=%s/%s: %s", obj.GetKind(), obj.GetName(), string(jsonVal))
17+
result, err := status.Compute(&obj)
18+
if err != nil {
19+
return "", "", ""
20+
}
1621
statusF, ok := obj.Object["status"]
1722
if !ok {
18-
return "", ""
23+
return "", "", ""
1924
}
2025
statusV, ok := statusF.(map[string]interface{})
2126
if !ok {
22-
return "", ""
27+
return "", "", ""
2328
}
2429
conditionsF, ok := statusV["conditions"]
2530
if !ok {
26-
return "", ""
31+
return "", "", ""
2732
}
2833
conditionsV, ok := conditionsF.([]interface{})
2934
if !ok {
30-
return "", ""
35+
return "", "", ""
3136
}
3237

3338
for _, cond := range conditionsV {
3439
condM, ok := cond.(map[string]interface{})
3540
if !ok {
36-
return "", ""
41+
return "", "", ""
3742
}
3843
condType, ok := condM["type"].(string)
3944
if !ok {
40-
return "", ""
45+
return "", "", ""
4146
}
4247
if condType == "Ready" {
4348
condStatus, _ := condM["status"].(string)
4449
condReason, _ := condM["reason"].(string)
45-
return ReadyStatus(condStatus), Reason(condReason)
50+
return ReadyStatus(condStatus), Reason(condReason), status.Status(result.Status.String())
4651
}
4752
}
48-
return "", ""
53+
return "", "", ""
4954
}

cmd/kubectl-tree/tree.go

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/gosuri/uitable"
1111
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1212
"k8s.io/apimachinery/pkg/util/duration"
13+
"sigs.k8s.io/cli-utils/pkg/kstatus/status"
1314
)
1415

1516
const (
@@ -20,22 +21,23 @@ const (
2021
)
2122

2223
var (
23-
gray = color.New(color.FgHiBlack)
24-
red = color.New(color.FgRed)
25-
green = color.New(color.FgGreen)
24+
gray = color.New(color.FgHiBlack)
25+
red = color.New(color.FgRed)
26+
yellow = color.New(color.FgYellow)
27+
green = color.New(color.FgGreen)
2628
)
2729

2830
// treeView prints object hierarchy to out stream.
2931
func treeView(out io.Writer, objs objectDirectory, obj unstructured.Unstructured) {
3032
tbl := uitable.New()
3133
tbl.Separator = " "
32-
tbl.AddRow("NAMESPACE", "NAME", "READY", "REASON", "AGE")
34+
tbl.AddRow("NAMESPACE", "NAME", "READY", "REASON", "STATUS", "AGE")
3335
treeViewInner("", tbl, objs, obj)
3436
fmt.Fprintln(color.Output, tbl)
3537
}
3638

3739
func treeViewInner(prefix string, tbl *uitable.Table, objs objectDirectory, obj unstructured.Unstructured) {
38-
ready, reason := extractStatus(obj)
40+
ready, reason, kstatus := extractStatus(obj)
3941

4042
var readyColor *color.Color
4143
switch ready {
@@ -50,6 +52,21 @@ func treeViewInner(prefix string, tbl *uitable.Table, objs objectDirectory, obj
5052
ready = "-"
5153
}
5254

55+
var statusColor *color.Color
56+
switch kstatus {
57+
case status.CurrentStatus:
58+
statusColor = green
59+
case status.InProgressStatus:
60+
statusColor = yellow
61+
case status.FailedStatus, status.TerminatingStatus:
62+
statusColor = red
63+
default:
64+
statusColor = gray
65+
}
66+
if kstatus == "" {
67+
kstatus = "-"
68+
}
69+
5370
c := obj.GetCreationTimestamp()
5471
age := duration.HumanDuration(time.Since(c.Time))
5572
if c.IsZero() {
@@ -62,6 +79,7 @@ func treeViewInner(prefix string, tbl *uitable.Table, objs objectDirectory, obj
6279
color.New(color.Bold).Sprint(obj.GetName())),
6380
readyColor.Sprint(ready),
6481
readyColor.Sprint(reason),
82+
statusColor.Sprint(kstatus),
6583
age)
6684
chs := objs.ownedBy(obj.GetUID())
6785
for i, child := range chs {

go.mod

Lines changed: 40 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,82 +3,71 @@ module github.com/ahmetb/kubectl-tree
33
go 1.22
44

55
require (
6-
github.com/fatih/color v1.13.0
6+
github.com/fatih/color v1.15.0
77
github.com/gosuri/uitable v0.0.4
8-
github.com/spf13/cobra v1.4.0
8+
github.com/pkg/errors v0.9.1
9+
github.com/spf13/cobra v1.7.0
910
github.com/spf13/pflag v1.0.5
10-
k8s.io/apimachinery v0.24.1
11-
k8s.io/cli-runtime v0.24.1
12-
k8s.io/client-go v0.24.1
11+
k8s.io/apimachinery v0.27.4
12+
k8s.io/cli-runtime v0.27.4
13+
k8s.io/client-go v0.27.4
1314
k8s.io/klog v1.0.0
15+
sigs.k8s.io/cli-utils v0.35.0
1416
)
1517

1618
require (
17-
cloud.google.com/go v0.81.0 // indirect
18-
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
19-
github.com/Azure/go-autorest/autorest v0.11.18 // indirect
20-
github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect
21-
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
22-
github.com/Azure/go-autorest/logger v0.2.1 // indirect
23-
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
24-
github.com/PuerkitoBio/purell v1.1.1 // indirect
25-
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
2619
github.com/davecgh/go-spew v1.1.1 // indirect
27-
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
20+
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
2821
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
29-
github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
30-
github.com/go-errors/errors v1.0.1 // indirect
31-
github.com/go-logr/logr v1.2.0 // indirect
32-
github.com/go-openapi/jsonpointer v0.19.5 // indirect
33-
github.com/go-openapi/jsonreference v0.19.5 // indirect
34-
github.com/go-openapi/swag v0.19.14 // indirect
22+
github.com/go-errors/errors v1.4.2 // indirect
23+
github.com/go-logr/logr v1.2.3 // indirect
24+
github.com/go-openapi/jsonpointer v0.19.6 // indirect
25+
github.com/go-openapi/jsonreference v0.20.1 // indirect
26+
github.com/go-openapi/swag v0.22.3 // indirect
3527
github.com/gogo/protobuf v1.3.2 // indirect
36-
github.com/golang/protobuf v1.5.2 // indirect
28+
github.com/golang/protobuf v1.5.3 // indirect
3729
github.com/google/btree v1.0.1 // indirect
3830
github.com/google/gnostic v0.5.7-v3refs // indirect
31+
github.com/google/go-cmp v0.5.9 // indirect
3932
github.com/google/gofuzz v1.1.0 // indirect
4033
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
41-
github.com/google/uuid v1.1.2 // indirect
34+
github.com/google/uuid v1.3.0 // indirect
4235
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
43-
github.com/imdario/mergo v0.3.5 // indirect
44-
github.com/inconshreveable/mousetrap v1.0.0 // indirect
36+
github.com/imdario/mergo v0.3.12 // indirect
37+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
4538
github.com/josharian/intern v1.0.0 // indirect
4639
github.com/json-iterator/go v1.1.12 // indirect
4740
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
48-
github.com/mailru/easyjson v0.7.6 // indirect
49-
github.com/mattn/go-colorable v0.1.9 // indirect
50-
github.com/mattn/go-isatty v0.0.14 // indirect
51-
github.com/mattn/go-runewidth v0.0.13 // indirect
41+
github.com/mailru/easyjson v0.7.7 // indirect
42+
github.com/mattn/go-colorable v0.1.13 // indirect
43+
github.com/mattn/go-isatty v0.0.17 // indirect
44+
github.com/mattn/go-runewidth v0.0.15 // indirect
5245
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
5346
github.com/modern-go/reflect2 v1.0.2 // indirect
5447
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
5548
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
5649
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
57-
github.com/pkg/errors v0.9.1 // indirect
58-
github.com/pmezard/go-difflib v1.0.0 // indirect
5950
github.com/rivo/uniseg v0.2.0 // indirect
60-
github.com/stretchr/testify v1.7.0 // indirect
61-
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect
51+
github.com/xlab/treeprint v1.1.0 // indirect
6252
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
63-
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect
64-
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
65-
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
66-
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
67-
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
68-
golang.org/x/text v0.3.7 // indirect
69-
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
53+
golang.org/x/net v0.8.0 // indirect
54+
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
55+
golang.org/x/sys v0.6.0 // indirect
56+
golang.org/x/term v0.6.0 // indirect
57+
golang.org/x/text v0.8.0 // indirect
58+
golang.org/x/time v0.3.0 // indirect
7059
google.golang.org/appengine v1.6.7 // indirect
71-
google.golang.org/protobuf v1.27.1 // indirect
60+
google.golang.org/protobuf v1.28.1 // indirect
7261
gopkg.in/inf.v0 v0.9.1 // indirect
7362
gopkg.in/yaml.v2 v2.4.0 // indirect
74-
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
75-
k8s.io/api v0.24.1 // indirect
76-
k8s.io/klog/v2 v2.60.1 // indirect
77-
k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect
78-
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
79-
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
80-
sigs.k8s.io/kustomize/api v0.11.4 // indirect
81-
sigs.k8s.io/kustomize/kyaml v0.13.6 // indirect
82-
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
83-
sigs.k8s.io/yaml v1.2.0 // indirect
63+
gopkg.in/yaml.v3 v3.0.1 // indirect
64+
k8s.io/api v0.27.4 // indirect
65+
k8s.io/klog/v2 v2.90.1 // indirect
66+
k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect
67+
k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect
68+
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
69+
sigs.k8s.io/kustomize/api v0.13.2 // indirect
70+
sigs.k8s.io/kustomize/kyaml v0.14.1 // indirect
71+
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
72+
sigs.k8s.io/yaml v1.3.0 // indirect
8473
)

0 commit comments

Comments
 (0)