Skip to content

Commit

Permalink
Add invalid-metadata-attribute rule
Browse files Browse the repository at this point in the history
This one checks that all attributes of a metadata block are
valid, i.e. not custom outside of the custom map.

Signed-off-by: Anders Eknert <anders@styra.com>
  • Loading branch information
anderseknert committed Jun 29, 2023
1 parent 17bc200 commit 2f68245
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 4 deletions.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ The following rules are currently available:
| Category | Title | Description |
|-----------|--------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------|
| bugs | [constant-condition](https://github.com/StyraInc/regal/blob/main/docs/rules/bugs/constant-condition.md) | Constant condition |
| bugs | [invalid-metadata-attribute](https://github.com/StyraInc/regal/blob/main/docs/rules/bugs/invalid-metadata-attribute.md) | Invalid attribute in metadata annotation |
| bugs | [not-equals-in-loop](https://github.com/StyraInc/regal/blob/main/docs/rules/bugs/not-equals-in-loop.md) | Use of != in loop |
| bugs | [rule-named-if](https://github.com/StyraInc/regal/blob/main/docs/rules/bugs/rule-named-if.md) | Rule named "if" |
| bugs | [rule-shadows-builtin](https://github.com/StyraInc/regal/blob/main/docs/rules/bugs/rule-shadows-builtin.md) | Rule name shadows built-in |
Expand All @@ -146,14 +147,15 @@ The following rules are currently available:
| imports | [import-shadows-import](https://github.com/StyraInc/regal/blob/main/docs/rules/imports/import-shadows-import.md) | Import shadows another import |
| imports | [redundant-alias](https://github.com/StyraInc/regal/blob/main/docs/rules/imports/redundant-alias.md) | Redundant alias |
| imports | [redundant-data-import](https://github.com/StyraInc/regal/blob/main/docs/rules/imports/redundant-data-import.md) | Redundant import of data |
| style | [external-reference](https://github.com/StyraInc/regal/blob/main/docs/rules/style/external-reference.md) | Reference to input, data or rule ref in function body |
| style | [use-assignment-operator](https://github.com/StyraInc/regal/blob/main/docs/rules/style/use-assignment-operator.md) | Prefer := over = for assignment |
| style | [avoid-get-and-list-prefix](https://github.com/StyraInc/regal/blob/main/docs/rules/style/avoid-get-and-list-prefix.md) | Avoid get_ and list_ prefix for rules and functions |
| style | [unconditional-assignment](https://github.com/StyraInc/regal/blob/main/docs/rules/style/unconditional-assignment.md) | Unconditional assignment in rule body |
| style | [function-arg-return](https://github.com/StyraInc/regal/blob/main/docs/rules/style/function-arg-return.md) | Function argument used for return value |
| style | [line-length](https://github.com/StyraInc/regal/blob/main/docs/rules/style/line-length.md) | Line too long |
| style | [no-whitespace-comment](https://github.com/StyraInc/regal/blob/main/docs/rules/style/no-whitespace-comment.md) | Comment should start with whitespace |
| style | [prefer-snake-case](https://github.com/StyraInc/regal/blob/main/docs/rules/style/prefer-snake-case.md) | Prefer snake_case for names |
| style | [todo-comment](https://github.com/StyraInc/regal/blob/main/docs/rules/style/todo-comment.md) | Avoid TODO comments |
| style | [unconditional-assignment](https://github.com/StyraInc/regal/blob/main/docs/rules/style/unconditional-assignment.md) | Unconditional assignment in rule body |
| style | [avoid-get-and-list-prefix](https://github.com/StyraInc/regal/blob/main/docs/rules/style/avoid-get-and-list-prefix.md) | Avoid get_ and list_ prefix for rules and functions |
| style | [external-reference](https://github.com/StyraInc/regal/blob/main/docs/rules/style/external-reference.md) | Reference to input, data or rule ref in function body |
| style | [use-assignment-operator](https://github.com/StyraInc/regal/blob/main/docs/rules/style/use-assignment-operator.md) | Prefer := over = for assignment |
| style | [use-in-operator](https://github.com/StyraInc/regal/blob/main/docs/rules/style/use-in-operator.md) | Use in to check for membership |
| style | [opa-fmt](https://github.com/StyraInc/regal/blob/main/docs/rules/style/opa-fmt.md) | File should be formatted with `opa fmt` |
| testing | [identically-named-tests](https://github.com/StyraInc/regal/blob/main/docs/rules/testing/identically-named-tests.md) | Multiple tests with same name |
Expand Down
12 changes: 12 additions & 0 deletions bundle/regal/ast.rego
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,18 @@ comments_decoded := [decoded |

comments["blocks"] := comment_blocks(comments_decoded)

comments["metadata_attributes"] := {
"scope",
"title",
"description",
"related_resources",
"authors",
"organizations",
"schemas",
"entrypoint",
"custom",
}

comment_blocks(comments) := [partition |
rows := [row |
some comment in comments
Expand Down
2 changes: 2 additions & 0 deletions bundle/regal/config/provided/data.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ rules:
bugs:
constant-condition:
level: error
invalid-metadata-attribute:
level: error
not-equals-in-loop:
level: error
rule-named-if:
Expand Down
35 changes: 35 additions & 0 deletions bundle/regal/rules/bugs/invalid_metadata_attribute.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# METADATA
# description: Invalid attribute in metadata annotation
package regal.rules.bugs["invalid-metadata-attribute"]

import future.keywords.contains
import future.keywords.if
import future.keywords.in

import data.regal.ast
import data.regal.result

report contains violation if {
some block in ast.comments.blocks

startswith(trim_space(block[0].Text), "METADATA")

text := _block_to_string(block)
attributes := object.keys(yaml.unmarshal(text))

some attribute in attributes
not attribute in ast.comments.metadata_attributes

violation := result.fail(rego.metadata.chain(), result.location(_find_line(block, attribute)))
}

_block_to_string(block) := concat("\n", [line |
some i, entry in block
i > 0
line := entry.Text
])

_find_line(block, attribute) := [line |
some line in block
startswith(trim_space(line.Text), sprintf("%s:", [attribute]))
][0]
37 changes: 37 additions & 0 deletions bundle/regal/rules/bugs/invalid_metadata_attribute_test.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package regal.rules.bugs["invalid-metadata-attribute_test"]

import future.keywords.if

import data.regal.ast
import data.regal.config
import data.regal.rules.bugs["invalid-metadata-attribute"] as rule

test_fail_invalid_attribute if {
r := rule.report with input as ast.policy(`
# METADATA
# title: allow
# is_true: yes
allow := true
`)
r == {{
"category": "bugs",
"description": "Invalid attribute in metadata annotation",
"level": "error",
"location": {"col": 1, "file": "policy.rego", "row": 6, "text": "# is_true: yes"},
"related_resources": [{
"description": "documentation",
"ref": config.docs.resolve_url("$baseUrl/$category/invalid-metadata-attribute", "bugs"),
}],
"title": "invalid-metadata-attribute",
}}
}

test_success_valid_metadata if {
r := rule.report with input as ast.policy(`
# METADATA
# title: valid
# description: also valid
allow := true
`)
r == set()
}
52 changes: 52 additions & 0 deletions docs/rules/bugs/invalid-metadata-attribute.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# invalid-metadata-attribute

**Summary**: Invalid attribute in metadata annotation

**Category**: Bugs

**Avoid**
```rego
# METADATA
# title: Main policy routing requests to other policies based on input
# category: Routing
package router
```

**Prefer**
```rego
# METADATA
# title: Main policy routing requests to other policies based on input
# custom:
# category: Routing
package router
```

## Rationale

Metadata comments should follow the schema expected by
[annotations](https://www.openpolicyagent.org/docs/latest/policy-language/#annotations). Custom attributes, like
`category` above, should be placed under the `custom` key, which is a map of arbitrary key-value pairs.

While arbitrary attributes is accepted, they will not be treated as metadata annotations but regular comments, and as
such won't be available to other tools that
[process annotations](https://www.openpolicyagent.org/docs/latest/policy-language/#accessing-annotations).
These tools include built-in functions like
[rego.metadata.rule](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-rego-regometadatarule) and
[rego.metadata.chain](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-rego-regometadatachain).

## Configuration Options

This linter rule provides the following configuration options:

```yaml
rules:
bugs:
invalid-metadata:
# one of "error", "warning", "ignore"
level: error
```
## Related Resources
- OPA Docs: [Annotations](https://www.openpolicyagent.org/docs/latest/policy-language/#annotations)
- OPA Docs: [Accessing Annotations](https://www.openpolicyagent.org/docs/latest/policy-language/#accessing-annotations)

0 comments on commit 2f68245

Please sign in to comment.