From b26d777e4f9ad3e5243f41016584613faa2946ad Mon Sep 17 00:00:00 2001 From: Johan Fylling Date: Mon, 23 Sep 2024 16:10:25 +0200 Subject: [PATCH 1/3] debug: Configurable rego-options on debugger Adding `RegoOption` launch option to debugger for setting custom rego options. Fixes: #7045 Signed-off-by: Johan Fylling --- debug/debugger.go | 60 ++++++++++++++++++++++++-------- debug/debugger_test.go | 77 +++++++++++++++++++++++++++++++++++++----- debug/thread.go | 3 ++ 3 files changed, 118 insertions(+), 22 deletions(-) diff --git a/debug/debugger.go b/debug/debugger.go index f5f5326136..5923b41914 100644 --- a/debug/debugger.go +++ b/debug/debugger.go @@ -36,7 +36,7 @@ import ( type Debugger interface { // LaunchEval starts a new eval debug session with the given LaunchEvalProperties. // The returned session is in a stopped state, and must be resumed to start execution. - LaunchEval(ctx context.Context, props LaunchEvalProperties) (Session, error) + LaunchEval(ctx context.Context, props LaunchEvalProperties, opts ...LaunchOption) (Session, error) } type debugger struct { @@ -207,14 +207,38 @@ type LaunchTestProperties struct { } type LaunchProperties struct { - BundlePaths []string - DataPaths []string - StopOnResult bool - StopOnEntry bool - StopOnFail bool - EnablePrint bool - SkipOps []topdown.Op - RuleIndexing bool + BundlePaths []string + DataPaths []string + StopOnResult bool + StopOnEntry bool + StopOnFail bool + EnablePrint bool + SkipOps []topdown.Op + StrictBuiltinErrors bool + RuleIndexing bool +} + +type LaunchOption func(options *launchOptions) + +type launchOptions struct { + regoOptions []func(*rego.Rego) +} + +func newLaunchOptions(opts []LaunchOption) *launchOptions { + options := &launchOptions{} + for _, opt := range opts { + opt(options) + } + return options +} + +// RegoOption adds a rego option to the internal Rego instance. +// Options may be overridden by the debugger, and it is recommended to +// use LaunchEvalProperties for commonly used options. +func RegoOption(opt func(*rego.Rego)) LaunchOption { + return func(options *launchOptions) { + options.regoOptions = append(options.regoOptions, opt) + } } func (lp LaunchProperties) String() string { @@ -225,19 +249,27 @@ func (lp LaunchProperties) String() string { return string(b) } -func (d *debugger) LaunchEval(ctx context.Context, props LaunchEvalProperties) (Session, error) { +func (d *debugger) LaunchEval(ctx context.Context, props LaunchEvalProperties, opts ...LaunchOption) (Session, error) { + options := newLaunchOptions(opts) + store := inmem.New() txn, err := store.NewTransaction(ctx, storage.TransactionParams{Write: true}) if err != nil { return nil, fmt.Errorf("failed to create store transaction: %v", err) } - regoArgs := []func(*rego.Rego){ - rego.Query(props.Query), - rego.Store(store), - rego.Transaction(txn), + var regoArgs []func(*rego.Rego) + + // We apply all user options first, so the debugger can make overrides if necessary. + for _, opt := range options.regoOptions { + regoArgs = append(regoArgs, opt) } + regoArgs = append(regoArgs, rego.Query(props.Query)) + regoArgs = append(regoArgs, rego.Store(store)) + regoArgs = append(regoArgs, rego.Transaction(txn)) + regoArgs = append(regoArgs, rego.StrictBuiltinErrors(props.StrictBuiltinErrors)) + if props.SkipOps == nil { props.SkipOps = []topdown.Op{topdown.IndexOp, topdown.RedoOp, topdown.SaveOp, topdown.UnifyOp} } diff --git a/debug/debugger_test.go b/debug/debugger_test.go index 2f7c691552..829408388b 100644 --- a/debug/debugger_test.go +++ b/debug/debugger_test.go @@ -6,6 +6,7 @@ package debug import ( "context" + "encoding/json" "fmt" "path" "reflect" @@ -20,6 +21,8 @@ import ( "github.com/open-policy-agent/opa/storage" "github.com/open-policy-agent/opa/storage/inmem" "github.com/open-policy-agent/opa/topdown" + "github.com/open-policy-agent/opa/topdown/builtins" + "github.com/open-policy-agent/opa/types" "github.com/open-policy-agent/opa/util/test" ) @@ -2018,14 +2021,6 @@ func newTestStack(events ...*topdown.Event) *testStack { } } -//func (ts *testStack) done() bool { -// return ts.index >= len(ts.events) -//} - -//func (ts *testStack) onLastEvent() bool { -// return ts.index == len(ts.events)-1 -//} - func (ts *testStack) Enabled() bool { return true } @@ -2068,3 +2063,69 @@ func (ts *testStack) Close() error { ts.closed = true return nil } + +func TestDebuggerCustomBuiltIn(t *testing.T) { + ctx := context.Background() + + decl := ®o.Function{ + Name: "my.builtin", + Description: "My built-in", + Decl: types.NewFunction( + types.Args(types.S, types.S), + types.S, + ), + } + + fn := func(bctx rego.BuiltinContext, a, b *ast.Term) (*ast.Term, error) { + aStr, err := builtins.StringOperand(a.Value, 1) + if err != nil { + return nil, err + } + + bStr, err := builtins.StringOperand(b.Value, 2) + if err != nil { + return nil, err + } + + return ast.StringTerm(fmt.Sprintf("%s+%s", aStr, bStr)), nil + } + + props := LaunchEvalProperties{ + Query: `x := my.builtin("hello", "world")`, + } + + exp := `[{"expressions":[{"value":true,"text":"x := my.builtin(\"hello\", \"world\")","location":{"row":1,"col":1}}],"bindings":{"x":"\"hello\"+\"world\""}}]` + + eh := newTestEventHandler() + + d := NewDebugger(SetEventHandler(eh.HandleEvent)) + + s, err := d.LaunchEval(ctx, props, RegoOption(rego.Function2(decl, fn))) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + if err := s.ResumeAll(); err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + // wait for result + if e := eh.WaitFor(ctx, TerminatedEventType); e == nil { + t.Fatal("Expected terminated event") + } + + ts, err := s.Threads() + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + res := ts[0].(*thread).stack.Result() + bs, err := json.Marshal(res) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + actual := string(bs) + if actual != exp { + t.Fatalf("Expected:\n\n%v\n\nbut got:\n\n%v", exp, actual) + } +} diff --git a/debug/thread.go b/debug/thread.go index b20f58ec84..b0884241cd 100644 --- a/debug/thread.go +++ b/debug/thread.go @@ -32,8 +32,11 @@ type eventHandler func(t *thread, stackIndex int, e *topdown.Event, s threadStat type ThreadID int +// Thread represents a single thread of execution. type Thread interface { + // ID returns the unique identifier for the thread. ID() ThreadID + // Name returns the human-readable name of the thread. Name() string } From 69f86c19f65297aff20dcd02aa36dfae5824e566 Mon Sep 17 00:00:00 2001 From: Johan Fylling Date: Mon, 23 Sep 2024 16:15:15 +0200 Subject: [PATCH 2/3] Making linter happy Signed-off-by: Johan Fylling --- debug/debugger.go | 4 +--- debug/debugger_test.go | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/debug/debugger.go b/debug/debugger.go index 5923b41914..9a8f935d3e 100644 --- a/debug/debugger.go +++ b/debug/debugger.go @@ -261,9 +261,7 @@ func (d *debugger) LaunchEval(ctx context.Context, props LaunchEvalProperties, o var regoArgs []func(*rego.Rego) // We apply all user options first, so the debugger can make overrides if necessary. - for _, opt := range options.regoOptions { - regoArgs = append(regoArgs, opt) - } + regoArgs = append(regoArgs, options.regoOptions...) regoArgs = append(regoArgs, rego.Query(props.Query)) regoArgs = append(regoArgs, rego.Store(store)) diff --git a/debug/debugger_test.go b/debug/debugger_test.go index 829408388b..bba77fdb6a 100644 --- a/debug/debugger_test.go +++ b/debug/debugger_test.go @@ -2076,7 +2076,7 @@ func TestDebuggerCustomBuiltIn(t *testing.T) { ), } - fn := func(bctx rego.BuiltinContext, a, b *ast.Term) (*ast.Term, error) { + fn := func(_ rego.BuiltinContext, a, b *ast.Term) (*ast.Term, error) { aStr, err := builtins.StringOperand(a.Value, 1) if err != nil { return nil, err From 6dac42d3f4274ed34c2a94356b77275e2dce5b3b Mon Sep 17 00:00:00 2001 From: Johan Fylling Date: Mon, 23 Sep 2024 16:21:19 +0200 Subject: [PATCH 3/3] Making linter happy (again) Signed-off-by: Johan Fylling --- debug/debugger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debug/debugger.go b/debug/debugger.go index 9a8f935d3e..550152109b 100644 --- a/debug/debugger.go +++ b/debug/debugger.go @@ -258,7 +258,7 @@ func (d *debugger) LaunchEval(ctx context.Context, props LaunchEvalProperties, o return nil, fmt.Errorf("failed to create store transaction: %v", err) } - var regoArgs []func(*rego.Rego) + regoArgs := make([]func(*rego.Rego), 0, 4) // We apply all user options first, so the debugger can make overrides if necessary. regoArgs = append(regoArgs, options.regoOptions...)