Skip to content

Commit 8eb3774

Browse files
authored
feat(policy): add default and user regal config to lint (#2277)
Signed-off-by: Sylwester Piskozub <[email protected]>
1 parent 912b127 commit 8eb3774

File tree

5 files changed

+95
-11
lines changed

5 files changed

+95
-11
lines changed

app/cli/cmd/policy_develop_lint.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ import (
2424

2525
func newPolicyDevelopLintCmd() *cobra.Command {
2626
var (
27-
policyPath string
28-
format bool
27+
policyPath string
28+
format bool
29+
regalConfig string
2930
)
3031

3132
cmd := &cobra.Command{
@@ -41,8 +42,9 @@ func newPolicyDevelopLintCmd() *cobra.Command {
4142
}
4243

4344
result, err := a.Run(cmd.Context(), &action.PolicyLintOpts{
44-
PolicyPath: policyPath,
45-
Format: format,
45+
PolicyPath: policyPath,
46+
Format: format,
47+
RegalConfig: regalConfig,
4648
})
4749
if err != nil {
4850
return fmt.Errorf("linting failed: %w", err)
@@ -63,5 +65,6 @@ func newPolicyDevelopLintCmd() *cobra.Command {
6365

6466
cmd.Flags().StringVarP(&policyPath, "policy", "p", ".", "Path to policy directory")
6567
cmd.Flags().BoolVar(&format, "format", false, "Auto-format file with opa fmt")
68+
cmd.Flags().StringVar(&regalConfig, "regal-config", "", "Path to custom regal config (Default: https://github.com/chainloop-dev/chainloop/tree/main/app/cli/internal/policydevel/.regal.yaml)")
6669
return cmd
6770
}

app/cli/documentation/cli-reference.mdx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2887,9 +2887,10 @@ chainloop policy develop lint [flags]
28872887
Options
28882888

28892889
```
2890-
--format Auto-format file with opa fmt
2891-
-h, --help help for lint
2892-
-p, --policy string Path to policy directory (default ".")
2890+
--format Auto-format file with opa fmt
2891+
-h, --help help for lint
2892+
-p, --policy string Path to policy directory (default ".")
2893+
--regal-config string Path to custom regal config (Default: https://github.com/chainloop-dev/chainloop/tree/main/app/cli/internal/policydevel/.regal.yaml)
28932894
```
28942895

28952896
Options inherited from parent commands

app/cli/internal/action/policy_develop_lint.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ import (
2424
)
2525

2626
type PolicyLintOpts struct {
27-
PolicyPath string
28-
Format bool
27+
PolicyPath string
28+
Format bool
29+
RegalConfig string
2930
}
3031

3132
type PolicyLintResult struct {
@@ -51,7 +52,7 @@ func (action *PolicyLint) Run(_ context.Context, opts *PolicyLintOpts) (*PolicyL
5152
}
5253

5354
// Read policies
54-
policy, err := policydevel.Lookup(absPath, opts.Format)
55+
policy, err := policydevel.Lookup(absPath, opts.RegalConfig, opts.Format)
5556
if err != nil {
5657
return nil, fmt.Errorf("loading policy: %w", err)
5758
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# based from https://github.com/spacelift-io/spacelift-policies-example-library/blob/main/.regal/config.yaml
2+
# Default config
3+
rules:
4+
idiomatic:
5+
directory-package-mismatch:
6+
level: ignore
7+
no-defined-entrypoint:
8+
level: warning
9+
style:
10+
todo-comment:
11+
level: ignore
12+
line-length:
13+
level: ignore
14+
rule-length:
15+
level: ignore

app/cli/internal/policydevel/lint.go

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package policydevel
1717

1818
import (
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+
3541
type 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
294358
func (p *PolicyToLint) processRegalViolation(rawErr error, path string) {
295359
if rawErr == nil {

0 commit comments

Comments
 (0)