diff --git a/pkg/analyze/analyzer.go b/pkg/analyze/analyzer.go index bb71c660..8e219c28 100644 --- a/pkg/analyze/analyzer.go +++ b/pkg/analyze/analyzer.go @@ -79,6 +79,9 @@ func Analyze(analyzer *troubleshootv1beta1.Analyze, getFile getCollectedFileCont } return analyzeDistribution(analyzer.Distribution, getFile) } + if analyzer.TextAnalyze != nil { + return analyzeTextAnalyze(analyzer.TextAnalyze, getFile) + } return nil, errors.New("invalid analyzer") } diff --git a/pkg/analyze/text_analyze.go b/pkg/analyze/text_analyze.go new file mode 100644 index 00000000..4ae4f44f --- /dev/null +++ b/pkg/analyze/text_analyze.go @@ -0,0 +1,47 @@ +package analyzer + +import ( + "path" + "regexp" + + "github.com/pkg/errors" + troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1" +) + +func analyzeTextAnalyze(analyzer *troubleshootv1beta1.TextAnalyze, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) { + fullPath := path.Join(analyzer.CollectorName, analyzer.FileName) + collected, err := getCollectedFileContents(fullPath) + if err != nil { + return nil, errors.Wrapf(err, "failed to read collected file name: %s", fullPath) + } + + re, err := regexp.Compile(analyzer.RegexPattern) + if err != nil { + return nil, errors.Wrapf(err, "failed to compile regex: %s", analyzer.RegexPattern) + } + + var failOutcome *troubleshootv1beta1.Outcome + var passOutcome *troubleshootv1beta1.Outcome + for _, outcome := range analyzer.Outcomes { + if outcome.Fail != nil { + failOutcome = outcome + } else if outcome.Pass != nil { + passOutcome = outcome + } + } + + if re.MatchString(string(collected)) { + return &AnalyzeResult{ + Title: analyzer.CheckName, + IsPass: true, + Message: passOutcome.Pass.Message, + URI: passOutcome.Pass.URI, + }, nil + } + return &AnalyzeResult{ + Title: analyzer.CheckName, + IsFail: true, + Message: failOutcome.Fail.Message, + URI: failOutcome.Fail.URI, + }, nil +} diff --git a/pkg/analyze/text_analyze_test.go b/pkg/analyze/text_analyze_test.go new file mode 100644 index 00000000..425f857a --- /dev/null +++ b/pkg/analyze/text_analyze_test.go @@ -0,0 +1,212 @@ +package analyzer + +import ( + "fmt" + "testing" + + troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_textAnalyze(t *testing.T) { + tests := []struct { + name string + analyzer troubleshootv1beta1.TextAnalyze + expectResult AnalyzeResult + files map[string][]byte + }{ + { + name: "success case 1", + analyzer: troubleshootv1beta1.TextAnalyze{ + Outcomes: []*troubleshootv1beta1.Outcome{ + { + Pass: &troubleshootv1beta1.SingleOutcome{ + Message: "pass", + }, + }, + { + Fail: &troubleshootv1beta1.SingleOutcome{ + Message: "fail", + }, + }, + }, + CollectorName: "text-collector-1", + FileName: "cfile-1.txt", + RegexPattern: "succeeded", + }, + expectResult: AnalyzeResult{ + IsPass: true, + IsWarn: false, + IsFail: false, + Message: "pass", + }, + files: map[string][]byte{ + "text-collector-1/cfile-1.txt": []byte("Yes it all succeeded"), + }, + }, + { + name: "failure case 1", + analyzer: troubleshootv1beta1.TextAnalyze{ + Outcomes: []*troubleshootv1beta1.Outcome{ + { + Pass: &troubleshootv1beta1.SingleOutcome{ + Message: "success", + }, + }, + { + Fail: &troubleshootv1beta1.SingleOutcome{ + Message: "fail", + }, + }, + }, + CollectorName: "text-collector-2", + FileName: "cfile-2.txt", + RegexPattern: "succeeded", + }, + expectResult: AnalyzeResult{ + IsPass: false, + IsWarn: false, + IsFail: true, + Message: "fail", + }, + files: map[string][]byte{ + "text-collector-2/cfile-2.txt": []byte(""), + }, + }, + { + name: "success case 2", + analyzer: troubleshootv1beta1.TextAnalyze{ + Outcomes: []*troubleshootv1beta1.Outcome{ + { + Pass: &troubleshootv1beta1.SingleOutcome{ + Message: "success", + }, + }, + { + Fail: &troubleshootv1beta1.SingleOutcome{ + Message: "fail", + }, + }, + }, + CollectorName: "text-collector-3", + FileName: "cfile-3.txt", + RegexPattern: "", + }, + expectResult: AnalyzeResult{ + IsPass: true, + IsWarn: false, + IsFail: false, + Message: "success", + }, + files: map[string][]byte{ + "text-collector-3/cfile-3.txt": []byte("Connection to service succeeded"), + }, + }, + { + name: "success case 3", + analyzer: troubleshootv1beta1.TextAnalyze{ + Outcomes: []*troubleshootv1beta1.Outcome{ + { + Pass: &troubleshootv1beta1.SingleOutcome{ + Message: "success", + }, + }, + { + Fail: &troubleshootv1beta1.SingleOutcome{ + Message: "fail", + }, + }, + }, + CollectorName: "text-collector-5", + FileName: "cfile-5.txt", + RegexPattern: "([a-zA-Z0-9\\-_:*\\s])*succe([a-zA-Z0-9\\-_:*\\s!])*", + }, + expectResult: AnalyzeResult{ + IsPass: true, + IsWarn: false, + IsFail: false, + Message: "success", + }, + files: map[string][]byte{ + "text-collector-5/cfile-5.txt": []byte("Connection to service succeeded!"), + }, + }, + { + name: "failure case 3", + analyzer: troubleshootv1beta1.TextAnalyze{ + Outcomes: []*troubleshootv1beta1.Outcome{ + { + Pass: &troubleshootv1beta1.SingleOutcome{ + Message: "success", + }, + }, + { + Fail: &troubleshootv1beta1.SingleOutcome{ + Message: "fail", + }, + }, + }, + CollectorName: "text-collector-4", + FileName: "cfile-4.txt", + RegexPattern: "succeeded", + }, + expectResult: AnalyzeResult{ + IsPass: false, + IsWarn: false, + IsFail: true, + Message: "fail", + }, + files: map[string][]byte{ + "text-collector-4/cfile-4.txt": []byte("A different message"), + }, + }, + { + name: "failure case 4", + analyzer: troubleshootv1beta1.TextAnalyze{ + Outcomes: []*troubleshootv1beta1.Outcome{ + { + Pass: &troubleshootv1beta1.SingleOutcome{ + Message: "success", + }, + }, + { + Fail: &troubleshootv1beta1.SingleOutcome{ + Message: "fail", + }, + }, + }, + CollectorName: "text-collector-6", + FileName: "cfile-6.txt", + RegexPattern: "([a-zA-Z0-9\\-_:*\\s])*succe([a-zA-Z0-9\\-_:*\\s!])*", + }, + expectResult: AnalyzeResult{ + IsPass: false, + IsWarn: false, + IsFail: true, + Message: "fail", + }, + files: map[string][]byte{ + "text-collector-6/cfile-6.txt": []byte("A different message"), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + req := require.New(t) + + getFiles := func(n string) ([]byte, error) { + val, ok := test.files[n] + if !ok { + return nil, fmt.Errorf("File not found: %s", n) + } + return val, nil + } + + actual, err := analyzeTextAnalyze(&test.analyzer, getFiles) + req.NoError(err) + assert.Equal(t, &test.expectResult, actual) + }) + } +} diff --git a/pkg/apis/troubleshoot/v1beta1/analyzer_shared.go b/pkg/apis/troubleshoot/v1beta1/analyzer_shared.go index 29b6b955..bc39e223 100644 --- a/pkg/apis/troubleshoot/v1beta1/analyzer_shared.go +++ b/pkg/apis/troubleshoot/v1beta1/analyzer_shared.go @@ -74,6 +74,14 @@ type Distribution struct { Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"` } +type TextAnalyze struct { + AnalyzeMeta `json:",inline" yaml:",inline"` + CollectorName string `json:"collectorName,omitempty" yaml:"collectorName,omitempty"` + FileName string `json:"fileName,omitempty" yaml:"fileName,omitempty"` + RegexPattern string `json:"regex,omitempty" yaml:"regex,omitempty"` + Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"` +} + type AnalyzeMeta struct { CheckName string `json:"checkName,omitempty" yaml:"checkName,omitempty"` Exclude bool `json:"exclude,omitempty" yaml:"exclude,omitempty"` @@ -90,4 +98,5 @@ type Analyze struct { StatefulsetStatus *StatefulsetStatus `json:"statefulsetStatus,omitempty" yaml:"statefulsetStatus,omitempty"` ContainerRuntime *ContainerRuntime `json:"containerRuntime,omitempty" yaml:"containerRuntime,omitempty"` Distribution *Distribution `json:"distribution,omitempty" yaml:"distribution,omitempty"` + TextAnalyze *TextAnalyze `json:"textAnalyze,omitempty" yaml:"textAnalyze,omitempty"` } diff --git a/pkg/apis/troubleshoot/v1beta1/zz_generated.deepcopy.go b/pkg/apis/troubleshoot/v1beta1/zz_generated.deepcopy.go index 6d6455ce..ae9c181b 100644 --- a/pkg/apis/troubleshoot/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/troubleshoot/v1beta1/zz_generated.deepcopy.go @@ -102,6 +102,11 @@ func (in *Analyze) DeepCopyInto(out *Analyze) { *out = new(Distribution) (*in).DeepCopyInto(*out) } + if in.TextAnalyze != nil { + in, out := &in.TextAnalyze, &out.TextAnalyze + *out = new(TextAnalyze) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Analyze. @@ -1499,3 +1504,30 @@ func (in *SupportBundleVersion) DeepCopy() *SupportBundleVersion { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TextAnalyze) DeepCopyInto(out *TextAnalyze) { + *out = *in + out.AnalyzeMeta = in.AnalyzeMeta + if in.Outcomes != nil { + in, out := &in.Outcomes, &out.Outcomes + *out = make([]*Outcome, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(Outcome) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TextAnalyze. +func (in *TextAnalyze) DeepCopy() *TextAnalyze { + if in == nil { + return nil + } + out := new(TextAnalyze) + in.DeepCopyInto(out) + return out +}