ci: enforce Rego v1 policies with Regal#408
Conversation
There was a problem hiding this comment.
Pull request overview
Adds CI enforcement to keep all embedded Rego policies compliant with Rego v1 and prevent formatting drift by extracting YAML-embedded policies, normalizing them with OPA, and linting them with Regal.
Changes:
- Added
scripts/extract-rego.pyto extract embedded Regodef: |blocks into standalone.regofiles. - Added
.regal/config.yamlto enforce Regal rules (use-rego-v1,opa-fmt) with pinned OPA capabilities. - Extended
.github/workflows/lint.yamlto install OPA, check embedded formatting, extract/normalize policies, and lint with Regal.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| scripts/extract-rego.py | New utility to extract embedded Rego blocks from YAML into standalone .rego files for linting/format checks. |
| .regal/config.yaml | Regal configuration enabling use-rego-v1 and opa-fmt, plus OPA capabilities pinning. |
| .github/workflows/lint.yaml | CI job additions to install OPA, validate embedded formatting, extract/normalize policies, and run Regal lint. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def output_name(path: pathlib.Path, policy_index: int) -> str: | ||
| normalized = "__".join(path.parts) | ||
| return f"{normalized}.{policy_index}.rego" |
e8b0710 to
84f4a4c
Compare
evankanderson
left a comment
There was a problem hiding this comment.
You may want to take a look at mindersec/minder#6558; that PR would allow us to move all the rego files to be .rego with comments for the rest of the RuleType definition, rather than embedded rego inside YAML.
Thanks for pointing me to mindersec/minder#6558. It looks like that approach could remove the need to extract Rego from YAML before linting. Would you prefer that I wait for mindersec/minder#6558 and update this PR to lint the .rego files directly, or keep the current approach as a temporary check for the existing YAML format? |
There was a problem hiding this comment.
It feels like you're extracting a single (well-known) YAML field from a document here -- this should almost certainly use pyyaml rather than string processing. (Or you could use yq and some shell, but I'm okay with using Python)
There was a problem hiding this comment.
I personally like python more, can change it if it's necessary.
There was a problem hiding this comment.
I'm happy to keep using python, but I'd like to use the yaml library for parsing, rather than ad-hoc parsing with regular expressions and python code.
There was a problem hiding this comment.
Will keep this in mind
evankanderson
left a comment
There was a problem hiding this comment.
I think it's fine to merge this now, when the rego change merges we can hopefully simplify a lot of this. (And seeing this pile higher motivated me to go in and try to fix things at the root.)
There was a problem hiding this comment.
I'm happy to keep using python, but I'd like to use the yaml library for parsing, rather than ad-hoc parsing with regular expressions and python code.
|
@evankanderson |
evankanderson
left a comment
There was a problem hiding this comment.
This should use the standard YAML module for the extraction. (It's fine to use python rather than e.g. yq .def.eval.rego.def $filename), but let's not roll our own extraction code when the library handles that case just fine.)
| lines = path.read_text().splitlines(True) | ||
| policies: list[str] = [] | ||
| index = 0 | ||
|
|
||
| while index < len(lines): | ||
| match = DEF_BLOCK_RE.match(lines[index]) | ||
| if not match: | ||
| index += 1 | ||
| continue | ||
|
|
||
| source, index = extract_block( | ||
| lines, | ||
| index + 1, | ||
| len(match.group("indent")), | ||
| ) | ||
| if source.lstrip().startswith("package "): | ||
| policies.append(source) | ||
|
|
||
| return policies |
There was a problem hiding this comment.
If you use PyYAML, this is:
| lines = path.read_text().splitlines(True) | |
| policies: list[str] = [] | |
| index = 0 | |
| while index < len(lines): | |
| match = DEF_BLOCK_RE.match(lines[index]) | |
| if not match: | |
| index += 1 | |
| continue | |
| source, index = extract_block( | |
| lines, | |
| index + 1, | |
| len(match.group("indent")), | |
| ) | |
| if source.lstrip().startswith("package "): | |
| policies.append(source) | |
| return policies | |
| contents = yaml.load(path.read_text()) | |
| rego = contents.get('def', {}).get('eval', {}),.get('rego', {}).get('def', "") | |
| if rego != "": | |
| return [rego] | |
| return [] |
And you don't need extract_block, etc.
There was a problem hiding this comment.
Done, it now reads def.eval.rego.def with safe_load, and the old extraction helpers are removed.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
39c5af6 to
1cd15d4
Compare
|
Apologies for a late revert, I was a bit busy from the past few days, I have made the changes, you can have a look now! |
Part of mindersec/minder#5262
Resolves #409
Summary
Adds a CI guardrail that keeps official rule policies on Rego v1 and prevents formatting regressions.
What changed
use-rego-v1andopa-fmt.opa fmt --v0-v1.The Regal job is intentionally limited to the two migration rules, so this PR does not introduce unrelated style cleanup.
Testing