Change required scope of entrypoint from rule to document (#6963)

And automatically change implied `scope` from `rule` to `document` when
no `scope` is provided (on rule metadata).

Fixes #6798

Signed-off-by: Anders Eknert <anders@styra.com>
This commit is contained in:
Anders Eknert
2024-08-28 13:22:30 +02:00
committed by GitHub
parent 5d087830d2
commit f10cc1f4c8
6 changed files with 122 additions and 23 deletions
+9 -4
View File
@@ -417,7 +417,7 @@ func (a *Annotations) Copy(node Node) *Annotations {
return &cpy
}
// toObject constructs an AST Object from a.
// toObject constructs an AST Object from the annotation.
func (a *Annotations) toObject() (*Object, *Error) {
obj := NewObject()
@@ -556,7 +556,11 @@ func attachAnnotationsNodes(mod *Module) Errors {
if a.Scope == "" {
switch a.node.(type) {
case *Rule:
a.Scope = annotationScopeRule
if a.Entrypoint {
a.Scope = annotationScopeDocument
} else {
a.Scope = annotationScopeRule
}
case *Package:
a.Scope = annotationScopePackage
case *Import:
@@ -596,8 +600,9 @@ func validateAnnotationScopeAttachment(a *Annotations) *Error {
}
func validateAnnotationEntrypointAttachment(a *Annotations) *Error {
if a.Entrypoint && !(a.Scope == annotationScopeRule || a.Scope == annotationScopePackage) {
return NewError(ParseErr, a.Loc(), "annotation entrypoint applied to non-rule or package scope '%v'", a.Scope)
if a.Entrypoint && !(a.Scope == annotationScopeDocument || a.Scope == annotationScopePackage) {
return NewError(
ParseErr, a.Loc(), "annotation entrypoint applied to non-document or package scope '%v'", a.Scope)
}
return nil
}
+82
View File
@@ -10,6 +10,88 @@ import (
"testing"
)
func TestEntrypointAnnotationScopeRequirements(t *testing.T) {
tests := []struct {
note string
module string
expectError bool
expectScope string
}{
{
note: "package scope explicit",
module: `# METADATA
# entrypoint: true
# scope: package
package foo`,
expectError: false,
expectScope: "package",
},
{
note: "package scope implied",
module: `# METADATA
# entrypoint: true
package foo`,
expectError: false,
expectScope: "package",
},
{
note: "subpackages scope explicit",
module: `# METADATA
# entrypoint: true
# scope: subpackages
package foo`,
expectError: true,
},
{
note: "document scope explicit",
module: `package foo
# METADATA
# entrypoint: true
# scope: document
foo := true`,
expectError: false,
expectScope: "document",
},
{
note: "document scope implied",
module: `package foo
# METADATA
# entrypoint: true
foo := true`,
expectError: false,
expectScope: "document",
},
{
note: "rule scope explicit",
module: `package foo
# METADATA
# entrypoint: true
# scope: rule
foo := true`,
expectError: true,
},
}
for _, tc := range tests {
t.Run(tc.note, func(t *testing.T) {
module, err := ParseModuleWithOpts("test.rego", tc.module, ParserOptions{ProcessAnnotation: true})
if err != nil {
if !tc.expectError {
t.Errorf("unexpected error: %v", err)
}
return
}
if tc.expectError {
t.Fatalf("expected error")
}
if tc.expectScope != module.Annotations[0].Scope {
t.Fatalf("expected scope %q, got %q", tc.expectScope, module.Annotations[0].Scope)
}
})
}
}
// Test of example code in docs/content/annotations.md
func ExampleAnnotationSet_Flatten() {
modules := [][]string{
+7 -7
View File
@@ -653,7 +653,7 @@ p2 := 2
"entrypoint":"test/p2",
"module":"/policy.wasm",
"annotations":[{
"scope":"rule",
"scope":"document",
"title":"P2",
"entrypoint":true
}]
@@ -742,7 +742,7 @@ bar := "baz"
"entrypoint":"test/foo/bar",
"module":"/policy.wasm",
"annotations":[{
"scope":"rule",
"scope":"document",
"title":"BAR",
"entrypoint":true
}]
@@ -750,7 +750,7 @@ bar := "baz"
"entrypoint":"test/p2",
"module":"/policy.wasm",
"annotations":[{
"scope":"rule",
"scope":"document",
"title":"P2",
"entrypoint":true
}]
@@ -767,10 +767,10 @@ package test
# METADATA
# title: P doc
# scope: document
# entrypoint: true
# METADATA
# title: P
# entrypoint: true
p := 1
`,
},
@@ -784,11 +784,11 @@ p := 1
"module":"/policy.wasm",
"annotations":[{
"scope":"document",
"title":"P doc"
"title":"P doc",
"entrypoint":true
},{
"scope":"rule",
"title":"P",
"entrypoint":true
"title":"P"
}]
}]
}
+7 -7
View File
@@ -255,20 +255,20 @@ func (c *Compiler) WithRegoVersion(v ast.RegoVersion) *Compiler {
return c
}
func addEntrypointsFromAnnotations(c *Compiler, ar []*ast.AnnotationsRef) error {
for _, ref := range ar {
func addEntrypointsFromAnnotations(c *Compiler, arefs []*ast.AnnotationsRef) error {
for _, aref := range arefs {
var entrypoint ast.Ref
scope := ref.Annotations.Scope
scope := aref.Annotations.Scope
if ref.Annotations.Entrypoint {
if aref.Annotations.Entrypoint {
// Build up the entrypoint path from either package path or rule.
switch scope {
case "package":
if p := ref.GetPackage(); p != nil {
if p := aref.GetPackage(); p != nil {
entrypoint = p.Path
}
case "rule":
if r := ref.GetRule(); r != nil {
case "document":
if r := aref.GetRule(); r != nil {
entrypoint = r.Ref().GroundPrefix()
}
default:
+2 -2
View File
@@ -2106,7 +2106,7 @@ q = true`,
Annotations: []*ast.Annotations{
{
Title: "My P rule",
Scope: "rule",
Scope: "document",
Entrypoint: true,
},
},
@@ -2366,7 +2366,7 @@ func TestCompilerRegoEntrypointAnnotations(t *testing.T) {
wantEntrypoints map[string]struct{}
}{
{
note: "rule annotation",
note: "implied document scope annotation",
entrypoints: []string{},
modules: map[string]string{
"test.rego": `
+15 -3
View File
@@ -2687,8 +2687,12 @@ Since the `document` scope annotation applies to all rules with the same name in
and the `package` and `subpackages` scope annotations apply to all packages with a matching path, metadata blocks with
these scopes are applied over all files with applicable package- and rule paths.
As there is no ordering across files in the same package, the `document`, `package`, and `subpackages` scope annotations
can only be specified **once** per path.
The `document` scope annotation can be applied to any rule in the set (i.e., ordering does not matter.)
can only be specified **once** per path. The `document` scope annotation can be applied to any rule in the set (i.e.,
ordering does not matter.)
An `entrypoint` annotation implies a `scope` of either `package` or `document`. When `entrypoint` is set to `true` on a
rule, the `scope` is automatically set to `document` if not explicitly provided. Setting the `scope` to `rule` will
result in an error, as an entrypoint always applies to the whole document.
#### Example
@@ -2708,6 +2712,13 @@ allow if {
allow if {
x == 2
}
# METADATA
# entrypoint: true
# description: |
# `scope` annotation automatically set to `document`
# as that is required for entrypoints
message := "welcome!" if allow
```
### Title
@@ -2890,7 +2901,8 @@ allow if {
### Entrypoint
The `entrypoint` annotation is a boolean used to mark rules and packages that should be used as entrypoints for a policy.
This value is false by default, and can only be used at `rule` or `package` scope.
This value is false by default, and can only be used at `document` or `package` scope. When used on a rule with no
explicit `scope` set, the presence of an `entrypoint` annotation will automatically set the scope to `document`.
The `build` and `eval` CLI commands will automatically pick up annotated entrypoints; you do not have to specify them with
[`--entrypoint`](../cli/#options-1).