@@ -17,6 +17,7 @@ package policydevel
1717
1818import (
1919 "context"
20+ "embed"
2021 "fmt"
2122 "os"
2223 "path/filepath"
@@ -28,15 +29,21 @@ import (
2829 v1 "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
2930 "github.com/chainloop-dev/chainloop/app/controlplane/pkg/unmarshal"
3031 "github.com/open-policy-agent/opa/v1/format"
32+ "github.com/styrainc/regal/pkg/config"
3133 "github.com/styrainc/regal/pkg/linter"
3234 "github.com/styrainc/regal/pkg/rules"
35+ "gopkg.in/yaml.v2"
3336)
3437
38+ //go:embed .regal.yaml
39+ var regalConfigFS embed.FS
40+
3541type PolicyToLint struct {
3642 Path string
3743 YAMLFiles []* File
3844 RegoFiles []* File
3945 Format bool
46+ Config string
4047 Errors []ValidationError
4148}
4249
@@ -73,7 +80,7 @@ func (p *PolicyToLint) AddError(path, message string, line int) {
7380}
7481
7582// Read policy files from the given directory or file
76- func Lookup (absPath string , format bool ) (* PolicyToLint , error ) {
83+ func Lookup (absPath , config string , format bool ) (* PolicyToLint , error ) {
7784 fileInfo , err := os .Stat (absPath )
7885 if err != nil {
7986 if os .IsNotExist (err ) {
@@ -85,6 +92,7 @@ func Lookup(absPath string, format bool) (*PolicyToLint, error) {
8592 policy := & PolicyToLint {
8693 Path : absPath ,
8794 Format : format ,
95+ Config : config ,
8896 }
8997
9098 if fileInfo .IsDir () {
@@ -277,7 +285,18 @@ func (p *PolicyToLint) runRegalLinter(filePath, content string) {
277285 return
278286 }
279287
288+ // Initialize linter with input modules
280289 lntr := linter .NewLinter ().WithInputModules (& inputModules )
290+
291+ // Load and apply configuration
292+ cfg , err := p .loadConfig ()
293+ if err != nil {
294+ p .AddError (filePath , fmt .Sprintf ("%s" , err ), 0 )
295+ }
296+ if cfg != nil {
297+ lntr = lntr .WithUserConfig (* cfg )
298+ }
299+
281300 report , err := lntr .Lint (context .Background ())
282301 if err != nil {
283302 p .AddError (filePath , fmt .Sprintf ("linting failed: %v" , err ), 0 )
@@ -290,6 +309,51 @@ func (p *PolicyToLint) runRegalLinter(filePath, content string) {
290309 }
291310}
292311
312+ // Attempts to load configuration in this order:
313+ // 1. User-specified config
314+ // 2. Default config
315+ // Returns nil config if no config found at all
316+ func (p * PolicyToLint ) loadConfig () (* config.Config , error ) {
317+ // 1. Try user-specified config first
318+ if p .Config != "" {
319+ userCfg , err := config .FromPath (p .Config )
320+ if err == nil {
321+ return & userCfg , nil
322+ }
323+ // If user config fails, we'll fall through to default config
324+ userErr := fmt .Errorf ("failed to load user config from %q: %w (using default config)" , p .Config , err )
325+
326+ // Continue to try default config
327+ defaultCfg , defaultErr := p .loadDefaultConfig ()
328+ if defaultErr == nil {
329+ return defaultCfg , userErr
330+ }
331+ return nil , fmt .Errorf ("%w; also %w" , userErr , defaultErr )
332+ }
333+
334+ // 2. No user config specified - try default config
335+ return p .loadDefaultConfig ()
336+ }
337+
338+ func (p * PolicyToLint ) loadDefaultConfig () (* config.Config , error ) {
339+ cfgData , err := regalConfigFS .ReadFile (".regal.yaml" )
340+ if err != nil {
341+ return nil , fmt .Errorf ("failed to read default config: %w" , err )
342+ }
343+
344+ var configMap map [string ]interface {}
345+ if err := yaml .Unmarshal (cfgData , & configMap ); err != nil {
346+ return nil , fmt .Errorf ("failed to parse default config: %w" , err )
347+ }
348+
349+ cfg , err := config .FromMap (configMap )
350+ if err != nil {
351+ return nil , fmt .Errorf ("failed to convert default config: %w" , err )
352+ }
353+
354+ return & cfg , nil
355+ }
356+
293357// Splits grouped errors into individual errors
294358func (p * PolicyToLint ) processRegalViolation (rawErr error , path string ) {
295359 if rawErr == nil {
0 commit comments