Skip to content

Commit 24329ca

Browse files
feat: add --condition-types flag for custom condition types (#102)
Co-authored-by: [email protected] <[email protected]>
1 parent ac0d67a commit 24329ca

File tree

4 files changed

+160
-23
lines changed

4 files changed

+160
-23
lines changed

cmd/kubectl-tree/rootcmd.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,15 @@ import (
3535
)
3636

3737
const (
38-
allNamespacesFlag = "all-namespaces"
39-
colorFlag = "color"
38+
allNamespacesFlag = "all-namespaces"
39+
colorFlag = "color"
40+
conditionTypesFlag = "condition-types"
4041
)
4142

42-
var cf *genericclioptions.ConfigFlags
43+
var (
44+
cf *genericclioptions.ConfigFlags
45+
conditionTypes []string
46+
)
4347

4448
// This variable is populated by goreleaser
4549
var version string
@@ -84,6 +88,15 @@ func run(command *cobra.Command, args []string) error {
8488
return errors.Errorf("invalid value for --%s", colorFlag)
8589
}
8690

91+
conditionTypes, err = command.Flags().GetStringSlice(conditionTypesFlag)
92+
if err != nil {
93+
return err
94+
}
95+
if len(conditionTypes) == 0 {
96+
// Default to "Ready" if not specified
97+
conditionTypes = []string{"Ready"}
98+
}
99+
87100
restConfig, err := cf.ToRESTConfig()
88101
if err != nil {
89102
return err
@@ -160,7 +173,7 @@ func run(command *cobra.Command, args []string) error {
160173
fmt.Println("No resources are owned by this object through ownerReferences.")
161174
return nil
162175
}
163-
treeView(os.Stderr, objs, *obj)
176+
treeView(os.Stderr, objs, *obj, conditionTypes)
164177
klog.V(2).Infof("done printing tree view")
165178
return nil
166179
}
@@ -180,6 +193,7 @@ func init() {
180193

181194
rootCmd.Flags().BoolP(allNamespacesFlag, "A", false, "query all objects in all API groups, both namespaced and non-namespaced")
182195
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.")
196+
rootCmd.Flags().StringSlice(conditionTypesFlag, []string{}, "Comma-separated list of condition types to check (default: Ready). Example: Ready,Processed,Scheduled")
183197

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

cmd/kubectl-tree/status.go

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
type ReadyStatus string // True False Unknown or ""
1212
type Reason string
1313

14-
func extractStatus(obj unstructured.Unstructured) (ReadyStatus, Reason, status.Status) {
14+
func extractStatus(obj unstructured.Unstructured, conditionTypes []string) (ReadyStatus, Reason, status.Status) {
1515
jsonVal, _ := json.Marshal(obj.Object["status"])
1616
klog.V(6).Infof("status for object=%s/%s: %s", obj.GetKind(), obj.GetName(), string(jsonVal))
1717
result, err := status.Compute(&obj)
@@ -35,19 +35,22 @@ func extractStatus(obj unstructured.Unstructured) (ReadyStatus, Reason, status.S
3535
return "", "", ""
3636
}
3737

38-
for _, cond := range conditionsV {
39-
condM, ok := cond.(map[string]interface{})
40-
if !ok {
41-
return "", "", ""
42-
}
43-
condType, ok := condM["type"].(string)
44-
if !ok {
45-
return "", "", ""
46-
}
47-
if condType == "Ready" {
48-
condStatus, _ := condM["status"].(string)
49-
condReason, _ := condM["reason"].(string)
50-
return ReadyStatus(condStatus), Reason(condReason), status.Status(result.Status.String())
38+
// Check each requested condition type in order
39+
for _, targetCondType := range conditionTypes {
40+
for _, cond := range conditionsV {
41+
condM, ok := cond.(map[string]interface{})
42+
if !ok {
43+
continue
44+
}
45+
condType, ok := condM["type"].(string)
46+
if !ok {
47+
continue
48+
}
49+
if condType == targetCondType {
50+
condStatus, _ := condM["status"].(string)
51+
condReason, _ := condM["reason"].(string)
52+
return ReadyStatus(condStatus), Reason(condReason), status.Status(result.Status.String())
53+
}
5154
}
5255
}
5356
return "", "", ""

cmd/kubectl-tree/status_test.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package main
2+
3+
import (
4+
"testing"
5+
6+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
7+
)
8+
9+
func TestExtractStatusWithMultipleConditionTypes(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
obj unstructured.Unstructured
13+
conditionTypes []string
14+
wantReady ReadyStatus
15+
wantReason Reason
16+
}{
17+
{
18+
name: "finds Ready condition",
19+
obj: unstructured.Unstructured{
20+
Object: map[string]interface{}{
21+
"status": map[string]interface{}{
22+
"conditions": []interface{}{
23+
map[string]interface{}{
24+
"type": "Ready",
25+
"status": "True",
26+
"reason": "AllGood",
27+
},
28+
},
29+
},
30+
},
31+
},
32+
conditionTypes: []string{"Ready"},
33+
wantReady: "True",
34+
wantReason: "AllGood",
35+
},
36+
{
37+
name: "finds Processed condition",
38+
obj: unstructured.Unstructured{
39+
Object: map[string]interface{}{
40+
"status": map[string]interface{}{
41+
"conditions": []interface{}{
42+
map[string]interface{}{
43+
"type": "Processed",
44+
"status": "True",
45+
"reason": "ProcessingComplete",
46+
},
47+
},
48+
},
49+
},
50+
},
51+
conditionTypes: []string{"Processed"},
52+
wantReady: "True",
53+
wantReason: "ProcessingComplete",
54+
},
55+
{
56+
name: "finds first matching condition from multiple types",
57+
obj: unstructured.Unstructured{
58+
Object: map[string]interface{}{
59+
"status": map[string]interface{}{
60+
"conditions": []interface{}{
61+
map[string]interface{}{
62+
"type": "Scheduled",
63+
"status": "True",
64+
"reason": "ScheduledOK",
65+
},
66+
map[string]interface{}{
67+
"type": "Processed",
68+
"status": "False",
69+
"reason": "ProcessingFailed",
70+
},
71+
},
72+
},
73+
},
74+
},
75+
conditionTypes: []string{"Ready", "Processed", "Scheduled"},
76+
wantReady: "False",
77+
wantReason: "ProcessingFailed",
78+
},
79+
{
80+
name: "returns empty when condition type not found",
81+
obj: unstructured.Unstructured{
82+
Object: map[string]interface{}{
83+
"status": map[string]interface{}{
84+
"conditions": []interface{}{
85+
map[string]interface{}{
86+
"type": "Ready",
87+
"status": "True",
88+
"reason": "AllGood",
89+
},
90+
},
91+
},
92+
},
93+
},
94+
conditionTypes: []string{"NonExistent"},
95+
wantReady: "",
96+
wantReason: "",
97+
},
98+
{
99+
name: "handles object without status",
100+
obj: unstructured.Unstructured{
101+
Object: map[string]interface{}{},
102+
},
103+
conditionTypes: []string{"Ready"},
104+
wantReady: "",
105+
wantReason: "",
106+
},
107+
}
108+
109+
for _, tt := range tests {
110+
t.Run(tt.name, func(t *testing.T) {
111+
gotReady, gotReason, _ := extractStatus(tt.obj, tt.conditionTypes)
112+
if gotReady != tt.wantReady {
113+
t.Errorf("extractStatus() gotReady = %v, want %v", gotReady, tt.wantReady)
114+
}
115+
if gotReason != tt.wantReason {
116+
t.Errorf("extractStatus() gotReason = %v, want %v", gotReason, tt.wantReason)
117+
}
118+
})
119+
}
120+
}

cmd/kubectl-tree/tree.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,16 @@ var (
2828
)
2929

3030
// treeView prints object hierarchy to out stream.
31-
func treeView(out io.Writer, objs objectDirectory, obj unstructured.Unstructured) {
31+
func treeView(out io.Writer, objs objectDirectory, obj unstructured.Unstructured, conditionTypes []string) {
3232
tbl := uitable.New()
3333
tbl.Separator = " "
3434
tbl.AddRow("NAMESPACE", "NAME", "READY", "REASON", "STATUS", "AGE")
35-
treeViewInner("", tbl, objs, obj)
35+
treeViewInner("", tbl, objs, obj, conditionTypes)
3636
fmt.Fprintln(color.Output, tbl)
3737
}
3838

39-
func treeViewInner(prefix string, tbl *uitable.Table, objs objectDirectory, obj unstructured.Unstructured) {
40-
ready, reason, kstatus := extractStatus(obj)
39+
func treeViewInner(prefix string, tbl *uitable.Table, objs objectDirectory, obj unstructured.Unstructured, conditionTypes []string) {
40+
ready, reason, kstatus := extractStatus(obj, conditionTypes)
4141

4242
var readyColor *color.Color
4343
switch ready {
@@ -90,7 +90,7 @@ func treeViewInner(prefix string, tbl *uitable.Table, objs objectDirectory, obj
9090
default:
9191
p = prefix + firstElemPrefix
9292
}
93-
treeViewInner(p, tbl, objs, child)
93+
treeViewInner(p, tbl, objs, child, conditionTypes)
9494
}
9595
}
9696

0 commit comments

Comments
 (0)