diff --git a/README.md b/README.md index 0515b9c3..62bf299f 100644 --- a/README.md +++ b/README.md @@ -97,15 +97,16 @@ First, author some Rego! ```rego package authz -import future.keywords +import rego.v1 default allow = false -deny if { - "admin" != input.user.roles[_] +allow if { + isEmployee + "developer" in input.user.roles } -allow if not deny +isEmployee if regex.match("@acmecorp\\.com$", input.user.email) ``` Next, run `regal lint` pointed at one or more files or directories to have them linted. @@ -116,19 +117,12 @@ regal lint policy/ ```text -Rule: not-equals-in-loop -Description: Use of != in loop -Category: bugs -Location: policy/authz.rego:8:10 -Text: "admin" != input.user.roles[_] -Documentation: https://docs.styra.com/regal/rules/bugs/not-equals-in-loop - -Rule: implicit-future-keywords -Description: Use explicit future keyword imports -Category: imports -Location: policy/authz.rego:3:8 -Text: import future.keywords -Documentation: https://docs.styra.com/regal/rules/imports/implicit-future-keywords +Rule: non-raw-regex-pattern +Description: Use raw strings for regex patterns +Category: idiomatic +Location: policy/authz.rego:12:27 +Text: isEmployee if regex.match("@acmecorp\\.com$", input.user.email) +Documentation: https://docs.styra.com/regal/rules/idiomatic/non-raw-regex-pattern Rule: use-assignment-operator Description: Prefer := over = for assignment @@ -137,6 +131,13 @@ Location: policy/authz.rego:5:1 Text: default allow = false Documentation: https://docs.styra.com/regal/rules/style/use-assignment-operator +Rule: prefer-snake-case +Description: Prefer snake_case for names +Category: style +Location: policy/authz.rego:12:1 +Text: isEmployee if regex.match("@acmecorp\\.com$", input.user.email) +Documentation: https://docs.styra.com/regal/rules/style/prefer-snake-case + 1 file linted. 3 violations found. ``` @@ -224,6 +225,7 @@ The following rules are currently available: | imports | [prefer-package-imports](https://docs.styra.com/regal/rules/imports/prefer-package-imports) | Prefer importing packages over rules | | imports | [redundant-alias](https://docs.styra.com/regal/rules/imports/redundant-alias) | Redundant alias | | imports | [redundant-data-import](https://docs.styra.com/regal/rules/imports/redundant-data-import) | Redundant import of data | +| imports | [use-rego-v1](https://docs.styra.com/regal/rules/imports/use-rego-v1) | Use `import rego.v1` | | style | [avoid-get-and-list-prefix](https://docs.styra.com/regal/rules/style/avoid-get-and-list-prefix) | Avoid `get_` and `list_` prefix for rules and functions | | style | [chained-rule-body](https://docs.styra.com/regal/rules/style/chained-rule-body) | Avoid chaining rule bodies | | style | [default-over-else](https://docs.styra.com/regal/rules/style/default-over-else) | Prefer default assignment over fallback else | diff --git a/bundle/regal/config/provided/data.yaml b/bundle/regal/config/provided/data.yaml index 853a876c..a1dcd457 100644 --- a/bundle/regal/config/provided/data.yaml +++ b/bundle/regal/config/provided/data.yaml @@ -77,6 +77,8 @@ rules: level: error redundant-data-import: level: error + use-rego-v1: + level: error style: avoid-get-and-list-prefix: level: error diff --git a/bundle/regal/regal.rego b/bundle/regal/regal.rego index ac3d8c47..25ae13b4 100644 --- a/bundle/regal/regal.rego +++ b/bundle/regal/regal.rego @@ -7,3 +7,5 @@ # schemas: # - input: schema.regal.ast package regal + +import rego.v1 diff --git a/bundle/regal/rules/idiomatic/use_if.rego b/bundle/regal/rules/idiomatic/use_if.rego index e3375ff1..f3f1007b 100644 --- a/bundle/regal/rules/idiomatic/use_if.rego +++ b/bundle/regal/rules/idiomatic/use_if.rego @@ -8,10 +8,6 @@ import data.regal.ast import data.regal.capabilities import data.regal.result -# Note: think more about what UX we want when import_rego_v1 -# capbility is available. Should we simply just recommend that -# and silence this rule in that case? I'm inclined to say yes. - # METADATA # description: Missing capability for keyword `if` # custom: diff --git a/bundle/regal/rules/imports/use_rego_v1.rego b/bundle/regal/rules/imports/use_rego_v1.rego new file mode 100644 index 00000000..d2cb8d9c --- /dev/null +++ b/bundle/regal/rules/imports/use_rego_v1.rego @@ -0,0 +1,21 @@ +# METADATA +# description: Use `import rego.v1` +package regal.rules.imports["use-rego-v1"] + +import rego.v1 + +import data.regal.ast +import data.regal.capabilities +import data.regal.result + +# METADATA +# description: Missing capability for `import rego.v1` +# custom: +# severity: warning +notices contains result.notice(rego.metadata.chain()) if not capabilities.has_rego_v1_feature + +report contains violation if { + not ast.imports_has_path(ast.imports, ["rego", "v1"]) + + violation := result.fail(rego.metadata.chain(), result.location(input["package"])) +} diff --git a/bundle/regal/rules/imports/use_rego_v1_test.rego b/bundle/regal/rules/imports/use_rego_v1_test.rego new file mode 100644 index 00000000..cf25208f --- /dev/null +++ b/bundle/regal/rules/imports/use_rego_v1_test.rego @@ -0,0 +1,37 @@ +package regal.rules.imports["use-rego-v1_test"] + +import rego.v1 + +import data.regal.capabilities +import data.regal.config +import data.regal.rules.imports["use-rego-v1"] as rule + +test_fail_missing_rego_v1_import if { + r := rule.report with input as regal.parse_module("policy.rego", `package policy + import future.keywords + + foo if not bar + `) + with data.internal.combined_config as {"capabilities": capabilities.provided} + r == {{ + "category": "imports", + "description": "Use `import rego.v1`", + "related_resources": [{ + "description": "documentation", + "ref": config.docs.resolve_url("$baseUrl/$category/use-rego-v1", "imports"), + }], + "title": "use-rego-v1", + "location": {"col": 1, "file": "policy.rego", "row": 1, "text": "package policy"}, + "level": "error", + }} +} + +test_success_rego_v1_import if { + r := rule.report with input as regal.parse_module("policy.rego", `package policy + import rego.v1 + + foo if not bar + `) + with data.internal.combined_config as {"capabilities": capabilities.provided} + r == set() +} diff --git a/docs/custom-rules.md b/docs/custom-rules.md index 5948ce1e..49b704e6 100644 --- a/docs/custom-rules.md +++ b/docs/custom-rules.md @@ -118,8 +118,7 @@ An example policy to implement this requirement might look something like this: # - input: schema.regal.ast package custom.regal.rules.naming["acme-corp-package"] -import future.keywords.contains -import future.keywords.if +import rego.v1 import data.regal.result @@ -186,9 +185,7 @@ from files, and one that actually lints and reports violations using that data. # - input: schema.regal.ast package custom.regal.rules.organizational["at-least-one-allow"] -import future.keywords.contains -import future.keywords.if -import future.keywords.in +import rego.v1 import data.regal.ast import data.regal.result @@ -196,15 +193,15 @@ import data.regal.result aggregate contains entry if { # ast.rules is input.rules with functions filtered out some rule in ast.rules - + # search for rule named alllow ast.name(rule) == "allow" - + # make sure it's a default assignemnt # ideally we'll want more than that, but the *requiremnt* is only # that such a rule exists... rule["default"] == true - + # ...and that it defaults to false rule.head.value.type == "boolean" rule.head.value.value == false diff --git a/docs/rules/bugs/constant-condition.md b/docs/rules/bugs/constant-condition.md index f6171e37..a042559b 100644 --- a/docs/rules/bugs/constant-condition.md +++ b/docs/rules/bugs/constant-condition.md @@ -8,7 +8,7 @@ ```rego package policy -import future.keywords.if +import rego.v1 allow if { 1 == 1 @@ -32,7 +32,7 @@ harmless, it has no place in production policy, and should be replaced or remove This linter rule provides the following configuration options: ```yaml -rules: +rules: bugs: constant-condition: # one of "error", "warning", "ignore" diff --git a/docs/rules/bugs/duplicate-rule.md b/docs/rules/bugs/duplicate-rule.md index 49ba3fb8..8793954e 100644 --- a/docs/rules/bugs/duplicate-rule.md +++ b/docs/rules/bugs/duplicate-rule.md @@ -8,7 +8,7 @@ ```rego package policy -import future.keywords.if +import rego.v1 allow if user.is_admin @@ -22,7 +22,7 @@ allow if user.is_admin ```rego package policy -import future.keywords.if +import rego.v1 allow if user.is_admin diff --git a/docs/rules/bugs/if-empty-object.md b/docs/rules/bugs/if-empty-object.md index e6d0fb84..136ad116 100644 --- a/docs/rules/bugs/if-empty-object.md +++ b/docs/rules/bugs/if-empty-object.md @@ -8,7 +8,7 @@ ```rego package policy -import future.keywords.if +import rego.v1 allow if {} ``` diff --git a/docs/rules/bugs/inconsistent-args.md b/docs/rules/bugs/inconsistent-args.md index 07bea7c8..0608c783 100644 --- a/docs/rules/bugs/inconsistent-args.md +++ b/docs/rules/bugs/inconsistent-args.md @@ -8,8 +8,7 @@ ```rego package policy -import future.keywords.if -import future.keywords.in +import rego.v1 find_vars(rule, node) if node in rule @@ -24,8 +23,7 @@ find_vars(node, rule) if { ```rego package policy -import future.keywords.if -import future.keywords.in +import rego.v1 find_vars(rule, node) if node in rule @@ -48,8 +46,7 @@ Using wildcards (`_`) in place of unused arguments is always allowed, and in fac ```rego package policy -import future.keywords.if -import future.keywords.in +import rego.v1 find_vars(rule, node) if node in rule @@ -68,7 +65,7 @@ also allowed. This linter rule provides the following configuration options: ```yaml -rules: +rules: bugs: inconsistent-args: # one of "error", "warning", "ignore" diff --git a/docs/rules/bugs/not-equals-in-loop.md b/docs/rules/bugs/not-equals-in-loop.md index 7260b587..59c1ca24 100644 --- a/docs/rules/bugs/not-equals-in-loop.md +++ b/docs/rules/bugs/not-equals-in-loop.md @@ -8,7 +8,7 @@ ```rego package policy -import future.keywords.if +import rego.v1 deny if { "admin" != input.user.roles[_] @@ -19,8 +19,7 @@ deny if { ```rego package policy -import future.keywords.if -import future.keywords.in +import rego.v1 deny if { not "admin" in input.user.roles @@ -39,7 +38,7 @@ day. If it doesn't mean "not in", what does it mean? ```rego package policy -import future.keywords.if +import rego.v1 deny if { "admin" != input.user.roles[_] diff --git a/docs/rules/bugs/rule-named-if.md b/docs/rules/bugs/rule-named-if.md index 812e7c36..f459c2a2 100644 --- a/docs/rules/bugs/rule-named-if.md +++ b/docs/rules/bugs/rule-named-if.md @@ -17,7 +17,7 @@ allow := true if { ```rego package policy -import future.keywords.if +import rego.v1 allow := true if { authorized @@ -26,10 +26,10 @@ allow := true if { ## Rationale -Forgetting to import the `if` keyword (using `import future.keywords.if`) is a common mistake. While this often results -in a parse error, there are some situations where the parser can't tell if the `if` is intended to be used as the -imported keyword, or a new rule named `if`. This is almost always a mistake, and if it isn't — consider using a better -name for your rule! +Forgetting to import the `if` keyword (using `import future.keywords.if`, or from OPA v0.59.0+ `import rego.v1`) is a +common mistake. While this often results in a parse error, there are some situations where the parser can't tell if the +`if` is intended to be used as the imported keyword, or a new rule named `if`. This is almost always a mistake, and if +it isn't — consider using a better name for your rule! ## Configuration Options diff --git a/docs/rules/bugs/unused-return-value.md b/docs/rules/bugs/unused-return-value.md index 39fa6be8..8d639de7 100644 --- a/docs/rules/bugs/unused-return-value.md +++ b/docs/rules/bugs/unused-return-value.md @@ -8,7 +8,7 @@ ```rego package policy -import future.keywords.if +import rego.v1 allow if { # return value unused @@ -40,7 +40,7 @@ is almost certainly a mistake. This linter rule provides the following configuration options: ```yaml -rules: +rules: bugs: unused-return-value: # one of "error", "warning", "ignore" diff --git a/docs/rules/community/circular-import.md b/docs/rules/community/circular-import.md index c185e111..13e12f9a 100644 --- a/docs/rules/community/circular-import.md +++ b/docs/rules/community/circular-import.md @@ -23,8 +23,7 @@ graph LR # authz.rego package authz -import future.keywords.if -import future.keywords.in +import rego.v1 import data.shared @@ -71,8 +70,7 @@ graph LR # authz.rego package authz -import future.keywords.if -import future.keywords.in +import rego.v1 import data.shared diff --git a/docs/rules/community/double-negative.md b/docs/rules/community/double-negative.md index fccaebd4..2303632a 100644 --- a/docs/rules/community/double-negative.md +++ b/docs/rules/community/double-negative.md @@ -15,7 +15,7 @@ ```rego package negative -import future.keywords.if +import rego.v1 fine if not not_fine @@ -30,7 +30,7 @@ without_friends if count(input.friends) == 0 ```rego package negative -import future.keywords.if +import rego.v1 fine if input.fine == true diff --git a/docs/rules/custom/one-liner-rule.md b/docs/rules/custom/one-liner-rule.md index b18662fc..e3d68fc2 100644 --- a/docs/rules/custom/one-liner-rule.md +++ b/docs/rules/custom/one-liner-rule.md @@ -8,7 +8,7 @@ ```rego package policy -import future.keywords.if +import rego.v1 allow if { is_admin @@ -23,7 +23,7 @@ is_admin if { ```rego package policy -import future.keywords.if +import rego.v1 allow if is_admin diff --git a/docs/rules/custom/prefer-value-in-head.md b/docs/rules/custom/prefer-value-in-head.md index 1b0e0b9e..a9ff4d53 100644 --- a/docs/rules/custom/prefer-value-in-head.md +++ b/docs/rules/custom/prefer-value-in-head.md @@ -8,8 +8,7 @@ ```rego package policy -import future.keywords.contains -import future.keywords.if +import rego.v1 pin_as_number := val if { is_number(input.pin_code) @@ -26,8 +25,7 @@ deny contains message if { ```rego package policy -import future.keywords.contains -import future.keywords.if +import rego.v1 pin_as_number := to_number(input.pin_code) if is_number(input.pin_code) diff --git a/docs/rules/idiomatic/custom-has-key-construct.md b/docs/rules/idiomatic/custom-has-key-construct.md index fe76373f..e5db0f6e 100644 --- a/docs/rules/idiomatic/custom-has-key-construct.md +++ b/docs/rules/idiomatic/custom-has-key-construct.md @@ -8,7 +8,7 @@ ```rego package policy -import future.keywords.if +import rego.v1 mfa if has_key(input.claims, "mfa") @@ -21,8 +21,7 @@ has_key(map, key) if { ```rego package policy -import future.keywords.if -import future.keywords.in +import rego.v1 mfa if "mfa" in object.keys(input.claims) ``` diff --git a/docs/rules/idiomatic/custom-in-construct.md b/docs/rules/idiomatic/custom-in-construct.md index 88fa338a..a4c0d4dd 100644 --- a/docs/rules/idiomatic/custom-in-construct.md +++ b/docs/rules/idiomatic/custom-in-construct.md @@ -8,7 +8,7 @@ ```rego package policy -import future.keywords.if +import rego.v1 allow if has_value(input.user.roles, "admin") @@ -23,8 +23,7 @@ has_value(arr, item) if { ```rego package policy -import future.keywords.if -import future.keywords.in +import rego.v1 allow if "admin" in input.user.roles ``` @@ -42,7 +41,7 @@ recommended. This linter rule provides the following configuration options: ```yaml -rules: +rules: idiomatic: custom-in-construct: # one of "error", "warning", "ignore" diff --git a/docs/rules/idiomatic/equals-pattern-matching.md b/docs/rules/idiomatic/equals-pattern-matching.md index d413e5a1..b6f28088 100644 --- a/docs/rules/idiomatic/equals-pattern-matching.md +++ b/docs/rules/idiomatic/equals-pattern-matching.md @@ -8,7 +8,7 @@ ```rego package policy -import future.keywords.if +import rego.v1 readable_number(x) := "one" if x == 1 readable_number(x) := "two" if x == 2 @@ -31,7 +31,7 @@ moving the equality check to match on the function call itself. This means that ```rego package policy -import future.keywords.if +import rego.v1 normalize_role(role) := "admin" if { role == "administrator" @@ -48,7 +48,7 @@ the equality "pattern": ```rego package policy -import future.keywords.if +import rego.v1 normalize_role("administrator") := "admin" @@ -60,7 +60,7 @@ Rules that evaluate to `true` may even have the assignment removed altogether, i ```rego package policy -import future.keywords.if +import rego.v1 is_admin(role) if role == "admin" @@ -91,7 +91,7 @@ will be improved in future releases. This linter rule provides the following configuration options: ```yaml -rules: +rules: idiomatic: equals-pattern-matching: # one of "error", "warning", "ignore" diff --git a/docs/rules/idiomatic/no-defined-entrypoint.md b/docs/rules/idiomatic/no-defined-entrypoint.md index e1c90156..61d96cfa 100644 --- a/docs/rules/idiomatic/no-defined-entrypoint.md +++ b/docs/rules/idiomatic/no-defined-entrypoint.md @@ -10,8 +10,7 @@ ```rego package policy -import future.keywords.if -import future.keywords.in +import rego.v1 default allow := false @@ -35,8 +34,7 @@ public_resource_read if { ```rego package policy -import future.keywords.if -import future.keywords.in +import rego.v1 default allow := false diff --git a/docs/rules/idiomatic/non-raw-regex-pattern.md b/docs/rules/idiomatic/non-raw-regex-pattern.md index fe2601e1..b2ebd5a4 100644 --- a/docs/rules/idiomatic/non-raw-regex-pattern.md +++ b/docs/rules/idiomatic/non-raw-regex-pattern.md @@ -33,7 +33,7 @@ try to "resolve" patterns assigned to variables. The following example would as ```rego package policy -import future.keywords.if +import rego.v1 # Pattern assigned to variable pattern := "[\\d]+" diff --git a/docs/rules/idiomatic/prefer-set-or-object-rule.md b/docs/rules/idiomatic/prefer-set-or-object-rule.md index abc46063..dbf15889 100644 --- a/docs/rules/idiomatic/prefer-set-or-object-rule.md +++ b/docs/rules/idiomatic/prefer-set-or-object-rule.md @@ -8,9 +8,7 @@ ````rego package policy -import future.keywords.contains -import future.keywords.if -import future.keywords.in +import rego.v1 # top level set comprehension developers := {developer | @@ -30,9 +28,7 @@ user_roles_mapping := {user: roles | ````rego package policy -import future.keywords.contains -import future.keywords.if -import future.keywords.in +import rego.v1 # set generating rule developers contains developer if { @@ -73,9 +69,7 @@ or unconditionally. ```rego package policy -import future.keywords.contains -import future.keywords.if -import future.keywords.in +import rego.v1 # Getting developers from input developers contains developer if { @@ -105,9 +99,7 @@ than one rule contributing to a single key-value pair. ```rego package policy -import future.keywords.contains -import future.keywords.if -import future.keywords.in +import rego.v1 novels[title] := content if { some document in input.documents @@ -138,7 +130,7 @@ This rule will also ignore simple comprehensions used solely for the purpose of ```rego package policy -import future.keywords.in +import rego.v1 # Convert set to array. This is fine. my_set := {item | some item in arr} diff --git a/docs/rules/idiomatic/use-in-operator.md b/docs/rules/idiomatic/use-in-operator.md index da5c3c70..5b97ffbc 100644 --- a/docs/rules/idiomatic/use-in-operator.md +++ b/docs/rules/idiomatic/use-in-operator.md @@ -8,7 +8,7 @@ ```rego package policy -import future.keywords.if +import rego.v1 # "Old" way of checking for membership - iteration + comparison allow if { @@ -20,8 +20,7 @@ allow if { ```rego package policy -import future.keywords.if -import future.keywords.in +import rego.v1 allow if { "admin" in input.user.roles @@ -38,7 +37,7 @@ checking if something is **not** part of a collection. This linter rule provides the following configuration options: ```yaml -rules: +rules: idiomatic: use-in-operator: # one of "error", "warning", "ignore" diff --git a/docs/rules/imports/avoid-importing-input.md b/docs/rules/imports/avoid-importing-input.md index c26e3938..83711ffc 100644 --- a/docs/rules/imports/avoid-importing-input.md +++ b/docs/rules/imports/avoid-importing-input.md @@ -8,8 +8,7 @@ ```rego package policy -import future.keywords.in -import future.keywords.if +import rego.v1 # This is always redundant import input @@ -28,8 +27,7 @@ allow if { ```rego package policy -import future.keywords.in -import future.keywords.if +import rego.v1 allow if "admin" in input.user.roles @@ -51,7 +49,7 @@ like a Terraform plan. Aliasing of specific input attributes should however be a ```rego package policy -import future.keywords.if +import rego.v1 # This is acceptable import input as tfplan diff --git a/docs/rules/imports/import-shadows-builtin.md b/docs/rules/imports/import-shadows-builtin.md index e9766224..cc40b10d 100644 --- a/docs/rules/imports/import-shadows-builtin.md +++ b/docs/rules/imports/import-shadows-builtin.md @@ -39,7 +39,7 @@ longer form. Provided a simple policy like this: ```rego package policy -import future.keywords.if +import rego.v1 import data.http diff --git a/docs/rules/imports/prefer-package-imports.md b/docs/rules/imports/prefer-package-imports.md index 6b3660d6..485a5764 100644 --- a/docs/rules/imports/prefer-package-imports.md +++ b/docs/rules/imports/prefer-package-imports.md @@ -10,14 +10,14 @@ ```rego package policy -import future.keywords.in +import rego.v1 # Rule imported directly import data.users.first_names has_waldo { # Not obvious where "first_names" comes from - "Waldo" in first_names + "Waldo" in first_names } ``` @@ -25,14 +25,14 @@ has_waldo { ```rego package policy -import future.keywords.in +import rego.v1 # Package imported rather than rule import data.users has_waldo { # Obvious where "first_names" comes from - "Waldo" in users.first_names + "Waldo" in users.first_names } ``` @@ -53,7 +53,7 @@ of external data, or use the various ignore options to ignore entire files. This linter rule provides the following configuration options: ```yaml -rules: +rules: imports: prefer-package-imports: # one of "error", "warning", "ignore" diff --git a/docs/rules/imports/use-rego-v1.md b/docs/rules/imports/use-rego-v1.md new file mode 100644 index 00000000..9ac1f317 --- /dev/null +++ b/docs/rules/imports/use-rego-v1.md @@ -0,0 +1,100 @@ +# use-rego-v1 + +**Summary**: Use `import rego.v1` + +**Category**: Imports + +**Avoid** +```rego +package policy + +# before OPA v0.59.0, this is alright +import future.keywords.contains +import future.keywords.if + +report contains item if { + # ... +} +``` + +**Prefer** +```rego +package policy + +# with OPA v0.59.0 and later, use this instead +import rego.v1 + +report contains item if { + # ... +} +``` + +## Rationale + +OPA [v0.59.0](https://github.com/open-policy-agent/opa/releases/tag/v0.59.0) introduced a new `rego.v1` import, which +allows policy authors to prepare for language changes coming in a future OPA 1.0 release. Some notable changes include: + +- All "future" keywords that currently must be imported through `import future.keywords` will be part of Rego by + default, without the need to first import them +- The `if` keyword will be required before the body of a rule +- The `contains` keyword will be required when declaring a multi-value rule (partial set rule) +- Deprecated built-in functions will be removed + +Using `import rego.v1` ensures that these requirements are met in any package including the import, and tools like +`opa check` and `opa fmt` have been updated to help users in this transition. + +See the [OPA v0.59.0 release notes](https://github.com/open-policy-agent/opa/releases/tag/v0.59.0) for more details. + +### Capabilities + +If you aren't yet using OPA v0.59.0 or later, it is recommended that you use the +[capabilities](https://docs.styra.com/regal#capabilities) setting in your Regal configuration file to tell Regal what +version of OPA to target. This way you won't need to disable rules that require capabilities that aren't in the version +of OPA you're targeting, and allows for a smoother transition to newer versions of OPA when you're ready for that. +Another benefit of using capabilities is that Regal will include notices in the report when there are rules that have +been disabled due to missing capabilities, kindly reminding you of them, but without having the command fail. + +In the example below we're using the capabilities setting to target OPA v0.55.0 (where `import rego.v1` is not +available): + +**.regal/config.yaml** +```yaml +capabilities: + from: + engine: opa + version: v0.55.0 +``` + +Linting with the above configuration will exclude the `use-rego-v1` rule, but add a notice to the report reminding you +that it was disabled due to missing capabilities: + +```shell +$ regal lint bundle +131 files linted. No violations found. 1 rule skipped: +- use-rego-v1: Missing capability for `import rego.v1` +``` + +## Configuration Options + +This linter rule provides the following configuration options: + +```yaml +rules: + imports: + use-rego-v1: + # one of "error", "warning", "ignore" + level: error + +# rather than disabling this rule, use the capabilities setting +# to tell Regal which version of OPA to target: +capabilities: + from: + engine: opa + version: v0.58.0 +``` + +## Community + +If you think you've found a problem with this rule or its documentation, would like to suggest improvements, new rules, +or just talk about Regal in general, please join us in the `#regal` channel in the Styra Community +[Slack](https://communityinviter.com/apps/styracommunity/signup)! diff --git a/docs/rules/style/default-over-else.md b/docs/rules/style/default-over-else.md index ce4de067..7fb576f6 100644 --- a/docs/rules/style/default-over-else.md +++ b/docs/rules/style/default-over-else.md @@ -8,7 +8,7 @@ ```rego package policy -import future.keywords.if +import rego.v1 permisisions := ["read", "write"] if { input.user == "admin" @@ -19,7 +19,7 @@ permisisions := ["read", "write"] if { ```rego package policy -import future.keywords.if +import rego.v1 default permisisions := ["read"] diff --git a/docs/rules/style/default-over-not.md b/docs/rules/style/default-over-not.md index 04e6507d..8d899395 100644 --- a/docs/rules/style/default-over-not.md +++ b/docs/rules/style/default-over-not.md @@ -8,7 +8,7 @@ ```rego package policy -import future.keywords.if +import rego.v1 username := input.user.name diff --git a/docs/rules/style/detached-metadata.md b/docs/rules/style/detached-metadata.md index 61c7d557..b1ba164f 100644 --- a/docs/rules/style/detached-metadata.md +++ b/docs/rules/style/detached-metadata.md @@ -8,8 +8,7 @@ ```rego package authz -import future.keywords.if -import future.keywords.in +import rego.v1 # METADATA # description: allow any requests by admin users @@ -23,8 +22,7 @@ allow if { ```rego package authz -import future.keywords.if -import future.keywords.in +import rego.v1 # METADATA # description: allow any requests by admin users diff --git a/docs/rules/style/function-arg-return.md b/docs/rules/style/function-arg-return.md index 48621056..ccc9d838 100644 --- a/docs/rules/style/function-arg-return.md +++ b/docs/rules/style/function-arg-return.md @@ -8,7 +8,7 @@ ```rego package policy -import future.keywords.if +import rego.v1 has_email(user) if { indexof(user.email, "@", i) @@ -21,7 +21,7 @@ has_email(user) if { ```rego package policy -import future.keywords.if +import rego.v1 has_email(user) if { i := indexof(user.email, "@") diff --git a/docs/rules/style/no-whitespace-comment.md b/docs/rules/style/no-whitespace-comment.md index b261e54b..ef4ff9d6 100644 --- a/docs/rules/style/no-whitespace-comment.md +++ b/docs/rules/style/no-whitespace-comment.md @@ -9,8 +9,7 @@ ```rego package policy -import future.keywords.if -import future.keywords.in +import rego.v1 #Deny by default default allow := false @@ -24,8 +23,7 @@ allow if "admin" in input.user.roles ```rego package policy -import future.keywords.if -import future.keywords.in +import rego.v1 # Deny by default default allow := false @@ -43,14 +41,14 @@ Comments should be preceded by a single space, as this makes them easier to read This linter rule provides the following configuration options: ```yaml -rules: +rules: style: no-whitespace-comment: # one of "error", "warning", "ignore" level: error # optional pattern to except from this rule # this example would allow comments like "#--" - # use or (`|`) to separate multiple patterns + # use or (`|`) to separate multiple patterns except-pattern: '^--' ``` diff --git a/docs/rules/style/prefer-snake-case.md b/docs/rules/style/prefer-snake-case.md index bb12a771..d5a209ce 100644 --- a/docs/rules/style/prefer-snake-case.md +++ b/docs/rules/style/prefer-snake-case.md @@ -8,8 +8,7 @@ ```rego package policy -import future.keywords.if -import future.keywords.in +import rego.v1 # camelCase rule name userIsAdmin if "admin" in input.user.roles @@ -19,8 +18,7 @@ userIsAdmin if "admin" in input.user.roles ```rego package policy -import future.keywords.if -import future.keywords.in +import rego.v1 # snake_case rule name user_is_admin if "admin" in input.user.roles diff --git a/docs/rules/style/prefer-some-in-iteration.md b/docs/rules/style/prefer-some-in-iteration.md index 670b0d67..6d9287c0 100644 --- a/docs/rules/style/prefer-some-in-iteration.md +++ b/docs/rules/style/prefer-some-in-iteration.md @@ -20,7 +20,7 @@ engineers[employee] { ```rego package policy -import future.keywords.in +import rego.v1 engineering_roles = {"engineer", "dba", "developer"} @@ -54,8 +54,7 @@ Deeply nested iteration is often easier to read using the more compact form. ```rego package policy -import future.keywords.if -import future.keywords.in +import rego.v1 # These rules are equivalent, but the more compact form is arguably easier to read diff --git a/docs/rules/style/unconditional-assignment.md b/docs/rules/style/unconditional-assignment.md index ea9ee818..a887cb3a 100644 --- a/docs/rules/style/unconditional-assignment.md +++ b/docs/rules/style/unconditional-assignment.md @@ -8,8 +8,7 @@ ```rego package policy -import future.keywords.contains -import future.keywords.if +import rego.v1 full_name := name if { name := concat(", ", [input.first_name, input.last_name]) @@ -28,8 +27,7 @@ names contains name if { ```rego package policy -import future.keywords.contains -import future.keywords.if +import rego.v1 full_name := concat(", ", [input.first_name, input.last_name]) @@ -48,7 +46,7 @@ body adds unnecessary noise. This linter rule provides the following configuration options: ```yaml -rules: +rules: style: unconditional-assignment: # one of "error", "warning", "ignore" diff --git a/docs/rules/style/unnecessary-some.md b/docs/rules/style/unnecessary-some.md index 21210225..0b11bced 100644 --- a/docs/rules/style/unnecessary-some.md +++ b/docs/rules/style/unnecessary-some.md @@ -8,8 +8,7 @@ ```rego package policy -import future.keywords.if -import future.keywords.in +import rego.v1 is_developer if some "developer" in input.user.roles ``` @@ -19,8 +18,7 @@ is_developer if some "developer" in input.user.roles ```rego package policy -import future.keywords.if -import future.keywords.in +import rego.v1 is_developer if "developer" in input.user.roles ``` @@ -38,9 +36,7 @@ should match for the loop assignment to succeed. This is not commonly needed, bu ```rego package policy -import future.keywords.contains -import future.keywords.if -import future.keywords.in +import rego.v1 developers contains name if { # name will only be bound when the value is "developer" @@ -53,7 +49,7 @@ developers contains name if { This linter rule provides the following configuration options: ```yaml -rules: +rules: style: unnecessary-some: # one of "error", "warning", "ignore" diff --git a/docs/rules/style/use-assignment-operator.md b/docs/rules/style/use-assignment-operator.md index c740ab9d..b0433d99 100644 --- a/docs/rules/style/use-assignment-operator.md +++ b/docs/rules/style/use-assignment-operator.md @@ -8,7 +8,7 @@ ```rego package policy -import future.keywords.if +import rego.v1 default allow = false @@ -24,7 +24,7 @@ allow if { ```rego package policy -import future.keywords.if +import rego.v1 default allow := false diff --git a/docs/rules/style/yoda-condition.md b/docs/rules/style/yoda-condition.md index f4903bac..d9976d4e 100644 --- a/docs/rules/style/yoda-condition.md +++ b/docs/rules/style/yoda-condition.md @@ -8,7 +8,7 @@ ```rego package policy -import future.keywords.if +import rego.v1 allow if { "GET" == input.request.method @@ -20,7 +20,7 @@ allow if { ```rego package policy -import future.keywords.if +import rego.v1 allow if { input.request.method == "GET" @@ -39,7 +39,7 @@ authors in the galaxy. This linter rule provides the following configuration options: ```yaml -rules: +rules: style: yoda-condition: # one of "error", "warning", "ignore" diff --git a/docs/rules/testing/print-or-trace-call.md b/docs/rules/testing/print-or-trace-call.md index c125b4cc..635189c4 100644 --- a/docs/rules/testing/print-or-trace-call.md +++ b/docs/rules/testing/print-or-trace-call.md @@ -8,9 +8,7 @@ ```rego package policy -import future.keywords.contains -import future.keywords.if -import future.keywords.in +import rego.v1 reasons contains sprintf("%q is a dog!", [user.name]) if { some user in input.users diff --git a/docs/rules/testing/test-outside-test-package.md b/docs/rules/testing/test-outside-test-package.md index 59d3621c..adc1960f 100644 --- a/docs/rules/testing/test-outside-test-package.md +++ b/docs/rules/testing/test-outside-test-package.md @@ -8,7 +8,7 @@ ```rego package policy -import future.keywords +import rego.v1 allow if { "admin" in input.user.roles @@ -25,7 +25,7 @@ test_allow_if_admin { # Tests in separate package with _test suffix package policy_test -import future.keywords +import rego.v1 import data.policy diff --git a/docs/rules/testing/todo-test.md b/docs/rules/testing/todo-test.md index 4fffa2df..253155d9 100644 --- a/docs/rules/testing/todo-test.md +++ b/docs/rules/testing/todo-test.md @@ -8,7 +8,7 @@ ```rego package policy_test -import future.keywords +import rego.v1 import data.policy diff --git a/e2e/cli_test.go b/e2e/cli_test.go index 17246da4..90141f1b 100644 --- a/e2e/cli_test.go +++ b/e2e/cli_test.go @@ -594,8 +594,9 @@ func TestLintWithCustomCapabilitiesAndUnmetRequirement(t *testing.T) { // This is only an informative warning — command should not fail expectExitCode(t, err, 0, &stdout, &stderr) - expectOut := "1 file linted. No violations found. 1 rule skipped:\n" + - "- custom-has-key-construct: Missing capability for built-in function `object.keys`\n\n" + expectOut := "1 file linted. No violations found. 2 rules skipped:\n" + + "- custom-has-key-construct: Missing capability for built-in function `object.keys`\n" + + "- use-rego-v1: Missing capability for `import rego.v1`\n\n" if stdout.String() != expectOut { t.Errorf("expected %q, got %q", expectOut, stdout.String()) @@ -619,8 +620,9 @@ func TestLintWithCustomCapabilitiesAndUnmetRequirementMultipleFiles(t *testing.T // This is only an informative warning — command should not fail expectExitCode(t, err, 0, &stdout, &stderr) - expectOut := "2 files linted. No violations found. 1 rule skipped:\n" + - "- custom-has-key-construct: Missing capability for built-in function `object.keys`\n\n" + expectOut := "2 files linted. No violations found. 2 rules skipped:\n" + + "- custom-has-key-construct: Missing capability for built-in function `object.keys`\n" + + "- use-rego-v1: Missing capability for `import rego.v1`\n\n" if stdout.String() != expectOut { t.Errorf("expected %q, got %q", expectOut, stdout.String()) diff --git a/e2e/testdata/aggregates/three_policies/policy_1.rego b/e2e/testdata/aggregates/three_policies/policy_1.rego index a4dbf2c2..6a2e6d99 100644 --- a/e2e/testdata/aggregates/three_policies/policy_1.rego +++ b/e2e/testdata/aggregates/three_policies/policy_1.rego @@ -1,5 +1,7 @@ package mypolicy1.public +import rego.v1 + # METADATA # entrypoint: true my_policy_1 := true diff --git a/e2e/testdata/aggregates/three_policies/policy_2.rego b/e2e/testdata/aggregates/three_policies/policy_2.rego index 12c1c985..71435b7c 100644 --- a/e2e/testdata/aggregates/three_policies/policy_2.rego +++ b/e2e/testdata/aggregates/three_policies/policy_2.rego @@ -1,3 +1,5 @@ package mypolicy2.public +import rego.v1 + export := [] diff --git a/e2e/testdata/aggregates/three_policies/policy_3.rego b/e2e/testdata/aggregates/three_policies/policy_3.rego index ad180bd9..f5a31fc6 100644 --- a/e2e/testdata/aggregates/three_policies/policy_3.rego +++ b/e2e/testdata/aggregates/three_policies/policy_3.rego @@ -1,3 +1,5 @@ package mypolicy3.public +import rego.v1 + my_policy_3 := true diff --git a/e2e/testdata/aggregates/two_policies/policy_1.rego b/e2e/testdata/aggregates/two_policies/policy_1.rego index a4dbf2c2..6a2e6d99 100644 --- a/e2e/testdata/aggregates/two_policies/policy_1.rego +++ b/e2e/testdata/aggregates/two_policies/policy_1.rego @@ -1,5 +1,7 @@ package mypolicy1.public +import rego.v1 + # METADATA # entrypoint: true my_policy_1 := true diff --git a/e2e/testdata/aggregates/two_policies/policy_2.rego b/e2e/testdata/aggregates/two_policies/policy_2.rego index 12c1c985..71435b7c 100644 --- a/e2e/testdata/aggregates/two_policies/policy_2.rego +++ b/e2e/testdata/aggregates/two_policies/policy_2.rego @@ -1,3 +1,5 @@ package mypolicy2.public +import rego.v1 + export := [] diff --git a/internal/lsp/server_test.go b/internal/lsp/server_test.go index 3930df8b..7e9fe406 100644 --- a/internal/lsp/server_test.go +++ b/internal/lsp/server_test.go @@ -56,6 +56,8 @@ func TestLanguageServerSingleFileWithConfig(t *testing.T) { } mainRegoContents := `package main + +import rego.v1 allow = true ` @@ -180,7 +182,7 @@ allow = true } if len(requestData.Items) != 2 { - t.Fatalf("expected 2 diagnostics, got %d", len(requestData.Items)) + t.Fatalf("expected 2 diagnostics, got %d, %v", len(requestData.Items), requestData) } expectedItems := map[string]bool{ @@ -212,6 +214,7 @@ allow = true ContentChanges: []TextDocumentContentChangeEvent{ { Text: `package main +import rego.v1 allow := true `, }, @@ -332,6 +335,8 @@ allow if input.user in users `, "admins.rego": `package admins +import rego.v1 + users = {"alice", "bob"} `, } @@ -444,7 +449,7 @@ users = {"alice", "bob"} } if len(requestData.Items) != 1 { - t.Fatalf("expected 1 diagnostics, got %d", len(requestData.Items)) + t.Fatalf("expected 1 diagnostics, got %d, %v", len(requestData.Items), requestData) } if requestData.Items[0].Code.Value != "use-assignment-operator" { diff --git a/pkg/linter/linter_test.go b/pkg/linter/linter_test.go index 5faadb17..fc51a1cf 100644 --- a/pkg/linter/linter_test.go +++ b/pkg/linter/linter_test.go @@ -22,7 +22,7 @@ func TestLintWithDefaultBundle(t *testing.T) { input := test.InputPolicy("p.rego", `package p -import future.keywords.if +import rego.v1 # TODO: fix this camelCase if { @@ -78,6 +78,8 @@ func TestLintWithUserConfig(t *testing.T) { input := test.InputPolicy("p.rego", `package p +import rego.v1 + boo := input.hoo[_] or := 1 @@ -107,6 +109,8 @@ func TestLintWithUserConfigTable(t *testing.T) { policy := `package p +import rego.v1 + boo := input.hoo[_] opa_fmt := "fail" @@ -213,6 +217,8 @@ func TestLintWithGoRule(t *testing.T) { t.Parallel() input := test.InputPolicy("p.rego", `package p + import rego.v1 + x := true `) @@ -239,6 +245,8 @@ func TestLintWithUserConfigGoRuleIgnore(t *testing.T) { }} input := test.InputPolicy("p.rego", `package p + import rego.v1 + x := true `) @@ -256,7 +264,7 @@ func TestLintWithUserConfigGoRuleIgnore(t *testing.T) { func TestLintWithCustomRule(t *testing.T) { t.Parallel() - input := test.InputPolicy("p.rego", "package p\n") + input := test.InputPolicy("p.rego", "package p\n\nimport rego.v1\n") linter := NewLinter(). WithCustomRules([]string{filepath.Join("testdata", "custom.rego")}). @@ -279,7 +287,7 @@ var testLintWithCustomEmbeddedRulesFS embed.FS func TestLintWithCustomEmbeddedRules(t *testing.T) { t.Parallel() - input := test.InputPolicy("p.rego", "package p\n") + input := test.InputPolicy("p.rego", "package p\n\nimport rego.v1\n") linter := NewLinter(). WithCustomRulesFromFS(testLintWithCustomEmbeddedRulesFS, "testdata"). @@ -299,7 +307,7 @@ func TestLintWithCustomEmbeddedRules(t *testing.T) { func TestLintWithCustomRuleAndCustomConfig(t *testing.T) { t.Parallel() - input := test.InputPolicy("p.rego", "package p\n") + input := test.InputPolicy("p.rego", "package p\n\nimport rego.v1\n") userConfig := config.Config{Rules: map[string]config.Category{ "naming": {"acme-corp-package": config.Rule{Level: "ignore"}},