mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-12 19:32:48 -06:00
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:
+9
-4
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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:
|
||||
|
||||
@@ -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": `
|
||||
|
||||
@@ -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).
|
||||
|
||||
Reference in New Issue
Block a user