Skip to content

feat: add policy develop lint#2250

Merged
Piskoo merged 7 commits into
chainloop-dev:mainfrom
Piskoo:feat-add-policy-develop-lint
Jul 18, 2025
Merged

feat: add policy develop lint#2250
Piskoo merged 7 commits into
chainloop-dev:mainfrom
Piskoo:feat-add-policy-develop-lint

Conversation

@Piskoo

@Piskoo Piskoo commented Jul 14, 2025

Copy link
Copy Markdown
Contributor

This PR adds policy linting and auto-formatting capabilities

Usage

# Single file that will get auto formatted
$ chainloop policy devel lint --policy mypolicy.yaml --format
  
# All files in the directory no formatting
$ chainloop policy devel lint

Flags:

  • --policy: File or directory, by default current working directory
  • --format: Auto-formatting for rego files with OPA fmt

Summary

  • Validates both standalone Rego files and YAML policies with embedded Rego
  • Checks for required policy structure and fields
    • apiVersion, kind, spec for .yaml files
    • skipped, violations, skip_reason for result in .rego files
  • Verifies proper Rego syntax using Regal linter
  • Optional auto-formatting of Rego code with --format flag
  • Preserves YAML structure and comments when formatting embedded policies

Part of #1108

Piskoo added 2 commits July 14, 2025 13:23
Signed-off-by: Sylwester Piskozub <sylwesterpiskozub@gmail.com>
Signed-off-by: Sylwester Piskozub <sylwesterpiskozub@gmail.com>
@Piskoo Piskoo marked this pull request as ready for review July 14, 2025 11:53

@migmartri migmartri left a comment

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 think we should re-evaluate the marshaling and validation of the policy spec by using the actual spec file, not custom ducktyping.

basically import the policy spec generated code, we might do it in other places already, then do protojson.Unmarshal or similar

additionally, once you have the marshalled object, you might be able to run protovalidate if the proto file has additional validation annotations.

Does it make sense?

Comment thread app/cli/internal/policydevel/lint.go Outdated
// See the License for the specific language governing permissions and
// limitations under the License.

package policy

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.

this package name should probably match the directory name that way you do not need to policy "github.com/chainloop-dev/chainloop/app/cli/internal/policydevel" :)

Comment thread app/cli/internal/policydevel/lint.go Outdated
return nil, fmt.Errorf("reading file: %w", err)
}

switch strings.ToLower(filepath.Ext(absPath)) {

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.

this code seems repeated ot the one above. Maybe this detection can be extracted? It looks like a good opportunity to maybe to a recursive function when you find potential candidate files in a directory :)

Comment thread app/cli/internal/policydevel/lint.go Outdated
}

// Process each embedded policy
if spec, ok := data["spec"].(map[string]interface{}); ok {

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 think you should unmarshal against the actual proto spec using protojson.Unmarshal and protovalidaet.Validate.

Piskoo added 2 commits July 16, 2025 14:03
… replicated code

Signed-off-by: Sylwester Piskozub <sylwesterpiskozub@gmail.com>
Signed-off-by: Sylwester Piskozub <sylwesterpiskozub@gmail.com>
@javirln

javirln commented Jul 16, 2025

Copy link
Copy Markdown
Member

Thanks for you PR @Piskoo one question, how difficult would it be to be able to pass a custom configuration file to the regal module?

Internally we have a set of custom configuration for regal and we were wondering how difficult would it be to add such option.

@migmartri

Copy link
Copy Markdown
Member

Thanks for you PR @Piskoo one question, how difficult would it be to be able to pass a custom configuration file to the regal module?

Internally we have a set of custom configuration for regal and we were wondering how difficult would it be to add such option.

I'd love it if we could come up with sensible defaults for that configuration, which one are we using internally?

@migmartri

Copy link
Copy Markdown
Member

Thanks for you PR @Piskoo one question, how difficult would it be to be able to pass a custom configuration file to the regal module?
Internally we have a set of custom configuration for regal and we were wondering how difficult would it be to add such option.

I'd love it if we could come up with sensible defaults for that configuration, which one are we using internally?

I think we are using this one correct? Do we like it? Should we make that a sensible default for our linter? cc/ @javirln @jiparis

# based from https://github.com/spacelift-io/spacelift-policies-example-library/blob/main/.regal/config.yaml
rules:
  idiomatic:
    directory-package-mismatch:
      level: ignore
    no-defined-entrypoint:
      level: warning
  style:
    todo-comment:
      level: ignore
    line-length:
      level: ignore
    rule-length:
      level: ignore
      ignore:
        files:
          - "*_test.rego"

in any case @Piskoo, adding support for configuration override or adding one built-in could come in another PR

Comment thread app/cli/internal/action/policy_develop_lint.go
Comment thread app/cli/internal/policydevel/lint.go Outdated
}

// Read policy files from the given directory or file
func Read(absPath string, format bool) (*Policy, error) {

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.

Suggested change
func Read(absPath string, format bool) (*Policy, error) {
func Lookup(absPath string, format bool) (*Policy, error) {

Comment thread app/cli/internal/policydevel/lint.go Outdated
"github.com/styrainc/regal/pkg/rules"
)

type Policy struct {

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.

Suggested change
type Policy struct {
type PolicyToLint struct {


// Read policy files from the given directory or file
func Read(absPath string, format bool) (*Policy, error) {
fileInfo, err := os.Stat(absPath)

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.

capture other errors too

Comment thread app/cli/internal/policydevel/lint.go Outdated
return policy, nil
}

func readDirectory(policy *Policy, dirPath string) error {

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.

please add a comment indicating that this is a one level directory lookup

Comment thread app/cli/internal/policydevel/lint.go Outdated
return allErrors
}

func (p *Policy) validateYAMLFile(file *File) []error {

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 do not think this is idiomatic. []error just return an error and use https://pkg.go.dev/errors#example-Join

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've changed the way that violations are stored/passed between functions

Comment thread app/cli/internal/policydevel/lint.go Outdated
}

// 2. Structural validation
errs = append(errs, checkResultStructure(content, path, []string{"skipped", "violations", "skip_reason"})...)

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.

@jiparis should we try to add here ignore as well? Or no? The engine by default fallback to false

@javirln

javirln commented Jul 17, 2025

Copy link
Copy Markdown
Member

Thanks for you PR @Piskoo one question, how difficult would it be to be able to pass a custom configuration file to the regal module?
Internally we have a set of custom configuration for regal and we were wondering how difficult would it be to add such option.

I'd love it if we could come up with sensible defaults for that configuration, which one are we using internally?

I think we are using this one correct? Do we like it? Should we make that a sensible default for our linter? cc/ @javirln @jiparis

# based from https://github.com/spacelift-io/spacelift-policies-example-library/blob/main/.regal/config.yaml
rules:
  idiomatic:
    directory-package-mismatch:
      level: ignore
    no-defined-entrypoint:
      level: warning
  style:
    todo-comment:
      level: ignore
    line-length:
      level: ignore
    rule-length:
      level: ignore
      ignore:
        files:
          - "*_test.rego"

in any case @Piskoo, adding support for configuration override or adding one built-in could come in another PR

From the list of rules we are ignoring on the custom configuration, I would say that we can remove the _test.rego since that's something specific to tests and shouldn't affect the actual policy.

The most important is no-defined-entrypoint. Our engine expects a custom result structure and we don't care about the entrypoint since the policies are meant to be run in our engine. Here's some documentation: https://docs.styra.com/regal/rules/idiomatic/no-defined-entrypoint

The rest of the options is up to us, not a strong opinion there.

@migmartri

Copy link
Copy Markdown
Member

@Piskoo let me know what you think about adding the following defaults

# based from https://github.com/spacelift-io/spacelift-policies-example-library/blob/main/.regal/config.yaml
rules:
  idiomatic:
    directory-package-mismatch:
      level: ignore
    no-defined-entrypoint:
      level: warning
  style:
    todo-comment:
      level: ignore
    line-length:
      level: ignore
    rule-length:
      level: ignore
      ignore:
        files:
          - "*_test.rego"

once done we can assume this PR is done :)

Piskoo added 3 commits July 18, 2025 09:12
Signed-off-by: Sylwester Piskozub <sylwesterpiskozub@gmail.com>
Signed-off-by: Sylwester Piskozub <sylwesterpiskozub@gmail.com>
@Piskoo

Piskoo commented Jul 18, 2025

Copy link
Copy Markdown
Contributor Author

@Piskoo let me know what you think about adding the following defaults

# based from https://github.com/spacelift-io/spacelift-policies-example-library/blob/main/.regal/config.yaml
rules:
  idiomatic:
    directory-package-mismatch:
      level: ignore
    no-defined-entrypoint:
      level: warning
  style:
    todo-comment:
      level: ignore
    line-length:
      level: ignore
    rule-length:
      level: ignore
      ignore:
        files:
          - "*_test.rego"

once done we can assume this PR is done :)

Looks good to me, although like @javirln mentioned _test.rego is not needed, so we can remove that from the default config.

@migmartri

Copy link
Copy Markdown
Member

Let me know if this is ready for review, otherwise, please mark it as draft, thanks!

@Piskoo

Piskoo commented Jul 18, 2025

Copy link
Copy Markdown
Contributor Author

Let me know if this is ready for review, otherwise, please mark it as draft, thanks!

It's ready for review, I'll add configs in another PR

@migmartri migmartri left a comment

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.

Thanks

@Piskoo Piskoo merged commit f252dd8 into chainloop-dev:main Jul 18, 2025
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants