Skip to content

Commit 3b61b30

Browse files
Add regex to clusterResources analyzer (#1189)
* similar to the textAnalyze analyzer, adds support to evaluate the value at the specified yamlPath with regex or regexGroups * fixes a typo in the shorthand name used for searching for cluster resource PVC objects
1 parent 6a9ad78 commit 3b61b30

File tree

10 files changed

+539
-24
lines changed

10 files changed

+539
-24
lines changed

config/crds/troubleshoot.sh_analyzers.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,10 @@ spec:
242242
type: object
243243
type: object
244244
type: array
245+
regex:
246+
type: string
247+
regexGroups:
248+
type: string
245249
strict:
246250
type: BoolString
247251
yamlPath:

config/crds/troubleshoot.sh_preflights.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,10 @@ spec:
242242
type: object
243243
type: object
244244
type: array
245+
regex:
246+
type: string
247+
regexGroups:
248+
type: string
245249
strict:
246250
type: BoolString
247251
yamlPath:

config/crds/troubleshoot.sh_supportbundles.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,10 @@ spec:
273273
type: object
274274
type: object
275275
type: array
276+
regex:
277+
type: string
278+
regexGroups:
279+
type: string
276280
strict:
277281
type: BoolString
278282
yamlPath:
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
{
2+
"kind": "PersistentVolumeClaimList",
3+
"apiVersion": "v1",
4+
"metadata": {
5+
"resourceVersion": "1463098"
6+
},
7+
"items": [
8+
{
9+
"kind": "PersistentVolumeClaim",
10+
"apiVersion": "v1",
11+
"metadata": {
12+
"name": "data-postgresql-0",
13+
"namespace": "default",
14+
"uid": "b733694c-a969-4763-9960-d3465c9fccd5",
15+
"resourceVersion": "1364772",
16+
"creationTimestamp": "2023-05-15T21:22:00Z",
17+
"labels": {
18+
"app.kubernetes.io/component": "primary",
19+
"app.kubernetes.io/instance": "some-app",
20+
"app.kubernetes.io/name": "postgresql"
21+
},
22+
"annotations": {
23+
"pv.kubernetes.io/bind-completed": "yes",
24+
"pv.kubernetes.io/bound-by-controller": "yes",
25+
"volume.beta.kubernetes.io/storage-provisioner": "rancher.io/local-path",
26+
"volume.kubernetes.io/selected-node": "k3d-mycluster-server-0"
27+
},
28+
"finalizers": [
29+
"kubernetes.io/pvc-protection"
30+
],
31+
"managedFields": [
32+
{
33+
"manager": "k3s",
34+
"operation": "Update",
35+
"apiVersion": "v1",
36+
"time": "2023-05-15T21:22:07Z",
37+
"fieldsType": "FieldsV1",
38+
"fieldsV1": {
39+
"f:metadata": {
40+
"f:annotations": {
41+
".": {},
42+
"f:pv.kubernetes.io/bind-completed": {},
43+
"f:pv.kubernetes.io/bound-by-controller": {},
44+
"f:volume.beta.kubernetes.io/storage-provisioner": {},
45+
"f:volume.kubernetes.io/selected-node": {}
46+
},
47+
"f:labels": {
48+
".": {},
49+
"f:app.kubernetes.io/component": {},
50+
"f:app.kubernetes.io/instance": {},
51+
"f:app.kubernetes.io/name": {}
52+
}
53+
},
54+
"f:spec": {
55+
"f:accessModes": {},
56+
"f:resources": {
57+
"f:requests": {
58+
".": {},
59+
"f:storage": {}
60+
}
61+
},
62+
"f:volumeMode": {},
63+
"f:volumeName": {}
64+
}
65+
}
66+
},
67+
{
68+
"manager": "k3s",
69+
"operation": "Update",
70+
"apiVersion": "v1",
71+
"time": "2023-05-15T21:22:07Z",
72+
"fieldsType": "FieldsV1",
73+
"fieldsV1": {
74+
"f:status": {
75+
"f:accessModes": {},
76+
"f:capacity": {
77+
".": {},
78+
"f:storage": {}
79+
},
80+
"f:phase": {}
81+
}
82+
},
83+
"subresource": "status"
84+
}
85+
]
86+
},
87+
"spec": {
88+
"accessModes": [
89+
"ReadWriteOnce"
90+
],
91+
"resources": {
92+
"requests": {
93+
"storage": "8Gi"
94+
}
95+
},
96+
"volumeName": "pvc-b733694c-a969-4763-9960-d3465c9fccd5",
97+
"storageClassName": "local-path",
98+
"volumeMode": "Filesystem"
99+
},
100+
"status": {
101+
"phase": "Bound",
102+
"accessModes": [
103+
"ReadWriteOnce"
104+
],
105+
"capacity": {
106+
"storage": "8Gi"
107+
}
108+
}
109+
},
110+
{
111+
"kind": "PersistentVolumeClaim",
112+
"apiVersion": "v1",
113+
"metadata": {
114+
"name": "redis-data-redis-replicas-0",
115+
"namespace": "default",
116+
"uid": "4e0ec7d1-5ff1-4054-bede-4cbffec0f595",
117+
"resourceVersion": "1450618",
118+
"creationTimestamp": "2023-05-25T15:45:35Z",
119+
"labels": {
120+
"app.kubernetes.io/component": "replica",
121+
"app.kubernetes.io/instance": "some-app",
122+
"app.kubernetes.io/name": "redis"
123+
},
124+
"annotations": {
125+
"pv.kubernetes.io/bind-completed": "yes",
126+
"pv.kubernetes.io/bound-by-controller": "yes",
127+
"volume.beta.kubernetes.io/storage-provisioner": "rancher.io/local-path",
128+
"volume.kubernetes.io/selected-node": "k3d-mycluster-server-0"
129+
},
130+
"finalizers": [
131+
"kubernetes.io/pvc-protection"
132+
],
133+
"managedFields": [
134+
{
135+
"manager": "k3s",
136+
"operation": "Update",
137+
"apiVersion": "v1",
138+
"time": "2023-05-25T15:45:41Z",
139+
"fieldsType": "FieldsV1",
140+
"fieldsV1": {
141+
"f:metadata": {
142+
"f:annotations": {
143+
".": {},
144+
"f:pv.kubernetes.io/bind-completed": {},
145+
"f:pv.kubernetes.io/bound-by-controller": {},
146+
"f:volume.beta.kubernetes.io/storage-provisioner": {},
147+
"f:volume.kubernetes.io/selected-node": {}
148+
},
149+
"f:labels": {
150+
".": {},
151+
"f:app.kubernetes.io/component": {},
152+
"f:app.kubernetes.io/instance": {},
153+
"f:app.kubernetes.io/name": {}
154+
}
155+
},
156+
"f:spec": {
157+
"f:accessModes": {},
158+
"f:resources": {
159+
"f:requests": {
160+
".": {},
161+
"f:storage": {}
162+
}
163+
},
164+
"f:volumeMode": {},
165+
"f:volumeName": {}
166+
}
167+
}
168+
},
169+
{
170+
"manager": "k3s",
171+
"operation": "Update",
172+
"apiVersion": "v1",
173+
"time": "2023-05-25T15:45:41Z",
174+
"fieldsType": "FieldsV1",
175+
"fieldsV1": {
176+
"f:status": {
177+
"f:accessModes": {},
178+
"f:capacity": {
179+
".": {},
180+
"f:storage": {}
181+
},
182+
"f:phase": {}
183+
}
184+
},
185+
"subresource": "status"
186+
}
187+
]
188+
},
189+
"spec": {
190+
"accessModes": [
191+
"ReadWriteMany"
192+
],
193+
"resources": {
194+
"requests": {
195+
"storage": "8Gi"
196+
}
197+
},
198+
"volumeName": "pvc-4e0ec7d1-5ff1-4054-bede-4cbffec0f595",
199+
"storageClassName": "local-path",
200+
"volumeMode": "Filesystem"
201+
},
202+
"status": {
203+
"phase": "Bound",
204+
"accessModes": [
205+
"ReadWriteMany"
206+
],
207+
"capacity": {
208+
"storage": "8Gi"
209+
}
210+
}
211+
}
212+
]
213+
}

pkg/analyze/kube_resource.go

Lines changed: 80 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,26 @@ import (
1111
"github.com/replicatedhq/troubleshoot/pkg/constants"
1212
iutils "github.com/replicatedhq/troubleshoot/pkg/interfaceutils"
1313
"gopkg.in/yaml.v2"
14+
"k8s.io/klog/v2"
1415
)
1516

1617
var Filemap = map[string]string{
17-
"Deployment": constants.CLUSTER_RESOURCES_DEPLOYMENTS,
18-
"StatefulSet": constants.CLUSTER_RESOURCES_STATEFULSETS,
19-
"NetworkPolicy": constants.CLUSTER_RESOURCES_NETWORK_POLICY,
20-
"Pod": constants.CLUSTER_RESOURCES_PODS,
21-
"Ingress": constants.CLUSTER_RESOURCES_INGRESS,
22-
"Service": constants.CLUSTER_RESOURCES_SERVICES,
23-
"ResourceQuota": constants.CLUSTER_RESOURCES_RESOURCE_QUOTA,
24-
"Job": constants.CLUSTER_RESOURCES_JOBS,
25-
"PersistentVoumeClaim": constants.CLUSTER_RESOURCES_PVCS,
26-
"pvc": constants.CLUSTER_RESOURCES_PVCS,
27-
"ReplicaSet": constants.CLUSTER_RESOURCES_REPLICASETS,
28-
"Namespace": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_NAMESPACES),
29-
"PersistentVolume": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_PVS),
30-
"pv": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_PVS),
31-
"Node": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_NODES),
32-
"StorageClass": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_STORAGE_CLASS),
18+
"Deployment": constants.CLUSTER_RESOURCES_DEPLOYMENTS,
19+
"StatefulSet": constants.CLUSTER_RESOURCES_STATEFULSETS,
20+
"NetworkPolicy": constants.CLUSTER_RESOURCES_NETWORK_POLICY,
21+
"Pod": constants.CLUSTER_RESOURCES_PODS,
22+
"Ingress": constants.CLUSTER_RESOURCES_INGRESS,
23+
"Service": constants.CLUSTER_RESOURCES_SERVICES,
24+
"ResourceQuota": constants.CLUSTER_RESOURCES_RESOURCE_QUOTA,
25+
"Job": constants.CLUSTER_RESOURCES_JOBS,
26+
"PersistentVolumeClaim": constants.CLUSTER_RESOURCES_PVCS,
27+
"pvc": constants.CLUSTER_RESOURCES_PVCS,
28+
"ReplicaSet": constants.CLUSTER_RESOURCES_REPLICASETS,
29+
"Namespace": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_NAMESPACES),
30+
"PersistentVolume": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_PVS),
31+
"pv": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_PVS),
32+
"Node": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_NODES),
33+
"StorageClass": fmt.Sprintf("%s.json", constants.CLUSTER_RESOURCES_STORAGE_CLASS),
3334
}
3435

3536
type AnalyzeClusterResource struct {
@@ -108,15 +109,28 @@ func FindResource(kind string, clusterScoped bool, namespace string, name string
108109
}
109110

110111
func (a *AnalyzeClusterResource) analyzeResource(analyzer *troubleshootv1beta2.ClusterResource, getFileContents getCollectedFileContents) (*AnalyzeResult, error) {
111-
112112
selected, err := FindResource(analyzer.Kind, analyzer.ClusterScoped, analyzer.Namespace, analyzer.Name, getFileContents)
113113
if err != nil {
114-
return nil, errors.Wrap(err, "failed to find resource")
114+
klog.Errorf("failed to find resource: %v", err)
115+
return &AnalyzeResult{
116+
Title: a.Title(),
117+
IconKey: "kubernetes_text_analyze",
118+
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
119+
IsFail: true,
120+
Message: "resource does not exist",
121+
}, nil
115122
}
116123

117124
actual, err := iutils.GetAtPath(selected, analyzer.YamlPath)
118125
if err != nil {
119-
return nil, errors.Wrapf(err, "failed to get object at path: %s", analyzer.YamlPath)
126+
klog.Errorf("invalid yaml path: %s for kind: %s: %v", analyzer.YamlPath, analyzer.Kind, err)
127+
return &AnalyzeResult{
128+
Title: a.Title(),
129+
IconKey: "kubernetes_text_analyze",
130+
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
131+
IsFail: true,
132+
Message: "YAML path provided is invalid",
133+
}, nil
120134
}
121135

122136
var expected interface{}
@@ -125,15 +139,58 @@ func (a *AnalyzeClusterResource) analyzeResource(analyzer *troubleshootv1beta2.C
125139
return nil, errors.Wrap(err, "failed to parse expected value as yaml doc")
126140
}
127141

128-
result := &AnalyzeResult{
142+
actualYAML, err := yaml.Marshal(actual)
143+
if err != nil {
144+
return nil, errors.Wrap(err, "failed to marshal actual value")
145+
}
146+
147+
if analyzer.ExpectedValue != "" {
148+
result, err := analyzeValue(expected, actual, analyzer.Outcomes, a.Title())
149+
if err != nil {
150+
return nil, err
151+
}
152+
if result != nil {
153+
return result, nil
154+
}
155+
} else if analyzer.RegexPattern != "" {
156+
result, err := analyzeRegexPattern(analyzer.RegexPattern, actualYAML, analyzer.Outcomes, a.Title())
157+
if err != nil {
158+
return nil, err
159+
}
160+
if result != nil {
161+
return result, nil
162+
}
163+
} else if analyzer.RegexGroups != "" {
164+
result, err := analyzeRegexGroups(analyzer.RegexGroups, actualYAML, analyzer.Outcomes, a.Title())
165+
if err != nil {
166+
return nil, err
167+
}
168+
if result != nil {
169+
return result, nil
170+
}
171+
}
172+
173+
return &AnalyzeResult{
129174
Title: a.Title(),
130175
IconKey: "kubernetes_text_analyze",
131176
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
132-
}
177+
IsFail: true,
178+
Message: "Invalid analyzer",
179+
}, nil
180+
}
181+
182+
func analyzeValue(expected interface{}, actual interface{}, outcomes []*troubleshootv1beta2.Outcome, checkName string) (*AnalyzeResult, error) {
183+
var err error
133184

134185
equal := reflect.DeepEqual(actual, expected)
135186

136-
for _, outcome := range analyzer.Outcomes {
187+
result := &AnalyzeResult{
188+
Title: checkName,
189+
IconKey: "kubernetes_text_analyze",
190+
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg?w=13&h=16",
191+
}
192+
193+
for _, outcome := range outcomes {
137194
if outcome.Fail != nil {
138195
when := false
139196
if outcome.Fail.When != "" {
@@ -183,7 +240,7 @@ func (a *AnalyzeClusterResource) analyzeResource(analyzer *troubleshootv1beta2.C
183240
}
184241

185242
return &AnalyzeResult{
186-
Title: a.Title(),
243+
Title: checkName,
187244
IconKey: "kubernetes_text_analyze",
188245
IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg",
189246
IsFail: true,

0 commit comments

Comments
 (0)