From 17518349ff3fc9356b16c049ca2de5ec7a56129e Mon Sep 17 00:00:00 2001 From: Benjamin Yang <82779168+bennyyang11@users.noreply.github.com> Date: Mon, 29 Sep 2025 19:49:54 -0500 Subject: [PATCH] Fix/exec textanalyze path clean (#1865) * created roadmap and yaml claude agent * Update roadmap.md * Fix textAnalyze analyzer to auto-match exec collector nested paths - Auto-detect exec output files (*-stdout.txt, *-stderr.txt, *-errors.json) - Convert simple filenames to wildcard patterns automatically - Preserve existing wildcard patterns - Fixes 'No matching file' errors for exec + textAnalyze workflows --------- Co-authored-by: Noah Campbell --- pkg/analyze/text_analyze.go | 30 +++++++++- pkg/analyze/text_analyze_test.go | 100 +++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 1 deletion(-) diff --git a/pkg/analyze/text_analyze.go b/pkg/analyze/text_analyze.go index 72c9000c..d39a5beb 100644 --- a/pkg/analyze/text_analyze.go +++ b/pkg/analyze/text_analyze.go @@ -37,9 +37,23 @@ func analyzeTextAnalyze( analyzer *troubleshootv1beta2.TextAnalyze, getCollectedFileContents getChildCollectedFileContents, title string, ) ([]*AnalyzeResult, error) { fullPath := filepath.Join(analyzer.CollectorName, analyzer.FileName) + + // Auto-handle exec collector output files which are nested deeper than expected + // Exec collectors store files in: {collectorName}/{namespace}/{podName}/{fileName} + // But textAnalyze expects: {collectorName}/{fileName} + // If the fileName looks like exec output and doesn't already have wildcards, make it work automatically + if isLikelyExecOutput(analyzer.FileName) && !containsWildcards(analyzer.FileName) && !containsWildcards(fullPath) { + fullPath = filepath.Join(analyzer.CollectorName, "*", "*", analyzer.FileName) + } + excludeFiles := []string{} for _, excludeFile := range analyzer.ExcludeFiles { - excludeFiles = append(excludeFiles, filepath.Join(analyzer.CollectorName, excludeFile)) + excludePath := filepath.Join(analyzer.CollectorName, excludeFile) + // Apply same logic to exclude files + if isLikelyExecOutput(excludeFile) && !containsWildcards(excludeFile) && !containsWildcards(excludePath) { + excludePath = filepath.Join(analyzer.CollectorName, "*", "*", excludeFile) + } + excludeFiles = append(excludeFiles, excludePath) } collected, err := getCollectedFileContents(fullPath, excludeFiles) @@ -108,6 +122,20 @@ func analyzeTextAnalyze( }, nil } +// isLikelyExecOutput checks if a filename looks like exec collector output +func isLikelyExecOutput(fileName string) bool { + return strings.HasSuffix(fileName, "-stdout.txt") || + strings.HasSuffix(fileName, "-stderr.txt") || + strings.HasSuffix(fileName, "-errors.json") +} + +// containsWildcards checks if a path contains glob wildcards +func containsWildcards(path string) bool { + return strings.Contains(path, "*") || + strings.Contains(path, "?") || + strings.Contains(path, "[") +} + func analyzeRegexPattern(pattern string, collected []byte, outcomes []*troubleshootv1beta2.Outcome, checkName string) (*AnalyzeResult, error) { re, err := regexp.Compile(pattern) if err != nil { diff --git a/pkg/analyze/text_analyze_test.go b/pkg/analyze/text_analyze_test.go index f3c7faa4..dee0979b 100644 --- a/pkg/analyze/text_analyze_test.go +++ b/pkg/analyze/text_analyze_test.go @@ -776,6 +776,106 @@ func Test_textAnalyze(t *testing.T) { "text-collector-1/cfile-2.txt": []byte("Yes it all succeeded"), }, }, + { + name: "exec collector auto-path matching for stdout", + analyzer: troubleshootv1beta2.TextAnalyze{ + Outcomes: []*troubleshootv1beta2.Outcome{ + { + Pass: &troubleshootv1beta2.SingleOutcome{ + Message: "Command output found", + }, + }, + { + Fail: &troubleshootv1beta2.SingleOutcome{ + Message: "Command output not found", + }, + }, + }, + CollectorName: "netbox-branch-check", + FileName: "netbox-branch-check-stdout.txt", // Simple filename, but file is nested deeper + RegexPattern: "success", + }, + expectResult: []AnalyzeResult{ + { + IsPass: true, + IsWarn: false, + IsFail: false, + Title: "netbox-branch-check", + Message: "Command output found", + IconKey: "kubernetes_text_analyze", + IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg", + }, + }, + files: map[string][]byte{ + // File is stored in exec-style nested path: {collector}/{namespace}/{pod}/{collector}-stdout.txt + "netbox-branch-check/netbox-enterprise/netbox-enterprise-858bcb8d4-cdgk7/netbox-branch-check-stdout.txt": []byte("operation success completed"), + }, + }, + { + name: "exec collector auto-path matching for stderr", + analyzer: troubleshootv1beta2.TextAnalyze{ + Outcomes: []*troubleshootv1beta2.Outcome{ + { + Pass: &troubleshootv1beta2.SingleOutcome{ + Message: "No errors in stderr", + When: "false", + }, + }, + { + Fail: &troubleshootv1beta2.SingleOutcome{ + Message: "Error found in stderr", + When: "true", + }, + }, + }, + CollectorName: "my-exec-collector", + FileName: "my-exec-collector-stderr.txt", + RegexPattern: "error", + }, + expectResult: []AnalyzeResult{ + { + IsPass: false, + IsWarn: false, + IsFail: true, + Title: "my-exec-collector", + Message: "Error found in stderr", + IconKey: "kubernetes_text_analyze", + IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg", + }, + }, + files: map[string][]byte{ + "my-exec-collector/default/my-pod-12345/my-exec-collector-stderr.txt": []byte("connection error occurred"), + }, + }, + { + name: "exec collector no auto-match when wildcards already present", + analyzer: troubleshootv1beta2.TextAnalyze{ + Outcomes: []*troubleshootv1beta2.Outcome{ + { + Pass: &troubleshootv1beta2.SingleOutcome{ + Message: "Found with existing wildcard", + }, + }, + }, + CollectorName: "test-collector", + FileName: "*/test-collector-stdout.txt", // Already has wildcard, should not be modified + RegexPattern: "output", + }, + expectResult: []AnalyzeResult{ + { + IsPass: true, + IsWarn: false, + IsFail: false, + Title: "test-collector", + Message: "Found with existing wildcard", + IconKey: "kubernetes_text_analyze", + IconURI: "https://troubleshoot.sh/images/analyzer-icons/text-analyze.svg", + }, + }, + files: map[string][]byte{ + "test-collector/something/test-collector-stdout.txt": []byte("some output here"), + }, + }, } for _, test := range tests {