Repository: replicatedhq/troubleshoot
Pull request #577 (2022-05-18 10:34:14)

Template message in outcome block when using textAnalyzer
I started playing with the `kubectl trouble-shoot` plugin because I saw it yesterday on kubecon+cloudnativecon 😎 Thanks for presenting it 😃

One feature that was missing for me when using the `textAnalyzer` to parse some log files, is to use the **reg ex groups** also in the **message** which will be displayed to the user.

This is how the feature looks like (the words `wrong-eventtypeprefix` and `default` are coming from a reg ex group match):
<img width="598" alt="Screenshot 2022-05-18 at 10 59 20 AM" src="https://user-images.githubusercontent.com/8013145/169000570-28c7189e-1c89-4f7b-9069-bd3cddfcc6ee.png">

CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/replicatedhq/troubleshoot?pullRequest=577) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/replicatedhq/troubleshoot?pullRequest=577) before we can accept your contribution.<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/replicatedhq/troubleshoot?pullRequest=577) it.</sub>

Patch:
diff --git a/pkg/analyze/text_analyze.go b/pkg/analyze/text_analyze.go
index f7119084..2179ac3e 100644
--- a/pkg/analyze/text_analyze.go
+++ b/pkg/analyze/text_analyze.go
@@ -1,11 +1,13 @@
 package analyzer
 
 import (
+	"bytes"
 	"fmt"
 	"path/filepath"
 	"regexp"
 	"strconv"
 	"strings"
+	"text/template"
 
 	"github.com/pkg/errors"
 	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
@@ -169,7 +171,11 @@ func analyzeRegexGroups(pattern string, collected []byte, outcomes []*troublesho
 
 			if isMatch {
 				result.IsFail = true
-				result.Message = outcome.Fail.Message
+				tplMessage, err := templateRegExGroup(outcome.Fail.Message, foundMatches)
+				if err != nil {
+					return result, errors.Wrap(err, "failed to template message in outcome.Fail block")
+				}
+				result.Message = tplMessage
 				result.URI = outcome.Fail.URI
 
 				return result, nil
@@ -182,7 +188,11 @@ func analyzeRegexGroups(pattern string, collected []byte, outcomes []*troublesho
 
 			if isMatch {
 				result.IsWarn = true
-				result.Message = outcome.Warn.Message
+				tplMessage, err := templateRegExGroup(outcome.Warn.Message, foundMatches)
+				if err != nil {
+					return result, errors.Wrap(err, "failed to template message in outcome.Warn block")
+				}
+				result.Message = tplMessage
 				result.URI = outcome.Warn.URI
 
 				return result, nil
@@ -195,7 +205,11 @@ func analyzeRegexGroups(pattern string, collected []byte, outcomes []*troublesho
 
 			if isMatch {
 				result.IsPass = true
-				result.Message = outcome.Pass.Message
+				tplMessage, err := templateRegExGroup(outcome.Pass.Message, foundMatches)
+				if err != nil {
+					return result, errors.Wrap(err, "failed to template message in outcome.Pass block")
+				}
+				result.Message = tplMessage
 				result.URI = outcome.Pass.URI
 
 				return result, nil
@@ -206,6 +220,20 @@ func analyzeRegexGroups(pattern string, collected []byte, outcomes []*troublesho
 	return result, nil
 }
 
+// templateRegExGroup takes a tpl and replaces the variables using matches.
+func templateRegExGroup(tpl string, matches map[string]string) (string, error) {
+	t, err := template.New("").Parse(tpl)
+	if err != nil {
+		return "", err
+	}
+	var msg bytes.Buffer
+	err = t.Execute(&msg, matches)
+	if err != nil {
+		return "", err
+	}
+	return msg.String(), nil
+}
+
 func compareRegex(conditional string, foundMatches map[string]string) (bool, error) {
 	if conditional == "" {
 		return true, nil


Test patch:
diff --git a/pkg/analyze/text_analyze_test.go b/pkg/analyze/text_analyze_test.go
index 51f036f0..5fc4527e 100644
--- a/pkg/analyze/text_analyze_test.go
+++ b/pkg/analyze/text_analyze_test.go
@@ -477,6 +477,111 @@ func Test_textAnalyze(t *testing.T) {
 				"text-collector-1/cfile-1.txt": []byte("value: 2\nother: 10"),
 			},
 		},
+		// This test ensures that the Outcomes.Pass.Message can be templated using the findings of the regular expression groups.
+		{
+			name: "Outcome pass message is templated with regex groups",
+			analyzer: troubleshootv1beta2.TextAnalyze{
+				Outcomes: []*troubleshootv1beta2.Outcome{
+					{
+						Pass: &troubleshootv1beta2.SingleOutcome{
+							When:    `Feature == insert-feature-name-here`,
+							Message: "Feature {{ .Feature }} is enabled for CR {{ .CRName }} in namespace {{ .Namespace }}",
+						},
+					},
+				},
+				CollectorName: "text-collector-templated-regex-message",
+				FileName:      "cfile-1.txt",
+				RegexGroups:   `"name":\s*"(?P<CRName>.*?)".*namespace":\s*"(?P<Namespace>.*?)".*feature":\s*.*"(?P<Feature>insert-feature-name-here.*?)"`,
+			},
+			expectResult: []AnalyzeResult{
+				{
+					IsPass:  true,
+					IsWarn:  false,
+					IsFail:  false,
+					Title:   "text-collector-templated-regex-message",
+					Message: "Feature insert-feature-name-here is enabled for CR insert-cr-name-here in namespace default",
+					IconKey: "kubernetes_text_analyze",
+					IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg?w=13&h=16",
+				},
+			},
+			files: map[string][]byte{
+				"text-collector-templated-regex-message/cfile-1.txt": []byte(`{"level":"INFO","timestamp":"2022-05-17T20:37:41Z","caller":"controller/controller.go:317","message":"Feature enabled","context":{"name":"insert-cr-name-here","namespace":"default","feature":"insert-feature-name-here"}}`),
+			},
+		},
+		// This test ensures that the Outcomes.Warn.Message can be templated using the findings of the regular expression groups.
+		{
+			name: "Outcome warn message is templated with regex groups",
+			analyzer: troubleshootv1beta2.TextAnalyze{
+				Outcomes: []*troubleshootv1beta2.Outcome{
+					{
+						Pass: &troubleshootv1beta2.SingleOutcome{
+							When:    `Warning == ""`,
+							Message: "No warning found",
+						},
+					},
+					// The Warn case is triggered if warning != ""
+					{
+						Warn: &troubleshootv1beta2.SingleOutcome{
+							Message: "Warning for CR with name {{ .CRName }} in namespace {{ .Namespace }}",
+						},
+					},
+				},
+				CollectorName: "text-collector-templated-regex-message",
+				FileName:      "cfile-1.txt",
+				RegexGroups:   `"name":\s*"(?P<CRName>.*?)".*namespace":\s*"(?P<Namespace>.*?)".*warning":\s*.*"(?P<Error>mywarning.*?)"`,
+			},
+			expectResult: []AnalyzeResult{
+				{
+					IsPass:  false,
+					IsWarn:  true,
+					IsFail:  false,
+					Title:   "text-collector-templated-regex-message",
+					Message: "Warning for CR with name insert-cr-name-here in namespace default",
+					IconKey: "kubernetes_text_analyze",
+					IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg?w=13&h=16",
+				},
+			},
+			files: map[string][]byte{
+				"text-collector-templated-regex-message/cfile-1.txt": []byte(`{"level":"WARN","timestamp":"2022-05-17T20:37:41Z","caller":"controller/controller.go:317","message":"Reconciler error","context":{"name":"insert-cr-name-here","namespace":"default","warning":"mywarning"}}`),
+			},
+		},
+		// This test ensures that the Outcomes.Fail.Message can be templated using the findings of the regular expression groups.
+		{
+			name: "Outcome fail message is templated with regex groups",
+			analyzer: troubleshootv1beta2.TextAnalyze{
+				Outcomes: []*troubleshootv1beta2.Outcome{
+					{
+						Pass: &troubleshootv1beta2.SingleOutcome{
+							When:    `Error == ""`,
+							Message: "No error found",
+						},
+					},
+					// The Fail case is triggered if warning != ""
+					{
+						Fail: &troubleshootv1beta2.SingleOutcome{
+							Message: "Error for CR with name {{ .CRName }} in namespace {{ .Namespace }}",
+						},
+					},
+				},
+				CollectorName: "text-collector-templated-regex-message",
+				FileName:      "cfile-1.txt",
+				RegexGroups:   `"name":\s*"(?P<CRName>.*?)".*namespace":\s*"(?P<Namespace>.*?)".*error":\s*.*"(?P<Error>myerror.*?)"`,
+			},
+			expectResult: []AnalyzeResult{
+				{
+					IsPass:  false,
+					IsWarn:  false,
+					IsFail:  true,
+					Title:   "text-collector-templated-regex-message",
+					Message: "Error for CR with name insert-cr-name-here in namespace default",
+					IconKey: "kubernetes_text_analyze",
+					IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg?w=13&h=16",
+				},
+			},
+			files: map[string][]byte{
+				"text-collector-templated-regex-message/cfile-1.txt": []byte(`{"level":"ERROR","timestamp":"2022-05-17T20:37:41Z","caller":"controller/controller.go:317","message":"Reconciler error","context":{"name":"insert-cr-name-here","namespace":"default","error":"myerror"}}`),
+			},
+		},
 	}
 
 	for _, test := range tests {