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 option for specifying decision ID to SDK #6101

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
36 changes: 17 additions & 19 deletions sdk/opa.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ func (opa *OPA) Decision(ctx context.Context, options DecisionOptions) (*Decisio
Input: &options.Input,
NDBuiltinCache: &options.NDBCache,
Metrics: options.Metrics,
DecisionID: options.DecisionID,
}

// Only use non-deterministic builtins cache if it's available.
Expand Down Expand Up @@ -294,41 +295,36 @@ type DecisionOptions struct {
Metrics metrics.Metrics // specifies the metrics to use for preparing and evaluation, optional
Profiler topdown.QueryTracer // specifies the profiler to use, optional
Instrument bool // if true, instrumentation will be enabled
DecisionID string // the identifier for this decision; if not set, a globally unique identifier will be generated
}

// DecisionResult contains the output of query evaluation.
type DecisionResult struct {
ID string // provides a globally unique identifier for this decision (which is included in the decision log.)
ID string // provides the identifier for this decision (which is included in the decision log.)
Result interface{} // provides the output of query evaluation.
Provenance types.ProvenanceV1 // wraps the bundle build/version information
}

func newDecisionResult() (*DecisionResult, error) {
id, err := uuid.New(rand.Reader)
if err != nil {
return nil, err
}
result := &DecisionResult{ID: id}
return result, nil
}

func (opa *OPA) executeTransaction(ctx context.Context, record *server.Info, work func(state, *DecisionResult)) (*DecisionResult, error) {
if record.Metrics == nil {
record.Metrics = metrics.New()
}
record.Metrics.Timer(metrics.SDKDecisionEval).Start()

result, err := newDecisionResult()
if err != nil {
return nil, err
if record.DecisionID == "" {
id, err := uuid.New(rand.Reader)
if err != nil {
return nil, err
}
record.DecisionID = id
}

result := &DecisionResult{ID: record.DecisionID}

opa.mtx.Lock()
s := *opa.state
opa.mtx.Unlock()

record.DecisionID = result.ID

if record.Timestamp.IsZero() {
record.Timestamp = time.Now().UTC()
}
Expand Down Expand Up @@ -375,10 +371,11 @@ func (opa *OPA) Partial(ctx context.Context, options PartialOptions) (*PartialRe
}

record := server.Info{
Timestamp: options.Now,
Input: &options.Input,
Query: options.Query,
Metrics: options.Metrics,
Timestamp: options.Now,
Input: &options.Input,
Query: options.Query,
Metrics: options.Metrics,
DecisionID: options.DecisionID,
}

var provenance types.ProvenanceV1
Expand Down Expand Up @@ -448,6 +445,7 @@ type PartialOptions struct {
Metrics metrics.Metrics // specifies the metrics to use for preparing and evaluation, optional
Profiler topdown.QueryTracer // specifies the profiler to use, optional
Instrument bool // if true, instrumentation will be enabled
DecisionID string // the identifier for this decision; if not set, a globally unique identifier will be generated
}

type PartialResult struct {
Expand Down
155 changes: 155 additions & 0 deletions sdk/opa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,76 @@ main = data.foo

}

func TestDecisionWithConfigurableID(t *testing.T) {
ctx := context.Background()

server := sdktest.MustNewServer(
sdktest.MockBundle("/bundles/bundle.tar.gz", map[string]string{
"main.rego": `
package system

main = time.now_ns()
`,
}),
)

defer server.Stop()

config := fmt.Sprintf(`{
"services": {
"test": {
"url": %q
}
},
"bundles": {
"test": {
"resource": "/bundles/bundle.tar.gz"
}
},
"decision_logs": {
"console": true
}
}`, server.URL())

testLogger := loggingtest.New()
opa, err := sdk.New(ctx, sdk.Options{
Config: strings.NewReader(config),
ConsoleLogger: testLogger})

if err != nil {
t.Fatal(err)
}

defer opa.Stop(ctx)

if _, err := opa.Decision(ctx, sdk.DecisionOptions{
Now: time.Unix(0, 1619868194450288000).UTC(),
}); err != nil {
t.Fatal(err)
}

if _, err := opa.Decision(ctx, sdk.DecisionOptions{
Now: time.Unix(0, 1619868194450288000).UTC(),
DecisionID: "164031de-e511-11ec-8fea-0242ac120002",
}); err != nil {
t.Fatal(err)
}

entries := testLogger.Entries()

if exp, act := 2, len(entries); exp != act {
t.Fatalf("expected %d entries, got %d", exp, act)
}

if entries[0].Fields["decision_id"] == "" {
t.Fatalf("expected not empty decision_id")
}

if entries[1].Fields["decision_id"] != "164031de-e511-11ec-8fea-0242ac120002" {
t.Fatalf("expected %v but got %v", "164031de-e511-11ec-8fea-0242ac120002", entries[1].Fields["decision_id"])
}
}

func TestPartial(t *testing.T) {

ctx := context.Background()
Expand Down Expand Up @@ -1230,6 +1300,91 @@ allow {

}

func TestPartialWithConfigurableID(t *testing.T) {

ctx := context.Background()

server := sdktest.MustNewServer(
sdktest.MockBundle("/bundles/bundle.tar.gz", map[string]string{
"main.rego": `
package test

allow {
data.junk.x = input.y
}
`,
}),
)

defer server.Stop()

config := fmt.Sprintf(`{
"services": {
"test": {
"url": %q
}
},
"bundles": {
"test": {
"resource": "/bundles/bundle.tar.gz"
}
},
"decision_logs": {
"console": true
}
}`, server.URL())

testLogger := loggingtest.New()
opa, err := sdk.New(ctx, sdk.Options{
Config: strings.NewReader(config),
ConsoleLogger: testLogger,
})
if err != nil {
t.Fatal(err)
}

defer opa.Stop(ctx)

if result, err := opa.Partial(ctx, sdk.PartialOptions{
Input: map[string]int{"y": 2},
Query: "data.test.allow = true",
Unknowns: []string{"data.junk.x"},
Mapper: &sdk.RawMapper{},
Now: time.Unix(0, 1619868194450288000).UTC(),
}); err != nil {
t.Fatal(err)
} else if decision, ok := result.Result.(*rego.PartialQueries); !ok || decision.Queries[0].String() != "2 = data.junk.x" {
t.Fatal("expected &{[2 = data.junk.x] []} true but got:", decision, ok)
}

if result, err := opa.Partial(ctx, sdk.PartialOptions{
Input: map[string]int{"y": 2},
Query: "data.test.allow = true",
Unknowns: []string{"data.junk.x"},
Mapper: &sdk.RawMapper{},
Now: time.Unix(0, 1619868194450288000).UTC(),
DecisionID: "164031de-e511-11ec-8fea-0242ac120002",
}); err != nil {
t.Fatal(err)
} else if decision, ok := result.Result.(*rego.PartialQueries); !ok || decision.Queries[0].String() != "2 = data.junk.x" {
t.Fatal("expected &{[2 = data.junk.x] []} true but got:", decision, ok)
}

entries := testLogger.Entries()

if exp, act := 2, len(entries); exp != act {
t.Fatalf("expected %d entries, got %d", exp, act)
}

if entries[0].Fields["decision_id"] == "" {
t.Fatalf("expected not empty decision_id")
}

if entries[1].Fields["decision_id"] != "164031de-e511-11ec-8fea-0242ac120002" {
t.Fatalf("expected %v but got %v", "164031de-e511-11ec-8fea-0242ac120002", entries[1].Fields["decision_id"])
}
}

func TestUndefinedError(t *testing.T) {

ctx := context.Background()
Expand Down