@@ -19,16 +19,17 @@ import (
1919 "bytes"
2020 "context"
2121 "embed"
22+ "errors"
2223 "fmt"
2324 "os"
2425 "path/filepath"
2526 "regexp"
26- "strconv"
2727 "strings"
2828
2929 v1 "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
3030 "github.com/chainloop-dev/chainloop/app/controlplane/pkg/unmarshal"
3131 "github.com/chainloop-dev/chainloop/pkg/resourceloader"
32+ opaAst "github.com/open-policy-agent/opa/v1/ast"
3233 "github.com/open-policy-agent/opa/v1/format"
3334 "github.com/styrainc/regal/pkg/config"
3435 "github.com/styrainc/regal/pkg/linter"
@@ -229,7 +230,11 @@ func (p *PolicyToLint) validateYAMLFile(file *File) {
229230 if err := os .WriteFile (file .Path , outYAML , 0600 ); err != nil {
230231 p .AddError (file .Path , fmt .Sprintf ("failed to write updated file: %v" , err ), 0 )
231232 } else {
232- file .Content = outYAML
233+ if err := os .WriteFile (file .Path , outYAML , 0600 ); err != nil {
234+ p .AddError (file .Path , fmt .Sprintf ("failed to save updated file: %v" , err ), 0 )
235+ } else {
236+ file .Content = outYAML
237+ }
233238 }
234239 }
235240}
@@ -315,7 +320,21 @@ func (p *PolicyToLint) checkResultStructure(content, path string, keys []string)
315320func (p * PolicyToLint ) runRegalLinter (filePath , content string ) {
316321 inputModules , err := rules .InputFromText (filePath , content )
317322 if err != nil {
318- p .AddError (filePath , fmt .Sprintf ("failed to prepare for linting: %v" , err ), 0 )
323+ // Cast to OPA AST errors for better formatting
324+ var astErrs opaAst.Errors
325+ if errors .As (err , & astErrs ) {
326+ for _ , e := range astErrs {
327+ line := 0
328+ if e .Location != nil {
329+ line = e .Location .Row
330+ }
331+
332+ p .AddError (filePath , e .Message , line )
333+ }
334+ return
335+ }
336+ // Fallback if it's not an ast.Errors type
337+ p .AddError (filePath , err .Error (), 0 )
319338 return
320339 }
321340
@@ -337,9 +356,10 @@ func (p *PolicyToLint) runRegalLinter(filePath, content string) {
337356 return
338357 }
339358
340- // Handle Regal violations by formatting
359+ // Add any violations to the policy errors
341360 for _ , v := range report .Violations {
342- p .processRegalViolation (fmt .Errorf ("%s:%d: %s" , filePath , v .Location .Row , v .Description ), filePath )
361+ errorStr := strings .ReplaceAll (v .Description , "`opa fmt`" , "`--format`" )
362+ p .AddError (filePath , errorStr , v .Location .Row )
343363 }
344364}
345365
@@ -388,42 +408,6 @@ func (p *PolicyToLint) loadDefaultConfig() (*config.Config, error) {
388408 return & cfg , nil
389409}
390410
391- // Splits grouped errors into individual errors
392- func (p * PolicyToLint ) processRegalViolation (rawErr error , path string ) {
393- if rawErr == nil {
394- return
395- }
396-
397- errorStr := rawErr .Error ()
398- // Regex matches file path, line number and error message like: /path/file:line: message
399- errorRegex := regexp .MustCompile (`^` + regexp .QuoteMeta (path ) + `:(\d+):\s*(.+)$` )
400-
401- // Split by newlines to handle both single and multi-line errors
402- lines := strings .Split (errorStr , "\n " )
403- for _ , line := range lines {
404- line = strings .TrimSpace (line )
405- if line == "" {
406- continue
407- }
408-
409- // Skip the "N errors occurred" header
410- if strings .Contains (line , "errors occurred:" ) {
411- continue
412- }
413-
414- // Try to match the standard error format
415- if matches := errorRegex .FindStringSubmatch (line ); len (matches ) == 3 {
416- if lineNum , convErr := strconv .Atoi (matches [1 ]); convErr == nil {
417- p .AddError (path , matches [2 ], lineNum )
418- continue
419- }
420- }
421-
422- // If we didn't match the standard format, preserve the original error
423- p .AddError (path , line , 0 )
424- }
425- }
426-
427411// Updates the embedded rego policies in a YAML file
428412// Manual update required due to yaml.marshal limitations
429413func (p * PolicyToLint ) updateEmbeddedRegoInYAML (file * File , rootNode * yaml.Node ) error {
0 commit comments