Skip to content
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

Add initial support for print() function #3868

Merged
merged 12 commits into from
Oct 14, 2021

Conversation

tsandall
Copy link
Member

@tsandall tsandall commented Oct 6, 2021

This is still WIP but the core implementation is there. Remaining work:

  • write docs for the feature
  • enable print() inside of opa test
  • enable print() inside of opa eval
  • enable print() inside of server

🐛 I've found an issue during manual testing with nested calls, e.g., x = split("A,B,C", ",")[_]; print(x) generates an error right now because the output var analysis doesn't take into account nested calls. Will fix this tomorrow.

@tsandall tsandall requested a review from srenatus October 6, 2021 19:40
@tsandall tsandall force-pushed the print-function branch 2 times, most recently from 999a778 to e856927 Compare October 6, 2021 23:56
@tsandall tsandall requested a review from anderseknert October 7, 2021 00:17
Copy link
Contributor

@srenatus srenatus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good so far! 👀

ast/compilehelper.go Outdated Show resolved Hide resolved
topdown/print.go Outdated Show resolved Hide resolved
ast/compile.go Show resolved Hide resolved
ast/compile.go Outdated Show resolved Hide resolved
topdown/print_test.go Outdated Show resolved Hide resolved
topdown/print_test.go Show resolved Hide resolved
@anderseknert
Copy link
Member

As for "breaking" print in the server.. I think that would be rather surprising to many, and I'd rather have as few surprises as possible here - even more so in features that are targeting new users. One of the core ideas behind print for me was that it would feel familiar for anyone coming from another language.. and I while not using print for production/server use is pretty much common sense in any language, I'm not aware of any other language that enforces this by simply not printing in some contexts deemed inappropriate.

I was probably into my second year of working with OPA before I really started using opa eval, and to some extent the OPA REPL, for development and testing.. before that it was all opa test and opa run (since that was how the client was going to interact with OPA). From what I've seen when working with others I'm not alone in this. I guess it just comes pretty natural when you're used to working with micro services and APIs that the way you interact with a service is through its public API, even in development.

I suspect the special treatment of print when running the OPA server would lead to a lot of "why aren't my print statements printing!?" questions, and I think we've seen before that this isn't always the best time to try to educate someone that there are better ways of doing things - that's IMO one of the reasons why we're looking at print in the first place.

If an OPA admin wants to forbid print in production, I'd rather see they'd be able to do so by using a command line flag, or by using the capabilities.json file (though that's only a "build time" check right now IIRC).

@tsandall tsandall force-pushed the print-function branch 8 times, most recently from 844bd45 to d89c79e Compare October 8, 2021 21:17
@tsandall
Copy link
Member Author

tsandall commented Oct 8, 2021

@anderseknert @srenatus I've fixed the outstanding issue and added e2e tests for the server and addressed the comments. I've added a small section to the language reference for the function. PTAL.

anderseknert
anderseknert previously approved these changes Oct 9, 2021
Copy link
Member

@anderseknert anderseknert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Doc update is great 👍

srenatus
srenatus previously approved these changes Oct 11, 2021
Copy link
Contributor

@srenatus srenatus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉 🥳 Let's make sure we get a proper announcement for this when it comes wrapped in a release.

internal/logging/logging.go Outdated Show resolved Hide resolved
internal/logging/logging.go Show resolved Hide resolved
internal/logging/logging.go Show resolved Hide resolved
rego/rego.go Outdated Show resolved Hide resolved
runtime/logging.go Show resolved Hide resolved
topdown/builtins.go Outdated Show resolved Hide resolved
topdown/eval.go Show resolved Hide resolved
topdown/print.go Outdated Show resolved Hide resolved
topdown/print.go Show resolved Hide resolved
topdown/print_test.go Outdated Show resolved Hide resolved
@srenatus
Copy link
Contributor

💭 How will this break now?

package p

x {
  print("hello")
}

print(x) = trace(sprintf("trace: %v", [x]))

@tsandall
Copy link
Member Author

tsandall commented Oct 12, 2021

@srenatus your example shouldn't break; the print function defined inside of the module will take precedence:

> show
package repl

print(x) = trace(sprintf("trace: %v", [x]))

x {
        print("x")
}
> x
query:1     Enter data.repl.x = _
query:1     | Eval data.repl.x = _
query:1     | Index data.repl.x (matched 1 rule)
query:1     | Enter data.repl.x
query:1     | | Eval data.repl.print("x")
query:1     | | Index data.repl.print (matched 1 rule)
query:1     | | Enter data.repl.print
query:1     | | | Eval true
query:1     | | | Eval sprintf("trace: %v", [x], __local1__)
query:1     | | | Eval trace(__local1__, __local2__)
query:1     | | | Note "trace: x"
query:1     | | | Exit data.repl.print
query:1     | | Exit data.repl.x
query:1     | Exit data.repl.x = _
query:1     Redo data.repl.x = _
query:1     | Redo data.repl.x = _
query:1     | Redo data.repl.x
query:1     | | Redo data.repl.print("x")
query:1     | | Redo data.repl.print
query:1     | | | Redo trace(__local1__, __local2__)
query:1     | | | Redo sprintf("trace: %v", [x], __local1__)
query:1     | | | Redo true
true

This applies to all built-ins... even ones with infix operators 😅 ...

> 1+2
-1
> show
package repl

plus(x, y) = x - y

@tsandall tsandall dismissed stale reviews from srenatus and anderseknert via 3fd7313 October 12, 2021 17:11
@tsandall tsandall force-pushed the print-function branch 2 times, most recently from 3fd7313 to 1d71393 Compare October 12, 2021 17:27
srenatus
srenatus previously approved these changes Oct 12, 2021
Copy link
Contributor

@srenatus srenatus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@srenatus
Copy link
Contributor

GK users would appreciate this, too, I think. Have we reached out already? Would be nice if it made it into a GK release not too long after OPA...

@tsandall
Copy link
Member Author

This looks great ! Just one question for my understanding: For the server mode, the print statements will be included in info and above log levels. We don't provide a config to control the whether or not to include print statements in the log. So the expectation here is if the logs get filled with print values, we expect the user to adjust the policy accordingly. I think this makes sense btw.

@ashutosh-narkar right print() calls will get logged at INFO level. For production deployments, users should run at ERROR level though because there is no need for informational messages.

@tsandall tsandall dismissed stale reviews from ashutosh-narkar and srenatus via 87d42ae October 13, 2021 23:53
@tsandall tsandall force-pushed the print-function branch 2 times, most recently from 87d42ae to 5c79586 Compare October 14, 2021 00:48
srenatus
srenatus previously approved these changes Oct 14, 2021
Copy link
Contributor

@srenatus srenatus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wasm changes look good to me ✔️

@srenatus srenatus self-requested a review October 14, 2021 08:41
Comment on lines +1400 to +1402
x := NewTerm(gen.Generate()).SetLocation(args[j].Loc())
capture := Equality.Expr(x, args[j]).SetLocation(args[j].Loc())
arr = arr.Append(SetComprehensionTerm(x, NewBody(capture)).SetLocation(args[j].Loc()))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could use some sort of helper, walking a term and calling SetLocation on anything that accepts it.

@srenatus
Copy link
Contributor

Something's still funky with the wasm pieces:

$ opa eval -trego -fpretty 'print(input)'
<undefined>
true
$ opa eval -twasm -fpretty 'print(input)'
1 error occurred: internal_error: eval_internal_error: illegal argument type: object

@srenatus srenatus dismissed their stale review October 14, 2021 10:01

another wasm bit with input

@srenatus
Copy link
Contributor

So, print(input) with undefined input will end up as internal.print([{}]), and when converting the wasm value of the first arg to an ast.Value here, we'll parse {} as empty object, not as empty set. ⏩ That's a bug in value_dump, unrelated to this PR. I'll take care of it.

srenatus
srenatus previously approved these changes Oct 14, 2021
Copy link
Contributor

@srenatus srenatus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. I'll deal with value_dump and empty sets in another PR.

@srenatus
Copy link
Contributor

☝️ With that commit cherry-picked, we get:

$ opa eval -twasm -fpretty 'print(input)'
<undefined>
true
$ opa eval -trego -fpretty 'print(input)'
<undefined>
true

This commit prevents the rewriting step from attempting to capture the
result of void function calls. This avoids generating invalid queries
that will fail to type check.

Signed-off-by: Torin Sandall <torinsandall@gmail.com>
This commit fixes the output var analysis for nested
calls. Previously, statements like `x = split("abc", "")[y]` would not
mark `x` or `y` as outputs. The reason was that the output var
analysis was never invoked at an early enough stage in the compiler
where these nested calls still existed.

Signed-off-by: Torin Sandall <torinsandall@gmail.com>
The print built-in function has special-case handling in two ways:

1. Print arguments are wrapped in comprehensions at compile-time to
ensure that undefined values do not short-circuit evaluation. The
evaluator is aware of this rewriting and extracts the actual values
during evaluation.

2. Print arguments cannot contain unsafe/undeclared variables, i.e.,
vars appearing in refs inside of print args _must_ be assigned in
a previous expression in the body. This ensures that print statements
do not affect the semantics of the rule.

Signed-off-by: Torin Sandall <torinsandall@gmail.com>
Signed-off-by: Torin Sandall <torinsandall@gmail.com>
Signed-off-by: Torin Sandall <torinsandall@gmail.com>
Signed-off-by: Torin Sandall <torinsandall@gmail.com>
Signed-off-by: Torin Sandall <torinsandall@gmail.com>
This commit does not change any functionality except it provides
callers with a way to provide a logger when instantiating the
runtime. Previously, the runtime had hardcoded dependencies on the
global logrus logger which made it problematic to test logging
behaviour. With this change, the logger can be supplied as a
parameter (which allows the caller to mock out the logger in tests...)

As part of this change, the dependencies on logrus have been moved out
of the runtime package entirely.

This commit includes a breaking change to the
runtime.NewLoggingHandler function: the function now requires a logger
to be supplied.

Signed-off-by: Torin Sandall <torinsandall@gmail.com>
Signed-off-by: Torin Sandall <torinsandall@gmail.com>
This commit enables print() calls inside of the server for INFO and
DEBUG log levels. The print hook is plumbed through to the server via
the manager so that other server implementations (e.g., the Envoy
plugin) can be updated similarly.

The server will compile print() calls for the /v1/query API but not
others since (i) print() calls inside the policies will already have
been compiled and (ii) the queries are limited to fetching `data`
paths and therefore cannot contain print() calls themselves. The
bundle plugin has been updated to compile print() calls as well--this
way the bundle plugin/server will respect incoming bundles and not
attempt to override them.

Signed-off-by: Torin Sandall <torinsandall@gmail.com>
Signed-off-by: Torin Sandall <torinsandall@gmail.com>
This commit plumbs the print hook through to the wasm runtime so that
print calls are enabled when the wasm target is on. This commit also
updates the planner and compiler to support calls to void
functions--previously, the planner and wasm backend assumed that
functions returned values so they would perform checks for defined
values, however, with void functions, those checks must be suppressed.

Signed-off-by: Torin Sandall <torinsandall@gmail.com>
@tsandall tsandall merged commit a1f7e30 into open-policy-agent:main Oct 14, 2021
@tsandall tsandall mentioned this pull request Oct 14, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants