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
2 changes: 1 addition & 1 deletion app/cli/cmd/policy_develop.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ Refer to https://docs.chainloop.dev/guides/custom-policies
`,
}

cmd.AddCommand(newPolicyDevelopInitCmd())
cmd.AddCommand(newPolicyDevelopInitCmd(), newPolicyDevelopLintCmd())
return cmd
}
67 changes: 67 additions & 0 deletions app/cli/cmd/policy_develop_lint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//
// Copyright 2025 The Chainloop Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
"fmt"

"github.com/chainloop-dev/chainloop/app/cli/internal/action"
"github.com/spf13/cobra"
)

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

cmd := &cobra.Command{
Use: "lint",
Short: "Lint chainloop policy structure and content",
Long: `Performs comprehensive validation of:
- *.yaml files (schema validation)
- *.rego (formatting, linting, structure)`,
RunE: func(cmd *cobra.Command, _ []string) error {
a, err := action.NewPolicyLint(actionOpts)
if err != nil {
return fmt.Errorf("failed to initialize linter: %w", err)
}

result, err := a.Run(cmd.Context(), &action.PolicyLintOpts{
PolicyPath: policyPath,
Format: format,
})
if err != nil {
return fmt.Errorf("linting failed: %w", err)
}

if result.Valid {
logger.Info().Msg("policy is valid!")
return nil
}

// Print all validation errors
for _, err := range result.Errors {
logger.Error().Msg(err)
}
return fmt.Errorf("policy validation failed with %d issues", len(result.Errors))
},
}

cmd.Flags().StringVarP(&policyPath, "policy", "p", ".", "Path to policy directory")
cmd.Flags().BoolVar(&format, "format", false, "Auto-format file with opa fmt")
return cmd
}
38 changes: 38 additions & 0 deletions app/cli/documentation/cli-reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2870,6 +2870,44 @@ Options inherited from parent commands
-y, --yes Skip confirmation
```

#### chainloop policy develop lint

Lint chainloop policy structure and content

Synopsis

Performs comprehensive validation of:
- *.yaml files (schema validation)
- *.rego (formatting, linting, structure)

```
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 ".")
```

Options inherited from parent commands

```
--artifact-cas string URL for the Artifacts Content Addressable Storage API ($CHAINLOOP_ARTIFACT_CAS_API) (default "api.cas.chainloop.dev:443")
--artifact-cas-ca string CUSTOM CA file for the Artifacts CAS API (optional) ($CHAINLOOP_ARTIFACT_CAS_API_CA)
-c, --config string Path to an existing config file (default is $HOME/.config/chainloop/config.toml)
--control-plane string URL for the Control Plane API ($CHAINLOOP_CONTROL_PLANE_API) (default "api.cp.chainloop.dev:443")
--control-plane-ca string CUSTOM CA file for the Control Plane API (optional) ($CHAINLOOP_CONTROL_PLANE_API_CA)
--debug Enable debug/verbose logging mode
-i, --insecure Skip TLS transport during connection to the control plane ($CHAINLOOP_API_INSECURE)
-n, --org string organization name
-o, --output string Output format, valid options are json and table (default "table")
-t, --token string API token. NOTE: Alternatively use the env variable CHAINLOOP_TOKEN
-y, --yes Skip confirmation
```

### chainloop policy help

Help about any command
Expand Down
74 changes: 74 additions & 0 deletions app/cli/internal/action/policy_develop_lint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//
// Copyright 2025 The Chainloop Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package action

import (
"context"
"fmt"
"path/filepath"

"github.com/chainloop-dev/chainloop/app/cli/internal/policydevel"
)

type PolicyLintOpts struct {
PolicyPath string
Format bool
}

type PolicyLintResult struct {
Valid bool
Errors []string
}

type PolicyLint struct {
*ActionsOpts
}

func NewPolicyLint(actionOpts *ActionsOpts) (*PolicyLint, error) {
return &PolicyLint{
ActionsOpts: actionOpts,
}, nil
}

func (action *PolicyLint) Run(_ context.Context, opts *PolicyLintOpts) (*PolicyLintResult, error) {
Comment thread
migmartri marked this conversation as resolved.
// Resolve absolute path to policy directory
absPath, err := filepath.Abs(opts.PolicyPath)
if err != nil {
return nil, fmt.Errorf("resolving absolute path: %w", err)
}

// Read policies
policy, err := policydevel.Lookup(absPath, opts.Format)
if err != nil {
return nil, fmt.Errorf("loading policy: %w", err)
}

// Run all validations
policy.Validate()

// Prepare result
result := &PolicyLintResult{
Valid: !policy.HasErrors(),
Errors: make([]string, 0, len(policy.Errors)),
}

// Convert validation errors to strings
for _, err := range policy.Errors {
result.Errors = append(result.Errors, err.Error())
}

return result, nil
}
Loading
Loading