-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Arbitrary vars in rule refs #5913
Arbitrary vars in rule refs #5913
Conversation
✅ Deploy Preview for openpolicyagent ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
8ef1c96
to
9ae279d
Compare
ast/compile.go
Outdated
@@ -311,6 +335,8 @@ func NewCompiler() *Compiler { | |||
{"BuildComprehensionIndices", "compile_stage_rebuild_comprehension_indices", c.buildComprehensionIndices}, | |||
} | |||
|
|||
_, c.generalRuleRefsEnabled = os.LookupEnv("OPA_ENABLE_GENERAL_RULE_REFS") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huh interesting 💡 yeah, why not. We might throw in a _WIP_
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Started looking at it. Still much ground to cover on my side 🔍
This is the topdown part of supporting generic refs. Fixes: open-policy-agent#5993 Signed-off-by: Johan Fylling <johan.dev@fylling.se>
e2dbd38
to
525c93d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry it took me so long to get back to this. Some comments and questions inside
ast/parser.go
Outdated
@@ -140,6 +142,7 @@ func NewParser() *Parser { | |||
s: &state{}, | |||
po: ParserOptions{}, | |||
} | |||
_, p.po.generalRuleRefsEnabled = os.LookupEnv("OPA_ENABLE_GENERAL_RULE_REFS") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💭 treating the environment as a global variable is better avoided, but if we can keep this as a short-term stop-gap, it's probably OK.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is temporary, and will be removed once general refs are feature complete. Hopefully soon 🤞. I'm open to improve this if it's too much of an eyesore, though 😅 .
1: true | ||
c: | ||
2: true | ||
# FIXME: Below test case returns: rego_type_error: undefined ref: data.test.p.b |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When? 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has been solved in the type-checker update I have lined up: johanfylling/opa@jf/5685/multi-var-rule-refs...generic_refs/type-check
# | ||
# p.q[r].s := 1 { r := "foo" } | ||
# p.q[r].s := 2 { r := "bar" } | ||
# query: i := ["foo", "bar"][_]; data.test.p.q[i].s = x |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe quotes help?
# query: i := ["foo", "bar"][_]; data.test.p.q[i].s = x | |
# query: 'i := ["foo", "bar"][_]; data.test.p.q[i].s = x' |
Alternatively, you could query data.test.q
and add
q = x {
i := ["foo", "bar"][_]
p.q[i].s = x
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, looking at this again, I actually get a result with the expected parameters, but with some added "stuff":
{{"$0": 0, "__localq0__": ["foo", "bar"], "i": "foo", "x": 1}, {"$0": 1, "__localq0__": ["foo", "bar"], "i": "bar", "x": 2}}
If I change the query to data.test.p.q[i].s = x
, I get a good result:
{{"i": "bar", "x": 2}, {"i": "foo", "x": 1}}
Is this expected, or perhaps an unwanted side effect of my changes 🤔 ? Will try a similar thing on main
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
main
exhibit the same behavior. Running opa eval
does not output bindings for $0
and __localq0__
; so maybe this is a side effect of how the test works?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those should never make it out, I think; but opa eval
might have some extra code to filter them. Or maybe that was the rego package, https://github.com/open-policy-agent/opa/blob/main/rego/rego.go#L2166-L2168
topdown/eval.go
Outdated
|
||
func (e *eval) biunifyDynamicRef(pos int, a, b ast.Ref, b1, b2 *bindings, iter unifyIterator) error { | ||
if pos >= len(a) || pos >= len(b) { | ||
return iter() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So if they don't have the same length, but overlap, we'd accept that?
data.foo.bar = data.foo[x].z
we'd add x = "bar"
to the bindings and go on?
But I might be missing something here, I haven't fully understood the topdown change yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're simply binding any possibly known vars in a rule's ref before evaluating its body.
If data.foo.bar == data.foo.bar.z
then I'd expect your snipped should evaluate. (is that even possible, though? 😵💫)
This is an interesting case to test for, though; hadn't thought about it from this angle before 🤔.
Perhaps a bit hacky test, but this should trigger this scenario:
package unification
p := i {
foo.bar.x.y = foo[i].z
}
foo[a][b] := c {
a := "bar"
c := {
"x": {
"y": 42
},
"z": 42
}[b]
}
$ OPA_ENABLE_GENERAL_RULE_REFS=true opa eval -d . 'data.unification.p'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or am I completely misunderstanding you?
topdown/eval_test.go
Outdated
// `, | ||
// query: `i := ["foo", "bar"][_]; data.test.p.q[i].s = x`, | ||
// exp: `[{"x": 1}, {"x": 2}]`, | ||
//}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's up with these? Do they not work yet; or have they been subsumed by the tests above...? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I missed un-commenting those. Fixed 👍 .
e.e.virtualCache.Put(hint.key, result) | ||
return e.evalTerm(iter, e.pos+1, result, e.bindings) | ||
} | ||
|
||
for _, rule := range e.ir.Rules { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that generic ref head rules are the more general case, you might expect that the base case (below) is also covered by what's added above -- is that so? Or what am I missing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you're right. These two branches should be possible to merge into one; given that there is no performance benefit to keeping them separate.
I think it's around here PE is breaking down too; so I'll dig around here a bit to see what improvements are necessary + possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's even a bit broken as-is, given that this should be in an else
block 😅 . I think this rule unification just won't contribute any data in applicable cases, so the result ends up being correct; but we're doing unnecessary work 🤔 .
Scratch that; I'm not even reading my own code right 😄 .
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
…ert()` Signed-off-by: Johan Fylling <johan.dev@fylling.se>
Fixes: open-policy-agent#5994 Signed-off-by: Johan Fylling <johan.dev@fylling.se>
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
instead of `Or`:ing them Signed-off-by: Johan Fylling <johan.dev@fylling.se>
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
Nested object created by general ref didn't maintain ground:ness even though containing vars. Signed-off-by: Johan Fylling <johan.dev@fylling.se>
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
… with dynamic portion of a rule ref Signed-off-by: Johan Fylling <johan.dev@fylling.se>
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
213ac32
to
b9ea4d5
Compare
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
b9ea4d5
to
372def8
Compare
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
…l ref heads Signed-off-by: Johan Fylling <johan.dev@fylling.se>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checked the commits since last week, only had a few questions. It's great to see this progress 🚀
@@ -17,3 +17,26 @@ r[x] if x := 10 | |||
p.q.r[x] if x := 10 | |||
|
|||
p.q.r[2] = true | |||
|
|||
g[h].i[j].k = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks so wrong but h
and j
could be rules defined somewhere in the package, right? 😅 (Not that it matters for formatting)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, this isn't internally consistent; but it's valid Rego, and for this rule we're more interested to see what the formatter does with the redundant { true }
body.
Conflicts: ast/env.go
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
ast/check_test.go
Outdated
@@ -579,6 +687,7 @@ func TestCheckInferenceRules(t *testing.T) { | |||
t.Fatalf("Unexpected error %v:", err) | |||
} | |||
|
|||
fmt.Println(env.tree.String()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left from testing probably.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, yea 🤦♂️ 😄
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpicks only. Yay, can't wait to get this in. Thanks for the hard work; it's like a summer project 😎
types.NewStaticProperty("c", types.B)}, | ||
nil, | ||
expected: types.NewObject(nil, | ||
types.NewDynamicProperty(types.S, types.Any{types.B, types.N, types.S}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 took me a moment to understand this, but I had overlooked that it's p.q[r].b = 42
and not p.q[r] = 42
. So, the extra S keys would (could) be "a", "b", "c". ✔️
docs/content/policy-language.md
Outdated
Any term, except the very first, in a rule head's reference can be a variable. These variables can be assigned within the rule, just as for any other partial rule, to dynamically construct a nested collection of objects. | ||
|
||
{{< danger >}} | ||
General refs in rule heads is an experimental feature, and can be enabled by setting the `OPA_ENABLE_GENERAL_RULE_REFS` environment variable. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was wondering about the name for a moment, but I think it's OK. We've got another example here, FWIW.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I'll change this to EXPERIMENTAL_GENERAL_RULE_REFS
, to align 👍 .
docs/content/policy-language.md
Outdated
import future.keywords | ||
|
||
# A partial object rule that converts a list of users to a mapping by "role" and then "id". | ||
users_by_role[role][id] := user { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's throw in an if
here and below
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
This PR adds the possibility to declare rules with variables at arbitrary locations int the ref; e.g.
By default, the compiler won't allow rule refs to have vars in any other position than the last term; to enable general rule refs, set the
OPA_ENABLE_GENERAL_RULE_REFS
env var (any value). (is this the best way to do this, or do we have precedence of other ways of setting feature flags?)Not included in this PR:
Feature flag to enable/disablePartial evalType-checker updatefmt