From b4099d4df1519e7686128a41cd508d190f7926e0 Mon Sep 17 00:00:00 2001 From: Akshay Pant Date: Thu, 13 Nov 2025 17:44:48 +0530 Subject: [PATCH] feat(error-detection): add multiple pattern support Add support for configuring multiple error detection regex patterns at both global (ConfigMap) and repository (CR) levels. This allows users to match different error formats from various linters and tools in a single pipeline. Global Configuration (ConfigMap): - Changed error-detection-simple-regexp to support arrays - Supports 3 formats: single pattern (backward compatible), multi-line YAML, and JSON array Repository CR: - Added ErrorDetectionSettings with patterns array and max_number_of_lines - Patterns are additive with global patterns - Per-repository max_number_of_lines override Jira: https://issues.redhat.com/browse/SRVKP-7237 Signed-off-by: Akshay Pant Assisted-by: Cursor --- config/300-repositories.yaml | 28 +++++ config/302-pac-configmap.yaml | 17 ++- docs/content/docs/guide/repositorycrd.md | 98 +++++++++++++++ docs/content/docs/guide/statuses.md | 14 ++- .../docs/install/operator_installation.md | 1 + docs/content/docs/install/settings.md | 33 +++++ pkg/apis/pipelinesascode/v1alpha1/types.go | 32 +++++ pkg/params/settings/config.go | 85 +++++++++++-- pkg/params/settings/config_test.go | 83 +++++++++++- pkg/provider/github/status.go | 118 +++++++++++------- 10 files changed, 451 insertions(+), 58 deletions(-) diff --git a/config/300-repositories.yaml b/config/300-repositories.yaml index 3319d62d5e..f2766ce3f0 100644 --- a/config/300-repositories.yaml +++ b/config/300-repositories.yaml @@ -446,6 +446,34 @@ spec: - roles - secret_ref type: object + error_detection: + description: |- + ErrorDetection configures error detection for this repository. Error detection scans + container logs for error patterns and creates annotations (currently GitHub-only). + The global error-detection-from-container-logs setting must be enabled for this to work. + If not specified, uses only global error detection pattern. + properties: + max_number_of_lines: + description: |- + MaxNumberOfLines specifies how many lines to scan from the end of container logs + when looking for errors. This overrides the global error-detection-max-number-of-lines setting. + Higher values may increase memory usage. Use -1 for unlimited. + If not specified, uses the global setting (default: 50). + type: integer + patterns: + description: |- + Patterns is an array of regular expressions used to detect errors in container logs. + Each pattern must use named groups to capture: filename, line, and error. + The column group is optional. Example pattern: + ^(?P[^:]*):(?P[0-9]+):(?P[0-9]+)?([ ]*)?(?P.*) + + Multiple patterns can be specified to match different error formats. + Repository-specific patterns are tried first, followed by global patterns. + If not specified or empty, only the global error-detection-simple-regexp patterns are used. + items: + type: string + type: array + type: object github: properties: comment_strategy: diff --git a/config/302-pac-configmap.yaml b/config/302-pac-configmap.yaml index 97c8efe9b9..022e864cc6 100644 --- a/config/302-pac-configmap.yaml +++ b/config/302-pac-configmap.yaml @@ -101,7 +101,22 @@ data: # memory usage. Use -1 for unlimited lines. error-detection-max-number-of-lines: "50" - # The default regexp used when we use the simple error detection + # The default regexp(s) used for simple error detection. + # Supports multiple formats for backward compatibility: + # + # 1. Single pattern (backward compatible): + # error-detection-simple-regexp: '^(?P[^:]*):(?P[0-9]+):(?P[0-9]+)?([ ]*)?(?P.*)' + # + # 2. YAML list format (multiple patterns, one per line): + # error-detection-simple-regexp: |- + # ^(?P[^:]*):(?P[0-9]+):(?P[0-9]+)?([ ]*)?(?P.*) + # ^ERROR: (?P[^ ]+) line (?P[0-9]+): (?P.*) + # ^\[(?P[^\]]+)\]:(?P[0-9]+) - (?P.*) + # + # 3. JSON array format: + # error-detection-simple-regexp: '["^(?P[^:]*):(?P[0-9]+):(?P[0-9]+)?([ ]*)?(?P.*)", "^ERROR:.*"]' + # + # Each pattern must include named groups: filename, line, and error (column is optional) error-detection-simple-regexp: |- ^(?P[^:]*):(?P[0-9]+):(?P[0-9]+)?([ ]*)?(?P.*) diff --git a/docs/content/docs/guide/repositorycrd.md b/docs/content/docs/guide/repositorycrd.md index 1b683f9643..34bd6543ec 100644 --- a/docs/content/docs/guide/repositorycrd.md +++ b/docs/content/docs/guide/repositorycrd.md @@ -185,6 +185,104 @@ spec: comment_strategy: "disable_all" ``` +## Error Detection + +Error detection scans container logs for error patterns and creates inline +annotations on Pull Requests. This feature is currently **only supported for +GitHub Apps**. + +By default, error detection uses the global pattern configured in the +`pipelines-as-code` ConfigMap via the `error-detection-simple-regexp` setting. +However, you can customize error detection patterns on a per-repository basis +using the Repository CR. + +### Configuring Error Detection Patterns + +You can specify multiple regex patterns to detect different error formats in +your repository: + +```yaml +apiVersion: "pipelinesascode.tekton.dev/v1alpha1" +kind: Repository +metadata: + name: my-repo +spec: + url: "https://github.com/owner/repo" + settings: + error_detection: + patterns: + - "^(?P[^:]*):(?P[0-9]+):(?P[0-9]+)?([ ]*)?(?P.*)" + - "^ERROR: (?P[^ ]+) line (?P[0-9]+): (?P.*)" + max_number_of_lines: 100 +``` + +**Pattern Requirements:** + +Each pattern must use [named groups](https://www.regular-expressions.info/named.html) to capture: + +- `filename`: The file path where the error occurred +- `line`: The line number +- `error`: The error message +- `column`: (optional) The column number + +**Configuration Options:** + +- `patterns`: Array of regex patterns. Repository-specific patterns are tried + first, followed by global patterns. If not specified or empty, only the + global patterns are used. **Note:** Providing an empty array does not disable + error detection; it falls back to using only the global patterns defined in + the `pipelines-as-code` ConfigMap. +- `max_number_of_lines`: Number of log lines to scan (overrides global + setting). Default is 50. Use -1 for unlimited. + +{{< hint info >}} +**Global Override:** The global `error-detection-from-container-logs` setting +must be enabled (default: `true`) for error detection to work. If disabled +globally, repository-level settings cannot override it. +{{< /hint >}} + +### Examples + +**Multiple error formats:** + +```yaml +spec: + settings: + error_detection: + patterns: + # Standard format (make, gcc, eslint, etc.) + - "^(?P[^:]*):(?P[0-9]+):(?P[0-9]+)?([ ]*)?(?P.*)" + # Python traceback format + - 'File "(?P[^"]+)", line (?P[0-9]+).*\n.*(?P.*)' + # Custom CI format + - "^\\[(?P[^\\]]+)\\]:(?P[0-9]+) - (?P.*)" + max_number_of_lines: 200 +``` + +**Using only global patterns:** + +If you want to use only the global patterns defined in the `pipelines-as-code` +ConfigMap, simply omit the `error_detection` field or provide an empty +`patterns` array: + +```yaml +spec: + settings: + error_detection: + patterns: [] # Uses only global patterns + max_number_of_lines: 100 # Can still override line count +``` + +{{< hint info >}} +**Pattern Priority:** When repository-specific patterns are defined, they are +tried first for each log line. If no repository pattern matches, the global +patterns are then tried. This allows you to add repository-specific patterns +while still benefiting from the global patterns as a fallback. +{{< /hint >}} + +For more information about error detection and log snippets, see the +[Status documentation]({{< relref "/docs/guide/statuses.md" >}}). + ## Concurrency `concurrency_limit` allows you to define the maximum number of PipelineRuns running at any time for a Repository. diff --git a/docs/content/docs/guide/statuses.md b/docs/content/docs/guide/statuses.md index 3f887c6dae..8e72e4e246 100644 --- a/docs/content/docs/guide/statuses.md +++ b/docs/content/docs/guide/statuses.md @@ -84,8 +84,10 @@ You can customize the regular expression used for detecting errors with the `error-detection-simple-regexp` setting. The regular expression uses [named groups](https://www.regular-expressions.info/named.html) to provide flexibility in specifying the matching criteria. The necessary groups for matching are -filename, line, and error (the column group is not used). The default regular -expression is defined in the configuration map. +filename, line, and error (the column group is optional). The default regular +expression is defined in the configuration map. Multiple patterns are supported +using multi-line YAML or JSON array format, allowing you to detect errors from +different tools with various output formats. By default, Pipelines-as-Code searches for errors in only the last 50 lines of the container logs. However, you can increase this limit by setting the @@ -94,6 +96,14 @@ system will search through all available lines for errors. Keep in mind that increasing this maximum number of lines may increase the memory usage of the watcher. +{{< hint info >}} +**Repository-level configuration:** You can also configure error detection +patterns on a per-repository basis using the Repository CR. This allows you to +define multiple patterns and customize settings for individual repositories. +See the [Repository CR documentation]({{< relref "/docs/guide/repositorycrd.md#error-detection" >}}) +for more details. +{{< /hint >}} + ![annotations](/images/github-annotation-error-failure-detection.png) ## Namespace Event stream diff --git a/docs/content/docs/install/operator_installation.md b/docs/content/docs/install/operator_installation.md index 3a66354552..cf09dbfb2a 100644 --- a/docs/content/docs/install/operator_installation.md +++ b/docs/content/docs/install/operator_installation.md @@ -45,6 +45,7 @@ spec: hub-url: 'https://artifacthub.io' hub-catalog-type: 'artifacthub' error-detection-max-number-of-lines: '50' + # Single pattern example. For multiple patterns, use multi-line format (see settings docs) error-detection-simple-regexp: >- ^(?P[^:]*):(?P[0-9]+):(?P[0-9]+):([ ]*)?(?P.*) diff --git a/docs/content/docs/install/settings.md b/docs/content/docs/install/settings.md index 2aefa204c6..2498c9c571 100644 --- a/docs/content/docs/install/settings.md +++ b/docs/content/docs/install/settings.md @@ -291,6 +291,39 @@ A few settings are available to configure this feature: By default the error detection only support a simple output, the way GCC or Make will output error, which is supported by most linters and command line tools. + **Multiple Patterns Support:** You can now specify multiple regex patterns to + match different error formats. The setting supports three formats: + + 1. **Single pattern** (backward compatible): + + ```yaml + error-detection-simple-regexp: '^(?P[^:]*):(?P[0-9]+):(?P[0-9]+)?([ ]*)?(?P.*)' + ``` + + 2. **Multi-line YAML** (recommended for multiple patterns): + + ```yaml + error-detection-simple-regexp: |- + ^(?P[^:]*):(?P[0-9]+):(?P[0-9]+)?([ ]*)?(?P.*) + ^ERROR: (?P[^ ]+) line (?P[0-9]+): (?P.*) + ^\[(?P[^\]]+)\]:(?P[0-9]+) - (?P.*) + ``` + + 3. **JSON array format**: + + ```yaml + error-detection-simple-regexp: '["^(?P[^:]*):(?P[0-9]+):(?P[0-9]+)?([ ]*)?(?P.*)", "^ERROR:.*"]' + ``` + + Each pattern will be tried in order until one matches. This allows you to detect + errors from multiple tools with different output formats. + + **Pattern Requirements:** Each pattern must use regexp named groups to capture: + * `(?P...)` - The file path where the error occurred + * `(?P...)` - The line number + * `(?P...)` - The error message + * `(?P...)` - Column number (optional) + An example of an error that is supported is : ```console diff --git a/pkg/apis/pipelinesascode/v1alpha1/types.go b/pkg/apis/pipelinesascode/v1alpha1/types.go index 16bc22e1dc..b620a21aa6 100644 --- a/pkg/apis/pipelinesascode/v1alpha1/types.go +++ b/pkg/apis/pipelinesascode/v1alpha1/types.go @@ -164,6 +164,13 @@ type Settings struct { // AIAnalysis contains AI/LLM analysis configuration for automated CI/CD pipeline analysis. // +optional AIAnalysis *AIAnalysisConfig `json:"ai,omitempty"` + + // ErrorDetection configures error detection for this repository. Error detection scans + // container logs for error patterns and creates annotations (currently GitHub-only). + // The global error-detection-from-container-logs setting must be enabled for this to work. + // If not specified, uses only global error detection pattern. + // +optional + ErrorDetection *ErrorDetectionSettings `json:"error_detection,omitempty"` } type GitlabSettings struct { @@ -184,6 +191,28 @@ type GithubSettings struct { CommentStrategy string `json:"comment_strategy,omitempty"` } +// ErrorDetectionSettings configures how errors are detected from container logs and +// exposed as annotations on Pull Requests. Currently only supported for GitHub Apps. +type ErrorDetectionSettings struct { + // Patterns is an array of regular expressions used to detect errors in container logs. + // Each pattern must use named groups to capture: filename, line, and error. + // The column group is optional. Example pattern: + // ^(?P[^:]*):(?P[0-9]+):(?P[0-9]+)?([ ]*)?(?P.*) + // + // Multiple patterns can be specified to match different error formats. + // Repository-specific patterns are tried first, followed by global patterns. + // If not specified or empty, only the global error-detection-simple-regexp patterns are used. + // +optional + Patterns []string `json:"patterns,omitempty"` + + // MaxNumberOfLines specifies how many lines to scan from the end of container logs + // when looking for errors. This overrides the global error-detection-max-number-of-lines setting. + // Higher values may increase memory usage. Use -1 for unlimited. + // If not specified, uses the global setting (default: 50). + // +optional + MaxNumberOfLines *int `json:"max_number_of_lines,omitempty"` +} + func (s *Settings) Merge(newSettings *Settings) { if newSettings.PipelineRunProvenance != "" && s.PipelineRunProvenance == "" { s.PipelineRunProvenance = newSettings.PipelineRunProvenance @@ -197,6 +226,9 @@ func (s *Settings) Merge(newSettings *Settings) { if newSettings.AIAnalysis != nil && s.AIAnalysis == nil { s.AIAnalysis = newSettings.AIAnalysis } + if newSettings.ErrorDetection != nil && s.ErrorDetection == nil { + s.ErrorDetection = newSettings.ErrorDetection + } } type Policy struct { diff --git a/pkg/params/settings/config.go b/pkg/params/settings/config.go index 6161a6bca0..d70d8d1842 100644 --- a/pkg/params/settings/config.go +++ b/pkg/params/settings/config.go @@ -1,6 +1,7 @@ package settings import ( + "encoding/json" "fmt" "net/url" "regexp" @@ -64,11 +65,11 @@ type Settings struct { SecretGHAppRepoScoped bool `default:"true" json:"secret-github-app-token-scoped"` SecretGhAppTokenScopedExtraRepos string `json:"secret-github-app-scope-extra-repos"` - ErrorLogSnippet bool `default:"true" json:"error-log-snippet"` - ErrorLogSnippetNumberOfLines int `default:"3" json:"error-log-snippet-number-of-lines"` - ErrorDetection bool `default:"true" json:"error-detection-from-container-logs"` - ErrorDetectionNumberOfLines int `default:"50" json:"error-detection-max-number-of-lines"` - ErrorDetectionSimpleRegexp string `default:"^(?P[^:]*):(?P[0-9]+):(?P[0-9]+)?([ ]*)?(?P.*)" json:"error-detection-simple-regexp"` + ErrorLogSnippet bool `default:"true" json:"error-log-snippet"` + ErrorLogSnippetNumberOfLines int `default:"3" json:"error-log-snippet-number-of-lines"` + ErrorDetection bool `default:"true" json:"error-detection-from-container-logs"` + ErrorDetectionNumberOfLines int `default:"50" json:"error-detection-max-number-of-lines"` + ErrorDetectionSimpleRegexp []string `default:"^(?P[^:]*):(?P[0-9]+):(?P[0-9]+)?([ ]*)?(?P.*)"` // Note: no json tag, handled specially EnableCancelInProgressOnPullRequests bool `json:"enable-cancel-in-progress-on-pull-requests"` EnableCancelInProgressOnPush bool `json:"enable-cancel-in-progress-on-push"` @@ -106,11 +107,10 @@ func DefaultSettings() Settings { func DefaultValidators() map[string]func(string) error { return map[string]func(string) error{ - "ErrorDetectionSimpleRegexp": isValidRegex, - "TektonDashboardURL": isValidURL, - "CustomConsoleURL": isValidURL, - "CustomConsolePRTaskLog": startWithHTTPorHTTPS, - "CustomConsolePRDetail": startWithHTTPorHTTPS, + "TektonDashboardURL": isValidURL, + "CustomConsoleURL": isValidURL, + "CustomConsolePRTaskLog": startWithHTTPorHTTPS, + "CustomConsolePRDetail": startWithHTTPorHTTPS, } } @@ -122,6 +122,13 @@ func SyncConfig(logger *zap.SugaredLogger, setting *Settings, config map[string] return fmt.Errorf("failed to validate and assign values: %w", err) } + // Parse error detection patterns (supports backward compatibility) + // Handling error detection specially as we want to keep backward compatibility while + // allowing []string as a valid input value. + if err := parseErrorDetectionPatterns(setting, config); err != nil { + return fmt.Errorf("failed to parse error detection patterns: %w", err) + } + value, _ := setting.HubCatalogs.Load("default") catalogDefault, ok := value.(HubCatalog) if ok { @@ -160,3 +167,61 @@ func startWithHTTPorHTTPS(url string) error { } return nil } + +// parseErrorDetectionPatterns parses the error-detection-simple-regexp from ConfigMap +// and populates ErrorDetectionSimpleRegexp as []string. It supports: +// 1. Single string pattern (backward compatible) +// 2. JSON array format: ["pattern1", "pattern2"] +// 3. Newline-separated patterns (YAML multi-line). +func parseErrorDetectionPatterns(setting *Settings, config map[string]string) error { + regexpValue := config["error-detection-simple-regexp"] + + // If not in config, use default + if regexpValue == "" { + regexpValue = "^(?P[^:]*):(?P[0-9]+):(?P[0-9]+)?([ ]*)?(?P.*)" + } + + regexpValue = strings.TrimSpace(regexpValue) + + // Try to parse as JSON array first + if strings.HasPrefix(regexpValue, "[") && strings.HasSuffix(regexpValue, "]") { + var patterns []string + if err := json.Unmarshal([]byte(regexpValue), &patterns); err == nil { + // Validate each pattern + for _, pattern := range patterns { + if err := isValidRegex(pattern); err != nil { + return fmt.Errorf("invalid regex in array: %w", err) + } + } + setting.ErrorDetectionSimpleRegexp = patterns + return nil + } + } + + // Check if it contains newlines (multi-line YAML format) + if strings.Contains(regexpValue, "\n") { + lines := strings.Split(regexpValue, "\n") + var patterns []string + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" { + continue + } + if err := isValidRegex(line); err != nil { + return fmt.Errorf("invalid regex in multi-line format: %w", err) + } + patterns = append(patterns, line) + } + if len(patterns) > 0 { + setting.ErrorDetectionSimpleRegexp = patterns + return nil + } + } + + // Single pattern (backward compatible) + if err := isValidRegex(regexpValue); err != nil { + return err + } + setting.ErrorDetectionSimpleRegexp = []string{regexpValue} + return nil +} diff --git a/pkg/params/settings/config_test.go b/pkg/params/settings/config_test.go index e8e4cdaa09..7d2ba8697a 100644 --- a/pkg/params/settings/config_test.go +++ b/pkg/params/settings/config_test.go @@ -38,7 +38,7 @@ func TestSyncConfig(t *testing.T) { ErrorLogSnippetNumberOfLines: 3, ErrorDetection: true, ErrorDetectionNumberOfLines: 50, - ErrorDetectionSimpleRegexp: "^(?P[^:]*):(?P[0-9]+):(?P[0-9]+)?([ ]*)?(?P.*)", + ErrorDetectionSimpleRegexp: []string{"^(?P[^:]*):(?P[0-9]+):(?P[0-9]+)?([ ]*)?(?P.*)"}, EnableCancelInProgressOnPullRequests: false, EnableCancelInProgressOnPush: false, SkipPushEventForPRCommits: true, @@ -98,7 +98,7 @@ func TestSyncConfig(t *testing.T) { ErrorLogSnippetNumberOfLines: 3, ErrorDetection: false, ErrorDetectionNumberOfLines: 100, - ErrorDetectionSimpleRegexp: "^(?P[^:]*):(?P[0-9]+):(?P[0-9]+)?([ ]*)?(?P.*)", + ErrorDetectionSimpleRegexp: []string{"^(?P[^:]*):(?P[0-9]+):(?P[0-9]+)?([ ]*)?(?P.*)"}, EnableCancelInProgressOnPullRequests: false, EnableCancelInProgressOnPush: false, SkipPushEventForPRCommits: true, @@ -130,7 +130,84 @@ func TestSyncConfig(t *testing.T) { configMap: map[string]string{ "error-detection-simple-regexp": "[", }, - expectedError: "custom validation failed for field ErrorDetectionSimpleRegexp: invalid regex: error parsing regexp: missing closing ]: `[`", + expectedError: "failed to parse error detection patterns: invalid regex: error parsing regexp: missing closing ]: `[`", + }, + { + name: "multiple patterns via multi-line YAML", + configMap: map[string]string{ + "error-detection-simple-regexp": "^(?P[^:]*):(?P[0-9]+):(?P[0-9]+)?([ ]*)?(?P.*)\n^ERROR: (?P[^ ]+) line (?P[0-9]+): (?P.*)", + }, + expectedStruct: Settings{ + ApplicationName: "Pipelines as Code CI", + HubCatalogs: nil, + RemoteTasks: true, + MaxKeepRunsUpperLimit: 0, + DefaultMaxKeepRuns: 0, + BitbucketCloudCheckSourceIP: true, + BitbucketCloudAdditionalSourceIP: "", + TektonDashboardURL: "", + AutoConfigureNewGitHubRepo: false, + AutoConfigureRepoNamespaceTemplate: "", + SecretAutoCreation: true, + SecretGHAppRepoScoped: true, + SecretGhAppTokenScopedExtraRepos: "", + ErrorLogSnippet: true, + ErrorLogSnippetNumberOfLines: 3, + ErrorDetection: true, + ErrorDetectionNumberOfLines: 50, + ErrorDetectionSimpleRegexp: []string{ + "^(?P[^:]*):(?P[0-9]+):(?P[0-9]+)?([ ]*)?(?P.*)", + "^ERROR: (?P[^ ]+) line (?P[0-9]+): (?P.*)", + }, + EnableCancelInProgressOnPullRequests: false, + EnableCancelInProgressOnPush: false, + SkipPushEventForPRCommits: true, + CustomConsoleName: "", + CustomConsoleURL: "", + CustomConsolePRdetail: "", + CustomConsolePRTaskLog: "", + CustomConsoleNamespaceURL: "", + RememberOKToTest: false, + }, + }, + { + name: "multiple patterns via JSON array", + configMap: map[string]string{ + "error-detection-simple-regexp": `["^(?P[^:]*):(?P[0-9]+):(?P[0-9]+)?([ ]*)?(?P.*)", "^ERROR: (?P[^ ]+) line (?P[0-9]+): (?P.*)", "^\\[(?P[^\\]]+)\\]:(?P[0-9]+) - (?P.*)"]`, + }, + expectedStruct: Settings{ + ApplicationName: "Pipelines as Code CI", + HubCatalogs: nil, + RemoteTasks: true, + MaxKeepRunsUpperLimit: 0, + DefaultMaxKeepRuns: 0, + BitbucketCloudCheckSourceIP: true, + BitbucketCloudAdditionalSourceIP: "", + TektonDashboardURL: "", + AutoConfigureNewGitHubRepo: false, + AutoConfigureRepoNamespaceTemplate: "", + SecretAutoCreation: true, + SecretGHAppRepoScoped: true, + SecretGhAppTokenScopedExtraRepos: "", + ErrorLogSnippet: true, + ErrorLogSnippetNumberOfLines: 3, + ErrorDetection: true, + ErrorDetectionNumberOfLines: 50, + ErrorDetectionSimpleRegexp: []string{ + "^(?P[^:]*):(?P[0-9]+):(?P[0-9]+)?([ ]*)?(?P.*)", + "^ERROR: (?P[^ ]+) line (?P[0-9]+): (?P.*)", + "^\\[(?P[^\\]]+)\\]:(?P[0-9]+) - (?P.*)", + }, + EnableCancelInProgressOnPullRequests: false, + EnableCancelInProgressOnPush: false, + SkipPushEventForPRCommits: true, + CustomConsoleName: "", + CustomConsoleURL: "", + CustomConsolePRdetail: "", + CustomConsolePRTaskLog: "", + CustomConsoleNamespaceURL: "", + RememberOKToTest: false, + }, }, { name: "invalid value url", diff --git a/pkg/provider/github/status.go b/pkg/provider/github/status.go index 6838b89e07..b68c63a6d4 100644 --- a/pkg/provider/github/status.go +++ b/pkg/provider/github/status.go @@ -140,64 +140,98 @@ func (v *Provider) createCheckRunStatus(ctx context.Context, runevent *info.Even func (v *Provider) getFailuresMessageAsAnnotations(ctx context.Context, pr *tektonv1.PipelineRun, pacopts *info.PacOpts) []*github.CheckRunAnnotation { annotations := []*github.CheckRunAnnotation{} - r, err := regexp.Compile(pacopts.ErrorDetectionSimpleRegexp) - if err != nil { - v.Logger.Errorf("invalid regexp for filtering failure messages: %v", pacopts.ErrorDetectionSimpleRegexp) + var patterns []string + maxNumberOfLines := pacopts.ErrorDetectionNumberOfLines + + // Check if repository has custom error detection settings + if v.repo != nil && v.repo.Spec.Settings != nil && v.repo.Spec.Settings.ErrorDetection != nil { + repoErrorDetection := v.repo.Spec.Settings.ErrorDetection + // Use repository-specific patterns if provided + if repoErrorDetection.Patterns != nil { + patterns = repoErrorDetection.Patterns + } + // Use repository-specific max number of lines if provided + if repoErrorDetection.MaxNumberOfLines != nil { + maxNumberOfLines = *repoErrorDetection.MaxNumberOfLines + } + } + + patterns = append(patterns, pacopts.ErrorDetectionSimpleRegexp...) + + compiledPatterns := make([]*regexp.Regexp, 0, len(patterns)) + for _, pattern := range patterns { + r, err := regexp.Compile(pattern) + if err != nil { + v.Logger.Errorf("invalid regexp for filtering failure messages: %v", pattern) + continue + } + compiledPatterns = append(compiledPatterns, r) + } + + if len(compiledPatterns) == 0 { + v.Logger.Error("no valid error detection patterns available") return annotations } + intf, err := kubeinteraction.NewKubernetesInteraction(v.Run) if err != nil { v.Logger.Errorf("failed to create kubeinteraction: %v", err) return annotations } - taskinfos := kstatus.CollectFailedTasksLogSnippet(ctx, v.Run, intf, pr, int64(pacopts.ErrorDetectionNumberOfLines)) + + taskinfos := kstatus.CollectFailedTasksLogSnippet(ctx, v.Run, intf, pr, int64(maxNumberOfLines)) for _, taskinfo := range taskinfos { for _, errline := range strings.Split(taskinfo.LogSnippet, "\n") { - results := map[string]string{} - if !r.MatchString(errline) { - continue - } - matches := r.FindStringSubmatch(errline) - for i, name := range r.SubexpNames() { - if i != 0 && name != "" { - results[name] = matches[i] + // Try each pattern until we find a match + for _, r := range compiledPatterns { + results := map[string]string{} + if !r.MatchString(errline) { + continue + } + matches := r.FindStringSubmatch(errline) + for i, name := range r.SubexpNames() { + if i != 0 && name != "" { + results[name] = matches[i] + } } - } - // check if we have file in results - var linenumber, errmsg, filename string - var ok bool + // check if we have file in results + var linenumber, errmsg, filename string + var ok bool - if filename, ok = results["filename"]; !ok { - v.Logger.Errorf("regexp for filtering failure messages does not contain a filename regexp group: %v", pacopts.ErrorDetectionSimpleRegexp) - continue - } - // remove ./ cause it would bug github otherwise - filename = strings.TrimPrefix(filename, "./") + if filename, ok = results["filename"]; !ok { + v.Logger.Errorf("regexp for filtering failure messages does not contain a filename regexp group: %v", r.String()) + continue + } + // remove ./ cause it would bug github otherwise + filename = strings.TrimPrefix(filename, "./") - if linenumber, ok = results["line"]; !ok { - v.Logger.Errorf("regexp for filtering failure messages does not contain a line regexp group: %v", pacopts.ErrorDetectionSimpleRegexp) - continue - } + if linenumber, ok = results["line"]; !ok { + v.Logger.Errorf("regexp for filtering failure messages does not contain a line regexp group: %v", r.String()) + continue + } - if errmsg, ok = results["error"]; !ok { - v.Logger.Errorf("regexp for filtering failure messages does not contain a error regexp group: %v", pacopts.ErrorDetectionSimpleRegexp) - continue - } + if errmsg, ok = results["error"]; !ok { + v.Logger.Errorf("regexp for filtering failure messages does not contain a error regexp group: %v", r.String()) + continue + } - ilinenumber, err := strconv.Atoi(linenumber) - if err != nil { - // can't do much regexp has probably failed to detect - v.Logger.Errorf("cannot convert %s as integer: %v", linenumber, err) - continue + ilinenumber, err := strconv.Atoi(linenumber) + if err != nil { + // can't do much regexp has probably failed to detect + v.Logger.Errorf("cannot convert %s as integer: %v", linenumber, err) + continue + } + annotations = append(annotations, &github.CheckRunAnnotation{ + Path: github.Ptr(filename), + StartLine: github.Ptr(ilinenumber), + EndLine: github.Ptr(ilinenumber), + AnnotationLevel: github.Ptr("failure"), + Message: github.Ptr(errmsg), + }) + // Pattern matched, no need to try other patterns for this line + break } - annotations = append(annotations, &github.CheckRunAnnotation{ - Path: github.Ptr(filename), - StartLine: github.Ptr(ilinenumber), - EndLine: github.Ptr(ilinenumber), - AnnotationLevel: github.Ptr("failure"), - Message: github.Ptr(errmsg), - }) } } return annotations