-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: use opa for validate and patch
- Loading branch information
Showing
10 changed files
with
481 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package mutator | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
|
||
jsonpatch "github.com/evanphx/json-patch" | ||
"github.com/hashicorp/go-hclog" | ||
"github.com/hashicorp/nomad/api" | ||
"github.com/mxab/nacp/admissionctrl/opa" | ||
) | ||
|
||
type OpaJsonPatchMutator struct { | ||
ruleSets []*opa.OpaRuleSet | ||
logger hclog.Logger | ||
} | ||
|
||
func (j *OpaJsonPatchMutator) Mutate(job *api.Job) (out *api.Job, warnings []error, err error) { | ||
|
||
ctx := context.TODO() | ||
for _, ruleSet := range j.ruleSets { | ||
result, err := ruleSet.Eval(ctx, job) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
patchData, ok := result[0].Bindings["patch"].([]interface{}) | ||
patchJSON, err := json.Marshal(patchData) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
|
||
if ok { | ||
patch, err := jsonpatch.DecodePatch(patchJSON) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
j.logger.Debug("Got patch fom ruleset %s, patch: %v", ruleSet.Name(), patchJSON) | ||
jobJson, err := json.Marshal(job) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
|
||
patched, err := patch.Apply(jobJson) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
var patchedJob api.Job | ||
err = json.Unmarshal(patched, &patchedJob) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
job = &patchedJob | ||
|
||
} | ||
|
||
} | ||
|
||
return job, nil, nil | ||
} | ||
func (j *OpaJsonPatchMutator) Name() string { | ||
return "jsonpatch" | ||
} | ||
|
||
func NewOpaJsonPatchMutator(rules []opa.OpaQueryAndModule, logger hclog.Logger) (*OpaJsonPatchMutator, error) { | ||
|
||
ctx := context.TODO() | ||
// read the policy file | ||
ruleSets, err := opa.CreateOpaRuleSet(rules, ctx) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &OpaJsonPatchMutator{ | ||
ruleSets: ruleSets, | ||
logger: logger, | ||
}, nil | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package mutator | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/hashicorp/go-hclog" | ||
"github.com/hashicorp/nomad/api" | ||
"github.com/mxab/nacp/admissionctrl/opa" | ||
"github.com/mxab/nacp/testutil" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestJSONPatcher_Mutate(t *testing.T) { | ||
type args struct { | ||
job *api.Job | ||
} | ||
tests := []struct { | ||
name string | ||
j *OpaJsonPatchMutator | ||
args args | ||
wantOut *api.Job | ||
wantWarnings []error | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "nothing", | ||
j: newMutator(t, []opa.OpaQueryAndModule{}), | ||
|
||
args: args{ | ||
job: &api.Job{}, | ||
}, | ||
wantOut: &api.Job{}, | ||
wantWarnings: []error{}, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "hello world", | ||
j: newMutator(t, []opa.OpaQueryAndModule{ | ||
{ | ||
Filename: testutil.Filepath(t, "opa/mutators/hello_world_meta.rego"), | ||
Query: "patch = data.hello_world_meta.patch", | ||
}, | ||
}), | ||
|
||
args: args{ | ||
job: &api.Job{}, | ||
}, | ||
wantOut: &api.Job{ | ||
Meta: map[string]string{ | ||
"hello": "world", | ||
}, | ||
}, | ||
wantWarnings: []error{}, | ||
wantErr: false, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
gotOut, gotWarnings, err := tt.j.Mutate(tt.args.job) | ||
require.Equal(t, tt.wantErr, err != nil, "JSONPatcher.Mutate() error = %v, wantErr %v", err, tt.wantErr) | ||
assert.Empty(t, gotWarnings) | ||
assert.Equal(t, tt.wantOut, gotOut) | ||
|
||
}) | ||
} | ||
} | ||
|
||
func newMutator(t *testing.T, rules []opa.OpaQueryAndModule) *OpaJsonPatchMutator { | ||
t.Helper() | ||
m, err := NewOpaJsonPatchMutator(rules, hclog.NewNullLogger()) | ||
require.NoError(t, err) | ||
return m | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package opa | ||
|
||
import ( | ||
"context" | ||
"os" | ||
|
||
"github.com/hashicorp/nomad/api" | ||
"github.com/open-policy-agent/opa/rego" | ||
) | ||
|
||
type OpaQueryAndModule struct { | ||
Filename string | ||
Query string | ||
} | ||
|
||
type OpaRuleSet struct { | ||
rule OpaQueryAndModule | ||
prepared *rego.PreparedEvalQuery | ||
} | ||
|
||
func CreateOpaRuleSet(rules []OpaQueryAndModule, ctx context.Context) ([]*OpaRuleSet, error) { | ||
var ruleSets []*OpaRuleSet | ||
|
||
for _, rule := range rules { | ||
|
||
module, err := os.ReadFile(rule.Filename) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
query, err := rego.New( | ||
rego.Query(rule.Query), | ||
rego.Module(rule.Filename, string(module)), | ||
).PrepareForEval(ctx) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
ruleSets = append(ruleSets, &OpaRuleSet{ | ||
rule: rule, | ||
prepared: &query, | ||
}, | ||
) | ||
|
||
} | ||
return ruleSets, nil | ||
} | ||
|
||
func (r *OpaRuleSet) Eval(ctx context.Context, job *api.Job) (rego.ResultSet, error) { | ||
|
||
results, err := r.prepared.Eval(ctx, rego.EvalInput(job)) | ||
return results, err | ||
} | ||
func (r *OpaRuleSet) Name() string { | ||
return r.rule.Filename | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package hello_world_meta | ||
|
||
|
||
patch[operation] { | ||
|
||
not input.Meta | ||
operation := { | ||
"op": "add", | ||
"path": "/Meta", | ||
"value": {} | ||
} | ||
} | ||
patch[operation] { | ||
|
||
is_null(input.Meta) | ||
operation := { | ||
"op": "add", | ||
"path": "/Meta", | ||
"value": {} | ||
} | ||
} | ||
patch[operation] { | ||
|
||
not input.Meta.hello | ||
operation := { | ||
"op": "add", | ||
"path": "/Meta/hello", | ||
"value": "world" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package hello_world_meta_test | ||
|
||
import data.hello_world_meta.patch | ||
|
||
import future.keywords | ||
|
||
test_hello_world if { | ||
e := patch with input as { | ||
"ID": "my-job", | ||
"Meta": {}, | ||
} | ||
e[{ | ||
"op": "add", | ||
"path": "/Meta/hello", | ||
"value": "world" | ||
}] | ||
|
||
} | ||
|
||
test_hello_world_add_meta if { | ||
e := patch with input as { | ||
"ID": "my-job" | ||
} | ||
count(e) == 2 | ||
trace(sprintf("patch: %v", [e])) | ||
|
||
e == { | ||
{ | ||
"op": "add", | ||
"path": "/Meta", | ||
"value": {} | ||
}, | ||
{ | ||
"op": "add", | ||
"path": "/Meta/hello", | ||
"value": "world" | ||
} | ||
} | ||
} | ||
test_hello_world_add_meta_if_meta_null if { | ||
e := patch with input as { | ||
"ID": "my-job", | ||
"Meta": null | ||
} | ||
count(e) == 2 | ||
trace(sprintf("patch: %v", [e])) | ||
|
||
e == { | ||
{ | ||
"op": "add", | ||
"path": "/Meta", | ||
"value": {} | ||
}, | ||
{ | ||
"op": "add", | ||
"path": "/Meta/hello", | ||
"value": "world" | ||
} | ||
} | ||
} | ||
test_hello_world_no_code_if_exists if { | ||
e := patch with input as { | ||
"ID": "my-job", | ||
"Meta": {"hello": "world"} | ||
} | ||
count(e) == 0 | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
|
||
package costcenter_meta | ||
|
||
|
||
import future.keywords.contains | ||
import future.keywords.if | ||
|
||
# This definition checks if the costcenter label is not provided. Each rule definition | ||
# contributes to the set of error messages. | ||
errors contains msg if { | ||
# The `not` keyword turns an undefined statement into a true statement. If any | ||
# of the keys are missing, this statement will be true. | ||
|
||
|
||
not input.Meta.costcenter | ||
trace("Costcenter code is missing") | ||
|
||
msg := "Every job must have a costcenter metadata label" | ||
} | ||
|
||
# This definition checks if the costcenter label is formatted appropriately. Each rule | ||
# definition contributes to the set of error messages. | ||
errors contains msg if { | ||
value := input.Meta.costcenter | ||
|
||
not startswith(value, "cccode-") | ||
msg := sprintf("Costcenter code must start with `cccode-`; found `%v`", [value]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package costcenter_meta_test | ||
import data.costcenter_meta.errors | ||
|
||
import future.keywords | ||
|
||
test_missing_costcenter if { | ||
count(errors) == 1 with input as { | ||
"ID": "my-job", | ||
"Meta": {}, | ||
} | ||
|
||
} | ||
|
||
test_costcenter_prefix_wrong if { | ||
count(errors)==1 with input as { | ||
"ID": "my-job", | ||
"Meta": {"costcenter": "my-costcenter"}, | ||
} | ||
|
||
} | ||
|
||
test_costcenter_correct if { | ||
count(errors) == 0 with input as { | ||
"ID": "my-job", | ||
"Meta": {"costcenter": "cccode-my-costcenter"}, | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package dummy | ||
|
||
errors[errMsg] { | ||
errMsg := "This is a error message" | ||
} | ||
|
||
warnings[warnMsg] { | ||
warnMsg := "This is a warning message" | ||
} |
Oops, something went wrong.