Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions app/cli/cmd/policy_develop_lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ import (

func newPolicyDevelopLintCmd() *cobra.Command {
var (
policyPath string
format bool
policyPath string
format bool
regalConfig string
)

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

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

cmd.Flags().StringVarP(&policyPath, "policy", "p", ".", "Path to policy directory")
cmd.Flags().BoolVar(&format, "format", false, "Auto-format file with opa fmt")
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)")
return cmd
}
7 changes: 4 additions & 3 deletions app/cli/documentation/cli-reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2887,9 +2887,10 @@ chainloop policy develop lint [flags]
Options

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

Options inherited from parent commands
Expand Down
7 changes: 4 additions & 3 deletions app/cli/internal/action/policy_develop_lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ import (
)

type PolicyLintOpts struct {
PolicyPath string
Format bool
PolicyPath string
Format bool
RegalConfig string
}

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

// Read policies
policy, err := policydevel.Lookup(absPath, opts.Format)
policy, err := policydevel.Lookup(absPath, opts.RegalConfig, opts.Format)
if err != nil {
return nil, fmt.Errorf("loading policy: %w", err)
}
Expand Down
15 changes: 15 additions & 0 deletions app/cli/internal/policydevel/.regal.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# based from https://github.com/spacelift-io/spacelift-policies-example-library/blob/main/.regal/config.yaml
# Default config
rules:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd keep the credits for the original author, i.e spacelift I think?

idiomatic:
directory-package-mismatch:
level: ignore
no-defined-entrypoint:
level: warning
style:
todo-comment:
level: ignore
line-length:
level: ignore
rule-length:
level: ignore
66 changes: 65 additions & 1 deletion app/cli/internal/policydevel/lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package policydevel

import (
"context"
"embed"
"fmt"
"os"
"path/filepath"
Expand All @@ -28,15 +29,21 @@ import (
v1 "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
"github.com/chainloop-dev/chainloop/app/controlplane/pkg/unmarshal"
"github.com/open-policy-agent/opa/v1/format"
"github.com/styrainc/regal/pkg/config"
"github.com/styrainc/regal/pkg/linter"
"github.com/styrainc/regal/pkg/rules"
"gopkg.in/yaml.v2"
)

//go:embed .regal.yaml
var regalConfigFS embed.FS

type PolicyToLint struct {
Path string
YAMLFiles []*File
RegoFiles []*File
Format bool
Config string
Errors []ValidationError
}

Expand Down Expand Up @@ -73,7 +80,7 @@ func (p *PolicyToLint) AddError(path, message string, line int) {
}

// Read policy files from the given directory or file
func Lookup(absPath string, format bool) (*PolicyToLint, error) {
func Lookup(absPath, config string, format bool) (*PolicyToLint, error) {
fileInfo, err := os.Stat(absPath)
if err != nil {
if os.IsNotExist(err) {
Expand All @@ -85,6 +92,7 @@ func Lookup(absPath string, format bool) (*PolicyToLint, error) {
policy := &PolicyToLint{
Path: absPath,
Format: format,
Config: config,
}

if fileInfo.IsDir() {
Expand Down Expand Up @@ -277,7 +285,18 @@ func (p *PolicyToLint) runRegalLinter(filePath, content string) {
return
}

// Initialize linter with input modules
lntr := linter.NewLinter().WithInputModules(&inputModules)

// Load and apply configuration
cfg, err := p.loadConfig()
if err != nil {
p.AddError(filePath, fmt.Sprintf("%s", err), 0)
}
if cfg != nil {
lntr = lntr.WithUserConfig(*cfg)
}

report, err := lntr.Lint(context.Background())
if err != nil {
p.AddError(filePath, fmt.Sprintf("linting failed: %v", err), 0)
Expand All @@ -290,6 +309,51 @@ func (p *PolicyToLint) runRegalLinter(filePath, content string) {
}
}

// Attempts to load configuration in this order:
// 1. User-specified config
// 2. Default config
// Returns nil config if no config found at all
func (p *PolicyToLint) loadConfig() (*config.Config, error) {
// 1. Try user-specified config first
if p.Config != "" {
userCfg, err := config.FromPath(p.Config)
if err == nil {
return &userCfg, nil
}
// If user config fails, we'll fall through to default config
userErr := fmt.Errorf("failed to load user config from %q: %w (using default config)", p.Config, err)

// Continue to try default config
defaultCfg, defaultErr := p.loadDefaultConfig()
if defaultErr == nil {
return defaultCfg, userErr
}
return nil, fmt.Errorf("%w; also %w", userErr, defaultErr)
}

// 2. No user config specified - try default config
return p.loadDefaultConfig()
}

func (p *PolicyToLint) loadDefaultConfig() (*config.Config, error) {
cfgData, err := regalConfigFS.ReadFile(".regal.yaml")
if err != nil {
return nil, fmt.Errorf("failed to read default config: %w", err)
}

var configMap map[string]interface{}
if err := yaml.Unmarshal(cfgData, &configMap); err != nil {
return nil, fmt.Errorf("failed to parse default config: %w", err)
}

cfg, err := config.FromMap(configMap)
if err != nil {
return nil, fmt.Errorf("failed to convert default config: %w", err)
}

return &cfg, nil
}

// Splits grouped errors into individual errors
func (p *PolicyToLint) processRegalViolation(rawErr error, path string) {
if rawErr == nil {
Expand Down
Loading