mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-12 19:32:48 -06:00
rego: make wasmtime-go dependency "more optional" (#3708)
Users of OPA as a library are concerned about big binary blobs in their vendor/
directories. Even more so if they don't use them. This is the case for anyone
using OPA as library, but not using the wasm-backed evaluation feature.
With this change, importers of any packages other than `server` and `cmd`
will have to explicitly opt-in to using wasm evaluation features by having an
underscore import somewhere:
import _ "github.com/open-policy-agent/opa/features/wasm"
Fixes #3545.
Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
// Copyright 2021 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build opa_wasm
|
||||
|
||||
package cmd
|
||||
|
||||
import _ "github.com/open-policy-agent/opa/features/wasm"
|
||||
+1
-1
@@ -45,7 +45,7 @@ func generateCmdOutput(out io.Writer, check bool) {
|
||||
|
||||
var wasmAvailable string
|
||||
|
||||
if version.WasmRuntimeAvailable {
|
||||
if version.WasmRuntimeAvailable() {
|
||||
wasmAvailable = "available"
|
||||
} else {
|
||||
wasmAvailable = "unavailable"
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright 2021 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Import this package to enable evaluation of rego code using the
|
||||
// built-in wasm engine.
|
||||
package wasm
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/open-policy-agent/opa/internal/rego/opa"
|
||||
wopa "github.com/open-policy-agent/opa/internal/wasm/sdk/opa"
|
||||
)
|
||||
|
||||
func init() {
|
||||
opa.RegisterEngine("wasm", &factory{})
|
||||
}
|
||||
|
||||
// OPA is an implementation of the OPA SDK.
|
||||
type OPA struct {
|
||||
opa *wopa.OPA
|
||||
}
|
||||
|
||||
type factory struct{}
|
||||
|
||||
// New constructs a new OPA instance.
|
||||
func (*factory) New() opa.EvalEngine {
|
||||
return &OPA{opa: wopa.New()}
|
||||
}
|
||||
|
||||
// WithPolicyBytes configures the compiled policy to load.
|
||||
func (o *OPA) WithPolicyBytes(policy []byte) opa.EvalEngine {
|
||||
o.opa = o.opa.WithPolicyBytes(policy)
|
||||
return o
|
||||
}
|
||||
|
||||
// WithDataJSON configures the JSON data to load.
|
||||
func (o *OPA) WithDataJSON(data interface{}) opa.EvalEngine {
|
||||
o.opa = o.opa.WithDataJSON(data)
|
||||
return o
|
||||
}
|
||||
|
||||
// Init initializes the OPA instance.
|
||||
func (o *OPA) Init() (opa.EvalEngine, error) {
|
||||
i, err := o.opa.Init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o.opa = i
|
||||
return o, nil
|
||||
}
|
||||
|
||||
func (o *OPA) Entrypoints(ctx context.Context) (map[string]int32, error) {
|
||||
return o.opa.Entrypoints(ctx)
|
||||
}
|
||||
|
||||
// Eval evaluates the policy.
|
||||
func (o *OPA) Eval(ctx context.Context, opts opa.EvalOpts) (*opa.Result, error) {
|
||||
evalOptions := wopa.EvalOpts{
|
||||
Input: opts.Input,
|
||||
Metrics: opts.Metrics,
|
||||
Entrypoint: opts.Entrypoint,
|
||||
Time: opts.Time,
|
||||
Seed: opts.Seed,
|
||||
InterQueryBuiltinCache: opts.InterQueryBuiltinCache,
|
||||
}
|
||||
|
||||
res, err := o.opa.Eval(ctx, evalOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &opa.Result{Result: res.Result}, nil
|
||||
}
|
||||
|
||||
func (o *OPA) SetData(ctx context.Context, data interface{}) error {
|
||||
return o.opa.SetData(ctx, data)
|
||||
}
|
||||
|
||||
func (o *OPA) SetDataPath(ctx context.Context, path []string, data interface{}) error {
|
||||
return o.opa.SetDataPath(ctx, path, data)
|
||||
}
|
||||
|
||||
func (o *OPA) RemoveDataPath(ctx context.Context, path []string) error {
|
||||
return o.opa.RemoveDataPath(ctx, path)
|
||||
}
|
||||
|
||||
func (o *OPA) Close() {
|
||||
o.opa.Close()
|
||||
}
|
||||
@@ -198,6 +198,9 @@ func NewOutputErrors(err error) []OutputError {
|
||||
Message: err.Error(),
|
||||
err: typedErr,
|
||||
}}
|
||||
if d, ok := err.(rego.ErrorWithDetails); ok {
|
||||
errs[0].Details = d.Details()
|
||||
}
|
||||
}
|
||||
}
|
||||
return errs
|
||||
@@ -388,8 +391,8 @@ func Raw(w io.Writer, r Output) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func prettyError(w io.Writer, err error) error {
|
||||
_, err = fmt.Fprintln(w, err)
|
||||
func prettyError(w io.Writer, errs OutputErrors) error {
|
||||
_, err := fmt.Fprintln(w, errs)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,15 @@ func (t *testErrorWithMarshaller) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
type testErrorWithDetails struct{}
|
||||
|
||||
func (*testErrorWithDetails) Error() string { return "something went wrong" }
|
||||
func (*testErrorWithDetails) Details() string {
|
||||
return `oh
|
||||
so
|
||||
wrong`
|
||||
}
|
||||
|
||||
func validateJSONOutput(t *testing.T, testErr error, expected string) {
|
||||
t.Helper()
|
||||
output := Output{Errors: NewOutputErrors(testErr)}
|
||||
@@ -81,6 +90,21 @@ func TestOutputJSONErrorCustomMarshaller(t *testing.T) {
|
||||
validateJSONOutput(t, err, expected)
|
||||
}
|
||||
|
||||
func TestOutputJSONErrorWithDetails(t *testing.T) {
|
||||
err := &testErrorWithDetails{}
|
||||
expected := `{
|
||||
"errors": [
|
||||
{
|
||||
"message": "something went wrong",
|
||||
"details": "oh\nso\nwrong"
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
validateJSONOutput(t, err, expected)
|
||||
}
|
||||
|
||||
func TestOutputJSONErrorStructuredASTErr(t *testing.T) {
|
||||
err := &ast.Error{
|
||||
Code: "1",
|
||||
@@ -454,6 +478,17 @@ func TestRaw(t *testing.T) {
|
||||
},
|
||||
want: "1 error occurred: boom\n",
|
||||
},
|
||||
{
|
||||
// NOTE(sr): The presentation package outputs whatever Error() on
|
||||
// the errors it is given yields. So even though NewOutputErrors
|
||||
// will pick up the error details, they won't be output, as they
|
||||
// are not included in the error's Error() string.
|
||||
note: "error with details",
|
||||
output: Output{
|
||||
Errors: NewOutputErrors(&testErrorWithDetails{}),
|
||||
},
|
||||
want: "1 error occurred: something went wrong\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2021 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package opa
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// ErrEngineNotFound is returned by LookupEngine if no wasm engine was
|
||||
// registered by that name.
|
||||
var ErrEngineNotFound error = &errEngineNotFound{}
|
||||
|
||||
type errEngineNotFound struct{}
|
||||
|
||||
func (*errEngineNotFound) Error() string { return "engine not found" }
|
||||
func (*errEngineNotFound) Details() string {
|
||||
return `WebAssembly runtime not supported in this build.
|
||||
----------------------------------------------------------------------------------
|
||||
Please download an OPA binary with Wasm enabled from
|
||||
https://www.openpolicyagent.org/docs/latest/#running-opa
|
||||
or build it yourself (with Wasm enabled).
|
||||
----------------------------------------------------------------------------------
|
||||
`
|
||||
}
|
||||
|
||||
// Engine repesents a factory for instances of EvalEngine implementations
|
||||
type Engine interface {
|
||||
New() EvalEngine
|
||||
}
|
||||
|
||||
// EvalEngine is the interface implemented by an engine used to eval a policy
|
||||
type EvalEngine interface {
|
||||
Init() (EvalEngine, error)
|
||||
Entrypoints(context.Context) (map[string]int32, error)
|
||||
WithPolicyBytes([]byte) EvalEngine
|
||||
WithDataJSON(interface{}) EvalEngine
|
||||
Eval(context.Context, EvalOpts) (*Result, error)
|
||||
SetData(context.Context, interface{}) error
|
||||
SetDataPath(context.Context, []string, interface{}) error
|
||||
RemoveDataPath(context.Context, []string) error
|
||||
Close()
|
||||
}
|
||||
|
||||
var engines = map[string]Engine{}
|
||||
|
||||
// RegisterEngine registers an evaluation engine by its target name.
|
||||
// Note that the "rego" target is always available.
|
||||
func RegisterEngine(name string, e Engine) {
|
||||
if engines[name] != nil {
|
||||
panic("duplicate engine registration")
|
||||
}
|
||||
engines[name] = e
|
||||
}
|
||||
|
||||
// LookupEngine allows retrieving an engine registered by name
|
||||
func LookupEngine(name string) (Engine, error) {
|
||||
e, ok := engines[name]
|
||||
if !ok {
|
||||
return nil, ErrEngineNotFound
|
||||
}
|
||||
return e, nil
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
// Copyright 2021 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !opa_wasm
|
||||
|
||||
package opa
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// OPA is a stub implementation of a opa.OPA.
|
||||
type OPA struct {
|
||||
}
|
||||
|
||||
// New unimplemented.
|
||||
func New() *OPA {
|
||||
fmt.Fprintf(os.Stderr, `WebAssembly runtime not supported in this build.
|
||||
----------------------------------------------------------------------------------
|
||||
Please download an OPA binary with Wasm enabled from
|
||||
https://www.openpolicyagent.org/docs/latest/#running-opa
|
||||
or build it yourself (with Wasm enabled).
|
||||
----------------------------------------------------------------------------------
|
||||
`)
|
||||
os.Exit(1)
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithPolicyBytes unimplemented.
|
||||
func (o *OPA) WithPolicyBytes(policy []byte) *OPA {
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// WithDataJSON unimplemented.
|
||||
func (o *OPA) WithDataJSON(data interface{}) *OPA {
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Init unimplemented.
|
||||
func (o *OPA) Init() (*OPA, error) {
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Eval unimplemented.
|
||||
func (o *OPA) Eval(ctx context.Context, opts EvalOpts) (*Result, error) {
|
||||
panic("unreachable")
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
// Copyright 2021 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build opa_wasm
|
||||
|
||||
package opa
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
wopa "github.com/open-policy-agent/opa/internal/wasm/sdk/opa"
|
||||
)
|
||||
|
||||
// OPA is an implementation of the OPA SDK.
|
||||
type OPA struct {
|
||||
opa *wopa.OPA
|
||||
}
|
||||
|
||||
// New constructs a new OPA instance.
|
||||
func New() *OPA {
|
||||
return &OPA{opa: wopa.New()}
|
||||
}
|
||||
|
||||
// WithPolicyBytes configures the compiled policy to load.
|
||||
func (o *OPA) WithPolicyBytes(policy []byte) *OPA {
|
||||
o.opa = o.opa.WithPolicyBytes(policy)
|
||||
return o
|
||||
}
|
||||
|
||||
// WithDataJSON configures the JSON data to load.
|
||||
func (o *OPA) WithDataJSON(data interface{}) *OPA {
|
||||
o.opa = o.opa.WithDataJSON(data)
|
||||
return o
|
||||
}
|
||||
|
||||
// Init initializes the OPA instance.
|
||||
func (o *OPA) Init() (*OPA, error) {
|
||||
i, err := o.opa.Init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o.opa = i
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// Eval evaluates the policy.
|
||||
func (o *OPA) Eval(ctx context.Context, opts EvalOpts) (*Result, error) {
|
||||
evalOptions := wopa.EvalOpts{
|
||||
Input: opts.Input,
|
||||
Metrics: opts.Metrics,
|
||||
Time: opts.Time,
|
||||
Seed: opts.Seed,
|
||||
InterQueryBuiltinCache: opts.InterQueryBuiltinCache,
|
||||
}
|
||||
|
||||
res, err := o.opa.Eval(ctx, evalOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Result{Result: res.Result}, nil
|
||||
}
|
||||
@@ -17,6 +17,7 @@ type Result struct {
|
||||
type EvalOpts struct {
|
||||
Input *interface{}
|
||||
Metrics metrics.Metrics
|
||||
Entrypoint int32
|
||||
Time time.Time
|
||||
Seed io.Reader
|
||||
InterQueryBuiltinCache cache.InterQueryCache
|
||||
|
||||
@@ -16,3 +16,9 @@ func (h *HaltError) Error() string {
|
||||
func NewHaltError(err error) error {
|
||||
return &HaltError{err: err}
|
||||
}
|
||||
|
||||
// ErrorWithDetails interface is satisfied by an error that provides further
|
||||
// details.
|
||||
type ErrorWithDetails interface {
|
||||
Details() string
|
||||
}
|
||||
|
||||
+7
-2
@@ -550,7 +550,7 @@ type Rego struct {
|
||||
resolvers []refResolver
|
||||
schemaSet *ast.SchemaSet
|
||||
target string // target type (wasm, rego, etc.)
|
||||
opa *opa.OPA
|
||||
opa opa.EvalEngine
|
||||
}
|
||||
|
||||
// Function represents a built-in function that is callable in Rego.
|
||||
@@ -1501,7 +1501,12 @@ func (r *Rego) PrepareForEval(ctx context.Context, opts ...PrepareOption) (Prepa
|
||||
return PreparedEvalQuery{}, err
|
||||
}
|
||||
|
||||
o, err := opa.New().WithPolicyBytes(cr.Bytes).WithDataJSON(data).Init()
|
||||
e, err := opa.LookupEngine(targetWasm)
|
||||
if err != nil {
|
||||
return PreparedEvalQuery{}, err
|
||||
}
|
||||
|
||||
o, err := e.New().WithPolicyBytes(cr.Bytes).WithDataJSON(data).Init()
|
||||
if err != nil {
|
||||
_ = txnClose(ctx, err) // Ignore error
|
||||
return PreparedEvalQuery{}, err
|
||||
|
||||
@@ -16,11 +16,14 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/fortytw2/leaktest"
|
||||
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
sdk_errors "github.com/open-policy-agent/opa/internal/wasm/sdk/opa/errors"
|
||||
"github.com/open-policy-agent/opa/storage/inmem"
|
||||
"github.com/open-policy-agent/opa/topdown"
|
||||
"github.com/open-policy-agent/opa/topdown/cache"
|
||||
|
||||
_ "github.com/open-policy-agent/opa/features/wasm"
|
||||
)
|
||||
|
||||
func TestPrepareAndEvalWithWasmTarget(t *testing.T) {
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
_ "github.com/open-policy-agent/opa/features/wasm"
|
||||
)
|
||||
|
||||
func TestReplWasmTarget(t *testing.T) {
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
// Copyright 2020 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !opa_wasm
|
||||
|
||||
package wasm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
"github.com/open-policy-agent/opa/resolver"
|
||||
)
|
||||
|
||||
// Resolver is a stub implementation of a resolver.Resolver.
|
||||
type Resolver struct {
|
||||
}
|
||||
|
||||
// Entrypoints unimplemented.
|
||||
func (r *Resolver) Entrypoints() []ast.Ref {
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Close unimplemented.
|
||||
func (r *Resolver) Close() {
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Eval unimplemented.
|
||||
func (r *Resolver) Eval(context.Context, resolver.Input) (resolver.Result, error) {
|
||||
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// SetData unimplemented.
|
||||
func (r *Resolver) SetData(context.Context, interface{}) error {
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// SetDataPath unimplemented.
|
||||
func (r *Resolver) SetDataPath(context.Context, []string, interface{}) error {
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// RemoveDataPath unimplemented.
|
||||
func (r *Resolver) RemoveDataPath(context.Context, []string) error {
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// New unimplemented. Will always return an error.
|
||||
func New([]ast.Ref, []byte, interface{}) (*Resolver, error) {
|
||||
return nil, errors.New("WebAssembly runtime not supported in this build")
|
||||
}
|
||||
@@ -2,8 +2,6 @@
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build opa_wasm
|
||||
|
||||
package wasm
|
||||
|
||||
import (
|
||||
@@ -11,16 +9,19 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/open-policy-agent/opa/internal/wasm/sdk/opa"
|
||||
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
"github.com/open-policy-agent/opa/internal/rego/opa"
|
||||
"github.com/open-policy-agent/opa/resolver"
|
||||
)
|
||||
|
||||
// New creates a new Resolver instance which is using the Wasm module
|
||||
// policy for the given entrypoint ref.
|
||||
func New(entrypoints []ast.Ref, policy []byte, data interface{}) (*Resolver, error) {
|
||||
o, err := opa.New().
|
||||
e, err := opa.LookupEngine("wasm")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o, err := e.New().
|
||||
WithPolicyBytes(policy).
|
||||
WithDataJSON(data).
|
||||
Init()
|
||||
@@ -63,7 +64,7 @@ func New(entrypoints []ast.Ref, policy []byte, data interface{}) (*Resolver, err
|
||||
type Resolver struct {
|
||||
entrypoints []ast.Ref
|
||||
entrypointIDs *ast.ValueMap
|
||||
o *opa.OPA
|
||||
o opa.EvalEngine
|
||||
}
|
||||
|
||||
// Entrypoints returns a list of entrypoints this resolver is configured to
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright 2021 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build opa_wasm
|
||||
|
||||
package server
|
||||
|
||||
import _ "github.com/open-policy-agent/opa/features/wasm"
|
||||
+6
-3
@@ -2,9 +2,12 @@
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build opa_wasm
|
||||
|
||||
package version
|
||||
|
||||
import "github.com/open-policy-agent/opa/internal/rego/opa"
|
||||
|
||||
// WasmRuntimeAvailable indicates if a wasm runtime is available in this OPA.
|
||||
const WasmRuntimeAvailable = true
|
||||
func WasmRuntimeAvailable() bool {
|
||||
_, err := opa.LookupEngine("wasm")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// Copyright 2020 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !opa_wasm
|
||||
|
||||
package version
|
||||
|
||||
// WasmRuntimeAvailable indicates if a wasm runtime is available in this OPA.
|
||||
const WasmRuntimeAvailable = false
|
||||
Reference in New Issue
Block a user