Skip to content

Commit f10cc1f

Browse files
authored
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 <[email protected]>
1 parent 5d08783 commit f10cc1f

File tree

6 files changed

+122
-23
lines changed

6 files changed

+122
-23
lines changed

ast/annotations.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ func (a *Annotations) Copy(node Node) *Annotations {
417417
return &cpy
418418
}
419419

420-
// toObject constructs an AST Object from a.
420+
// toObject constructs an AST Object from the annotation.
421421
func (a *Annotations) toObject() (*Object, *Error) {
422422
obj := NewObject()
423423

@@ -556,7 +556,11 @@ func attachAnnotationsNodes(mod *Module) Errors {
556556
if a.Scope == "" {
557557
switch a.node.(type) {
558558
case *Rule:
559-
a.Scope = annotationScopeRule
559+
if a.Entrypoint {
560+
a.Scope = annotationScopeDocument
561+
} else {
562+
a.Scope = annotationScopeRule
563+
}
560564
case *Package:
561565
a.Scope = annotationScopePackage
562566
case *Import:
@@ -596,8 +600,9 @@ func validateAnnotationScopeAttachment(a *Annotations) *Error {
596600
}
597601

598602
func validateAnnotationEntrypointAttachment(a *Annotations) *Error {
599-
if a.Entrypoint && !(a.Scope == annotationScopeRule || a.Scope == annotationScopePackage) {
600-
return NewError(ParseErr, a.Loc(), "annotation entrypoint applied to non-rule or package scope '%v'", a.Scope)
603+
if a.Entrypoint && !(a.Scope == annotationScopeDocument || a.Scope == annotationScopePackage) {
604+
return NewError(
605+
ParseErr, a.Loc(), "annotation entrypoint applied to non-document or package scope '%v'", a.Scope)
601606
}
602607
return nil
603608
}

ast/annotations_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,88 @@ import (
1010
"testing"
1111
)
1212

13+
func TestEntrypointAnnotationScopeRequirements(t *testing.T) {
14+
tests := []struct {
15+
note string
16+
module string
17+
expectError bool
18+
expectScope string
19+
}{
20+
{
21+
note: "package scope explicit",
22+
module: `# METADATA
23+
# entrypoint: true
24+
# scope: package
25+
package foo`,
26+
expectError: false,
27+
expectScope: "package",
28+
},
29+
{
30+
note: "package scope implied",
31+
module: `# METADATA
32+
# entrypoint: true
33+
package foo`,
34+
expectError: false,
35+
expectScope: "package",
36+
},
37+
{
38+
note: "subpackages scope explicit",
39+
module: `# METADATA
40+
# entrypoint: true
41+
# scope: subpackages
42+
package foo`,
43+
expectError: true,
44+
},
45+
{
46+
note: "document scope explicit",
47+
module: `package foo
48+
# METADATA
49+
# entrypoint: true
50+
# scope: document
51+
foo := true`,
52+
expectError: false,
53+
expectScope: "document",
54+
},
55+
{
56+
note: "document scope implied",
57+
module: `package foo
58+
# METADATA
59+
# entrypoint: true
60+
foo := true`,
61+
expectError: false,
62+
expectScope: "document",
63+
},
64+
{
65+
note: "rule scope explicit",
66+
module: `package foo
67+
# METADATA
68+
# entrypoint: true
69+
# scope: rule
70+
foo := true`,
71+
expectError: true,
72+
},
73+
}
74+
75+
for _, tc := range tests {
76+
t.Run(tc.note, func(t *testing.T) {
77+
module, err := ParseModuleWithOpts("test.rego", tc.module, ParserOptions{ProcessAnnotation: true})
78+
if err != nil {
79+
if !tc.expectError {
80+
t.Errorf("unexpected error: %v", err)
81+
}
82+
return
83+
}
84+
if tc.expectError {
85+
t.Fatalf("expected error")
86+
}
87+
if tc.expectScope != module.Annotations[0].Scope {
88+
t.Fatalf("expected scope %q, got %q", tc.expectScope, module.Annotations[0].Scope)
89+
}
90+
})
91+
}
92+
93+
}
94+
1395
// Test of example code in docs/content/annotations.md
1496
func ExampleAnnotationSet_Flatten() {
1597
modules := [][]string{

cmd/build_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,7 @@ p2 := 2
653653
"entrypoint":"test/p2",
654654
"module":"/policy.wasm",
655655
"annotations":[{
656-
"scope":"rule",
656+
"scope":"document",
657657
"title":"P2",
658658
"entrypoint":true
659659
}]
@@ -742,15 +742,15 @@ bar := "baz"
742742
"entrypoint":"test/foo/bar",
743743
"module":"/policy.wasm",
744744
"annotations":[{
745-
"scope":"rule",
745+
"scope":"document",
746746
"title":"BAR",
747747
"entrypoint":true
748748
}]
749749
},{
750750
"entrypoint":"test/p2",
751751
"module":"/policy.wasm",
752752
"annotations":[{
753-
"scope":"rule",
753+
"scope":"document",
754754
"title":"P2",
755755
"entrypoint":true
756756
}]
@@ -767,10 +767,10 @@ package test
767767
# METADATA
768768
# title: P doc
769769
# scope: document
770+
# entrypoint: true
770771
771772
# METADATA
772773
# title: P
773-
# entrypoint: true
774774
p := 1
775775
`,
776776
},
@@ -784,11 +784,11 @@ p := 1
784784
"module":"/policy.wasm",
785785
"annotations":[{
786786
"scope":"document",
787-
"title":"P doc"
787+
"title":"P doc",
788+
"entrypoint":true
788789
},{
789790
"scope":"rule",
790-
"title":"P",
791-
"entrypoint":true
791+
"title":"P"
792792
}]
793793
}]
794794
}

compile/compile.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -255,20 +255,20 @@ func (c *Compiler) WithRegoVersion(v ast.RegoVersion) *Compiler {
255255
return c
256256
}
257257

258-
func addEntrypointsFromAnnotations(c *Compiler, ar []*ast.AnnotationsRef) error {
259-
for _, ref := range ar {
258+
func addEntrypointsFromAnnotations(c *Compiler, arefs []*ast.AnnotationsRef) error {
259+
for _, aref := range arefs {
260260
var entrypoint ast.Ref
261-
scope := ref.Annotations.Scope
261+
scope := aref.Annotations.Scope
262262

263-
if ref.Annotations.Entrypoint {
263+
if aref.Annotations.Entrypoint {
264264
// Build up the entrypoint path from either package path or rule.
265265
switch scope {
266266
case "package":
267-
if p := ref.GetPackage(); p != nil {
267+
if p := aref.GetPackage(); p != nil {
268268
entrypoint = p.Path
269269
}
270-
case "rule":
271-
if r := ref.GetRule(); r != nil {
270+
case "document":
271+
if r := aref.GetRule(); r != nil {
272272
entrypoint = r.Ref().GroundPrefix()
273273
}
274274
default:

compile/compile_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2106,7 +2106,7 @@ q = true`,
21062106
Annotations: []*ast.Annotations{
21072107
{
21082108
Title: "My P rule",
2109-
Scope: "rule",
2109+
Scope: "document",
21102110
Entrypoint: true,
21112111
},
21122112
},
@@ -2366,7 +2366,7 @@ func TestCompilerRegoEntrypointAnnotations(t *testing.T) {
23662366
wantEntrypoints map[string]struct{}
23672367
}{
23682368
{
2369-
note: "rule annotation",
2369+
note: "implied document scope annotation",
23702370
entrypoints: []string{},
23712371
modules: map[string]string{
23722372
"test.rego": `

docs/content/policy-language.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2687,8 +2687,12 @@ Since the `document` scope annotation applies to all rules with the same name in
26872687
and the `package` and `subpackages` scope annotations apply to all packages with a matching path, metadata blocks with
26882688
these scopes are applied over all files with applicable package- and rule paths.
26892689
As there is no ordering across files in the same package, the `document`, `package`, and `subpackages` scope annotations
2690-
can only be specified **once** per path.
2691-
The `document` scope annotation can be applied to any rule in the set (i.e., ordering does not matter.)
2690+
can only be specified **once** per path. The `document` scope annotation can be applied to any rule in the set (i.e.,
2691+
ordering does not matter.)
2692+
2693+
An `entrypoint` annotation implies a `scope` of either `package` or `document`. When `entrypoint` is set to `true` on a
2694+
rule, the `scope` is automatically set to `document` if not explicitly provided. Setting the `scope` to `rule` will
2695+
result in an error, as an entrypoint always applies to the whole document.
26922696

26932697
#### Example
26942698

@@ -2708,6 +2712,13 @@ allow if {
27082712
allow if {
27092713
x == 2
27102714
}
2715+
2716+
# METADATA
2717+
# entrypoint: true
2718+
# description: |
2719+
# `scope` annotation automatically set to `document`
2720+
# as that is required for entrypoints
2721+
message := "welcome!" if allow
27112722
```
27122723

27132724
### Title
@@ -2890,7 +2901,8 @@ allow if {
28902901
### Entrypoint
28912902

28922903
The `entrypoint` annotation is a boolean used to mark rules and packages that should be used as entrypoints for a policy.
2893-
This value is false by default, and can only be used at `rule` or `package` scope.
2904+
This value is false by default, and can only be used at `document` or `package` scope. When used on a rule with no
2905+
explicit `scope` set, the presence of an `entrypoint` annotation will automatically set the scope to `document`.
28942906

28952907
The `build` and `eval` CLI commands will automatically pick up annotated entrypoints; you do not have to specify them with
28962908
[`--entrypoint`](../cli/#options-1).

0 commit comments

Comments
 (0)