Skip to content

Commit 518f826

Browse files
committed
Regex analyzer
1 parent c962dc5 commit 518f826

File tree

10 files changed

+241
-13
lines changed

10 files changed

+241
-13
lines changed

cmd/preflight/cli/run.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func runPreflights(v *viper.Viper, arg string) error {
6060

6161
preflight := troubleshootv1beta1.Preflight{}
6262
if err := yaml.Unmarshal([]byte(preflightContent), &preflight); err != nil {
63-
return fmt.Errorf("unable to parse %s as a preflight", arg)
63+
return fmt.Errorf("unable to parse %s as a preflight, error: %s", arg, err.Error())
6464
}
6565

6666
s := spin.New()
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
apiVersion: troubleshoot.replicated.com/v1beta1
2+
kind: Preflight
3+
metadata:
4+
name: postgres-connection-valid
5+
spec:
6+
collectors:
7+
- run:
8+
collectorName: "run-ping"
9+
image: busybox:1
10+
namespace: default
11+
command: [ping]
12+
args: [-w, "5", www.google.com]
13+
imagePullPolicy: IfNotPresent
14+
analyzers:
15+
- regex:
16+
collectorName: "run-ping"
17+
checkName: "Ping google with low packet loss?"
18+
expression: "(?P<Transmitted>\\d+) packets? transmitted, (?P<Received>\\d+) packets? received, (?P<Loss>\\d+\\.?\\d+)% packet loss"
19+
outcomes:
20+
- pass:
21+
when: "Loss < 20"
22+
message: "Solid connection to google.com"
23+
- fail:
24+
message: "Really high packet loss"

pkg/analyze/analyzer.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,12 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont
7979
}
8080
return analyzeDistribution(analyzer.Distribution, getFile)
8181
}
82+
if analyzer.RegEx != nil {
83+
if analyzer.RegEx.Exclude {
84+
return nil, nil
85+
}
86+
return analyzeRegex(analyzer.RegEx, getFile)
87+
}
8288

8389
return nil, errors.New("invalid analyzer")
8490
}

pkg/analyze/regex.go

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
package analyzer
2+
3+
import (
4+
"fmt"
5+
"path"
6+
"regexp"
7+
"strconv"
8+
"strings"
9+
10+
"github.com/pkg/errors"
11+
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
12+
)
13+
14+
func analyzeRegex(analyzer *troubleshootv1beta1.RegEx, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
15+
contents, err := getCollectedFileContents(path.Join(analyzer.CollectorName, analyzer.CollectorName+".txt"))
16+
if err != nil {
17+
return nil, errors.Wrap(err, "failed to get file contents for regex")
18+
}
19+
20+
expression := regexp.MustCompile(analyzer.Expression)
21+
match := expression.FindStringSubmatch(string(contents))
22+
23+
result := &AnalyzeResult{
24+
Title: analyzer.CheckName,
25+
}
26+
// to avoid empty strings in the UI..,.
27+
if result.Title == "" {
28+
result.Title = analyzer.CollectorName
29+
}
30+
31+
foundMatches := map[string]string{}
32+
for i, name := range expression.SubexpNames() {
33+
if i != 0 && name != "" {
34+
foundMatches[name] = match[i]
35+
}
36+
}
37+
38+
// allow fallthrough
39+
for _, outcome := range analyzer.Outcomes {
40+
if outcome.Fail != nil {
41+
if outcome.Fail.When == "" {
42+
result.IsFail = true
43+
result.Message = outcome.Fail.Message
44+
result.URI = outcome.Fail.URI
45+
46+
return result, nil
47+
}
48+
49+
isMatch, err := compareRegex(outcome.Fail.When, foundMatches)
50+
if err != nil {
51+
return result, errors.Wrap(err, "failed to compare regex fail conditional")
52+
}
53+
54+
if isMatch {
55+
result.IsFail = true
56+
result.Message = outcome.Fail.Message
57+
result.URI = outcome.Fail.URI
58+
59+
return result, nil
60+
}
61+
} else if outcome.Warn != nil {
62+
if outcome.Warn.When == "" {
63+
result.IsWarn = true
64+
result.Message = outcome.Warn.Message
65+
result.URI = outcome.Warn.URI
66+
67+
return result, nil
68+
}
69+
70+
isMatch, err := compareRegex(outcome.Warn.When, foundMatches)
71+
if err != nil {
72+
return result, errors.Wrap(err, "failed to compare regex warn conditional")
73+
}
74+
75+
if isMatch {
76+
result.IsWarn = true
77+
result.Message = outcome.Warn.Message
78+
result.URI = outcome.Warn.URI
79+
80+
return result, nil
81+
}
82+
} else if outcome.Pass != nil {
83+
if outcome.Pass.When == "" {
84+
result.IsPass = true
85+
result.Message = outcome.Pass.Message
86+
result.URI = outcome.Pass.URI
87+
88+
return result, nil
89+
}
90+
91+
isMatch, err := compareRegex(outcome.Pass.When, foundMatches)
92+
if err != nil {
93+
return result, errors.Wrap(err, "failed to compare regex pass conditional")
94+
}
95+
96+
if isMatch {
97+
result.IsPass = true
98+
result.Message = outcome.Pass.Message
99+
result.URI = outcome.Pass.URI
100+
101+
return result, nil
102+
}
103+
}
104+
}
105+
106+
return result, nil
107+
}
108+
109+
func compareRegex(conditional string, foundMatches map[string]string) (bool, error) {
110+
parts := strings.Split(strings.TrimSpace(conditional), " ")
111+
112+
if len(parts) != 3 {
113+
return false, errors.New("unable to parse regex conditional")
114+
}
115+
116+
lookForMatchName := parts[0]
117+
operator := parts[1]
118+
lookForValue := parts[2]
119+
120+
foundValue, ok := foundMatches[lookForMatchName]
121+
if !ok {
122+
// not an error, just wasn't matched
123+
return false, nil
124+
}
125+
126+
// if the value side of the conditional is an int, we assume it's an int
127+
lookForValueInt, err := strconv.Atoi(lookForValue)
128+
129+
fmt.Printf("lookForMatchName = %s, operator = %s lookForValue = %s foundValue = %s\n", lookForMatchName, operator, lookForValue, foundValue)
130+
if err == nil {
131+
foundValueInt, err := strconv.Atoi(foundValue)
132+
if err != nil {
133+
// not an error but maybe it should be...
134+
return false, nil
135+
}
136+
137+
switch operator {
138+
case "=":
139+
fallthrough
140+
case "==":
141+
fallthrough
142+
case "===":
143+
return foundValueInt == lookForValueInt, nil
144+
145+
case "<":
146+
return foundValueInt < lookForValueInt, nil
147+
148+
case ">":
149+
return foundValueInt > lookForValueInt, nil
150+
151+
case "<=":
152+
return foundValueInt <= lookForValueInt, nil
153+
154+
case ">=":
155+
return foundValueInt >= lookForValueInt, nil
156+
}
157+
} else {
158+
// all we can support is "=" and "==" and "===" for now
159+
if operator != "=" && operator != "==" && operator != "===" {
160+
return false, errors.New("unexpected operator in regex comparator")
161+
}
162+
163+
return foundValue == lookForValue, nil
164+
}
165+
166+
return false, nil
167+
}

pkg/apis/troubleshoot/v1beta1/analyzer_shared.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ type Distribution struct {
7474
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
7575
}
7676

77+
type RegEx struct {
78+
AnalyzeMeta `json:",inline" yaml:",inline"`
79+
CollectorName string `json:"collectorName" yaml:"collectorName"`
80+
Expression string `json:"expression" yaml:"expression"`
81+
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
82+
}
83+
7784
type AnalyzeMeta struct {
7885
CheckName string `json:"checkName,omitempty" yaml:"checkName,omitempty"`
7986
Exclude bool `json:"exclude,omitempty" yaml:"exclude,omitempty"`
@@ -90,4 +97,5 @@ type Analyze struct {
9097
StatefulsetStatus *StatefulsetStatus `json:"statefulsetStatus,omitempty" yaml:"statefulsetStatus,omitempty"`
9198
ContainerRuntime *ContainerRuntime `json:"containerRuntime,omitempty" yaml:"containerRuntime,omitempty"`
9299
Distribution *Distribution `json:"distribution,omitempty" yaml:"distribution,omitempty"`
100+
RegEx *RegEx `json:"regex,omitempty" yaml:"regex,omitempty"`
93101
}

pkg/apis/troubleshoot/v1beta1/collector_shared.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ type Data struct {
4343

4444
type Run struct {
4545
CollectorMeta `json:",inline" yaml:",inline"`
46-
Name string `json:"name,omitempty" yaml:"name,omitempty"`
4746
Namespace string `json:"namespace" yaml:"namespace"`
4847
Image string `json:"image" yaml:"image"`
4948
Command []string `json:"command,omitempty" yaml:"command,omitempty"`
@@ -54,7 +53,6 @@ type Run struct {
5453

5554
type Exec struct {
5655
CollectorMeta `json:",inline" yaml:",inline"`
57-
Name string `json:"name,omitempty" yaml:"name,omitempty"`
5856
Selector []string `json:"selector" yaml:"selector"`
5957
Namespace string `json:"namespace" yaml:"namespace"`
6058
ContainerName string `json:"containerName,omitempty" yaml:"containerName,omitempty"`
@@ -65,7 +63,6 @@ type Exec struct {
6563

6664
type Copy struct {
6765
CollectorMeta `json:",inline" yaml:",inline"`
68-
Name string `json:"name,omitempty" yaml:"name,omitempty"`
6966
Selector []string `json:"selector" yaml:"selector"`
7067
Namespace string `json:"namespace" yaml:"namespace"`
7168
ContainerPath string `json:"containerPath" yaml:"containerPath"`

pkg/apis/troubleshoot/v1beta1/zz_generated.deepcopy.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ func (in *Analyze) DeepCopyInto(out *Analyze) {
102102
*out = new(Distribution)
103103
(*in).DeepCopyInto(*out)
104104
}
105+
if in.RegEx != nil {
106+
in, out := &in.RegEx, &out.RegEx
107+
*out = new(RegEx)
108+
(*in).DeepCopyInto(*out)
109+
}
105110
}
106111

107112
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Analyze.
@@ -1359,6 +1364,33 @@ func (in *Put) DeepCopy() *Put {
13591364
return out
13601365
}
13611366

1367+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
1368+
func (in *RegEx) DeepCopyInto(out *RegEx) {
1369+
*out = *in
1370+
out.AnalyzeMeta = in.AnalyzeMeta
1371+
if in.Outcomes != nil {
1372+
in, out := &in.Outcomes, &out.Outcomes
1373+
*out = make([]*Outcome, len(*in))
1374+
for i := range *in {
1375+
if (*in)[i] != nil {
1376+
in, out := &(*in)[i], &(*out)[i]
1377+
*out = new(Outcome)
1378+
(*in).DeepCopyInto(*out)
1379+
}
1380+
}
1381+
}
1382+
}
1383+
1384+
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RegEx.
1385+
func (in *RegEx) DeepCopy() *RegEx {
1386+
if in == nil {
1387+
return nil
1388+
}
1389+
out := new(RegEx)
1390+
in.DeepCopyInto(out)
1391+
return out
1392+
}
1393+
13621394
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
13631395
func (in *ResultRequest) DeepCopyInto(out *ResultRequest) {
13641396
*out = *in

pkg/collect/copy.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func Copy(ctx *Context, copyCollector *troubleshootv1beta1.Copy) ([]byte, error)
3434

3535
if len(pods) > 0 {
3636
for _, pod := range pods {
37-
bundlePath := filepath.Join(copyCollector.Name, pod.Namespace, pod.Name)
37+
bundlePath := filepath.Join(copyCollector.CollectorName, pod.Namespace, pod.Name)
3838

3939
files, copyErrors := copyFiles(ctx, client, pod, copyCollector)
4040
if len(copyErrors) > 0 {
@@ -136,9 +136,6 @@ func (c CopyOutput) Redact() (CopyOutput, error) {
136136
}
137137

138138
func getCopyErrosFileName(copyCollector *troubleshootv1beta1.Copy) string {
139-
if len(copyCollector.Name) > 0 {
140-
return fmt.Sprintf("%s-errors.json", copyCollector.Name)
141-
}
142139
if len(copyCollector.CollectorName) > 0 {
143140
return fmt.Sprintf("%s-errors.json", copyCollector.CollectorName)
144141
}

pkg/collect/exec.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ func execWithoutTimeout(ctx *Context, execCollector *troubleshootv1beta1.Exec) (
7070
for _, pod := range pods {
7171
stdout, stderr, execErrors := getExecOutputs(ctx, client, pod, execCollector)
7272

73-
bundlePath := filepath.Join(execCollector.Name, pod.Namespace, pod.Name)
73+
bundlePath := filepath.Join(execCollector.CollectorName, pod.Namespace, pod.Name)
7474
if len(stdout) > 0 {
7575
execOutput[filepath.Join(bundlePath, execCollector.CollectorName+"-stdout.txt")] = stdout
7676
}
@@ -158,9 +158,6 @@ func (r ExecOutput) Redact() (ExecOutput, error) {
158158
}
159159

160160
func getExecErrosFileName(execCollector *troubleshootv1beta1.Exec) string {
161-
if len(execCollector.Name) > 0 {
162-
return fmt.Sprintf("%s-errors.json", execCollector.Name)
163-
}
164161
if len(execCollector.CollectorName) > 0 {
165162
return fmt.Sprintf("%s-errors.json", execCollector.CollectorName)
166163
}

pkg/collect/run.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ func runWithoutTimeout(ctx *Context, pod *corev1.Pod, runCollector *troubleshoot
8585
limits := troubleshootv1beta1.LogLimits{
8686
MaxLines: 10000,
8787
}
88-
podLogs, err := getPodLogs(client, *pod, runCollector.Name, "", &limits, true)
88+
podLogs, err := getPodLogs(client, *pod, runCollector.CollectorName, "", &limits, true)
8989

9090
for k, v := range podLogs {
9191
runOutput[k] = v

0 commit comments

Comments
 (0)