Skip to content

Commit

Permalink
Merge pull request #894 from CircleCI-Public/merge-main
Browse files Browse the repository at this point in the history
Merge main
  • Loading branch information
JulesFaucherre authored Mar 30, 2023
2 parents f660818 + 4cfa882 commit 6e5a974
Show file tree
Hide file tree
Showing 10 changed files with 177 additions and 73 deletions.
51 changes: 32 additions & 19 deletions cmd/policy/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ This group of commands allows the management of polices to be verified against b
var (
inputPath string
policyPath string
meta string
metaFile string
ownerID string
context string
Expand All @@ -284,15 +285,9 @@ This group of commands allows the management of polices to be verified against b
return fmt.Errorf("failed to read input file: %w", err)
}

var metadata map[string]interface{}
if metaFile != "" {
raw, err := os.ReadFile(metaFile)
if err != nil {
return fmt.Errorf("failed to read meta file: %w", err)
}
if err := yaml.Unmarshal(raw, &metadata); err != nil {
return fmt.Errorf("failed to decode meta content: %w", err)
}
metadata, err := readMetadata(meta, metaFile)
if err != nil {
return fmt.Errorf("failed to read metadata: %w", err)
}

decision, err := func() (*cpa.Decision, error) {
Expand Down Expand Up @@ -324,6 +319,7 @@ This group of commands allows the management of polices to be verified against b
cmd.Flags().StringVar(&ownerID, "owner-id", "", "the id of the policy's owner")
cmd.Flags().StringVar(&context, "context", "config", "policy context for decision")
cmd.Flags().StringVar(&inputPath, "input", "", "path to input file")
cmd.Flags().StringVar(&meta, "meta", "", "decision metadata (json string)")
cmd.Flags().StringVar(&metaFile, "metafile", "", "decision metadata file")
cmd.Flags().BoolVar(&strict, "strict", false, "return non-zero status code for decision resulting in HARD_FAIL")

Expand All @@ -335,7 +331,7 @@ This group of commands allows the management of polices to be verified against b
}()

eval := func() *cobra.Command {
var inputPath, metaFile, query string
var inputPath, meta, metaFile, query string
cmd := &cobra.Command{
Short: "perform raw opa evaluation locally",
Use: "eval <policy_file_or_dir_path>",
Expand All @@ -346,15 +342,9 @@ This group of commands allows the management of polices to be verified against b
return fmt.Errorf("failed to read input file: %w", err)
}

var metadata map[string]interface{}
if metaFile != "" {
raw, err := os.ReadFile(metaFile)
if err != nil {
return fmt.Errorf("failed to read meta file: %w", err)
}
if err := yaml.Unmarshal(raw, &metadata); err != nil {
return fmt.Errorf("failed to decode meta content: %w", err)
}
metadata, err := readMetadata(meta, metaFile)
if err != nil {
return fmt.Errorf("failed to read metadata: %w", err)
}

decision, err := getPolicyEvaluationLocally(policyPath, input, metadata, query)
Expand All @@ -373,6 +363,7 @@ This group of commands allows the management of polices to be verified against b
}

cmd.Flags().StringVar(&inputPath, "input", "", "path to input file")
cmd.Flags().StringVar(&meta, "meta", "", "decision metadata (json string)")
cmd.Flags().StringVar(&metaFile, "metafile", "", "decision metadata file")
cmd.Flags().StringVar(&query, "query", "data", "policy decision query")

Expand Down Expand Up @@ -510,6 +501,28 @@ This group of commands allows the management of polices to be verified against b
return cmd
}

func readMetadata(meta string, metaFile string) (map[string]interface{}, error) {
var metadata map[string]interface{}
if meta != "" && metaFile != "" {
return nil, fmt.Errorf("use either --meta or --metafile flag, but not both")
}
if meta != "" {
if err := json.Unmarshal([]byte(meta), &metadata); err != nil {
return nil, fmt.Errorf("failed to decode meta content: %w", err)
}
}
if metaFile != "" {
raw, err := os.ReadFile(metaFile)
if err != nil {
return nil, fmt.Errorf("failed to read meta file: %w", err)
}
if err := yaml.Unmarshal(raw, &metadata); err != nil {
return nil, fmt.Errorf("failed to decode metafile content: %w", err)
}
}
return metadata, nil
}

// prettyJSONEncoder takes a writer and returns a new json encoder with indent set to two space characters
func prettyJSONEncoder(dst io.Writer) *json.Encoder {
enc := json.NewEncoder(dst)
Expand Down
120 changes: 108 additions & 12 deletions cmd/policy/policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -698,7 +698,29 @@ func TestMakeDecisionCommand(t *testing.T) {
ExpectedOutput: "{\n \"status\": \"PASS\"\n}\n",
},
{
Name: "sends expected request with metadata",
Name: "sends expected request with meta",
Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test1/test.yml", "--context", "custom", "--meta", `{"project_id": "test-project-id","vcs": {"branch": "main"}}`},
ServerHandler: func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, r.Method, "POST")
assert.Equal(t, r.URL.Path, "/api/v1/owner/test-owner/context/custom/decision")

var payload map[string]interface{}
assert.NilError(t, json.NewDecoder(r.Body).Decode(&payload))

assert.DeepEqual(t, payload, map[string]interface{}{
"input": "test: config\n",
"metadata": map[string]interface{}{
"project_id": "test-project-id",
"vcs": map[string]any{"branch": "main"},
},
})

_, _ = io.WriteString(w, `{"status":"PASS"}`)
},
ExpectedOutput: "{\n \"status\": \"PASS\"\n}\n",
},
{
Name: "sends expected request with metafile",
Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test1/test.yml", "--context", "custom", "--metafile", "./testdata/test1/meta.yml"},
ServerHandler: func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, r.Method, "POST")
Expand All @@ -711,7 +733,7 @@ func TestMakeDecisionCommand(t *testing.T) {
"input": "test: config\n",
"metadata": map[string]interface{}{
"project_id": "test-project-id",
"branch": "main",
"vcs": map[string]any{"branch": "main"},
},
})

Expand Down Expand Up @@ -749,6 +771,11 @@ func TestMakeDecisionCommand(t *testing.T) {
Args: []string{"decide", "./testdata/no_such_file.rego", "--input", "./testdata/test1/test.yml"},
ExpectedErr: "failed to make decision: failed to load policy files: failed to walk root: ",
},
{
Name: "fails if both meta and metafile are provided",
Args: []string{"decide", "./testdata/test0/policy.rego", "--input", "./testdata/test1/test.yml", "--meta", "{}", "--metafile", "somefile"},
ExpectedErr: "failed to read metadata: use either --meta or --metafile flag, but not both",
},
{
Name: "successfully performs decision for policy FILE provided locally",
Args: []string{"decide", "./testdata/test0/policy.rego", "--input", "./testdata/test0/config.yml"},
Expand Down Expand Up @@ -797,7 +824,21 @@ func TestMakeDecisionCommand(t *testing.T) {
ExpectedErr: "policy decision status: ERROR",
},
{
Name: "successfully performs decision with metadata for policy FILE provided locally",
Name: "successfully performs decision with meta for policy FILE provided locally",
Args: []string{
"decide", "./testdata/test0/subdir/meta-policy-subdir/meta-policy.rego", "--meta",
`{"project_id": "test-project-id","vcs": {"branch": "main"}}`, "--input", "./testdata/test0/config.yml",
},
ExpectedOutput: `{
"status": "PASS",
"enabled_rules": [
"enabled"
]
}
`,
},
{
Name: "successfully performs decision with metafile for policy FILE provided locally",
Args: []string{
"decide", "./testdata/test0/subdir/meta-policy-subdir/meta-policy.rego", "--metafile",
"./testdata/test1/meta.yml", "--input", "./testdata/test0/config.yml",
Expand Down Expand Up @@ -866,14 +907,22 @@ func TestRawOPAEvaluationCommand(t *testing.T) {
ExpectedErr: "failed to make decision: failed to load policy files: failed to walk root: ",
},
{
Name: "successfully performs raw opa evaluation for policy FILE provided locally, input and metadata",
Name: "fails if both meta and metafile are provided",
Args: []string{"eval", "./testdata/test0/policy.rego", "--input", "./testdata/test1/test.yml", "--meta", "{}", "--metafile", "somefile"},
ExpectedErr: "failed to read metadata: use either --meta or --metafile flag, but not both",
},
{
Name: "successfully performs raw opa evaluation for policy FILE provided locally, input and meta",
Args: []string{
"eval", "./testdata/test0/subdir/meta-policy-subdir/meta-policy.rego", "--metafile",
"./testdata/test1/meta.yml", "--input", "./testdata/test0/config.yml",
"eval", "./testdata/test0/subdir/meta-policy-subdir/meta-policy.rego",
"--meta", `{"project_id": "test-project-id","vcs": {"branch": "main"}}`,
"--input", "./testdata/test0/config.yml",
},
ExpectedOutput: `{
"meta": {
"branch": "main",
"vcs": {
"branch": "main"
},
"project_id": "test-project-id"
},
"org": {
Expand All @@ -888,10 +937,50 @@ func TestRawOPAEvaluationCommand(t *testing.T) {
`,
},
{
Name: "successfully performs raw opa evaluation for policy FILE provided locally, input, metadata and query",
Name: "successfully performs raw opa evaluation for policy FILE provided locally, input and metafile",
Args: []string{
"eval", "./testdata/test0/subdir/meta-policy-subdir/meta-policy.rego", "--metafile",
"./testdata/test1/meta.yml", "--input", "./testdata/test0/config.yml", "--query", "data.org.enable_rule",
"eval", "./testdata/test0/subdir/meta-policy-subdir/meta-policy.rego",
"--metafile", "./testdata/test1/meta.yml",
"--input", "./testdata/test0/config.yml",
},
ExpectedOutput: `{
"meta": {
"vcs": {
"branch": "main"
},
"project_id": "test-project-id"
},
"org": {
"enable_rule": [
"enabled"
],
"policy_name": [
"meta_policy_test"
]
}
}
`,
},
{
Name: "successfully performs raw opa evaluation for policy FILE provided locally, input, meta and query",
Args: []string{
"eval", "./testdata/test0/subdir/meta-policy-subdir/meta-policy.rego",
"--meta", `{"project_id": "test-project-id","vcs": {"branch": "main"}}`,
"--input", "./testdata/test0/config.yml",
"--query", "data.org.enable_rule",
},
ExpectedOutput: `[
"enabled"
]
`,
},
{
Name: "successfully performs raw opa evaluation for policy FILE provided locally, input, metafile and query",
Args: []string{
"eval", "./testdata/test0/subdir/meta-policy-subdir/meta-policy.rego",
"--metafile", "./testdata/test1/meta.yml",
"--input", "./testdata/test0/config.yml",
"--query", "data.org.enable_rule",
},
ExpectedOutput: `[
"enabled"
Expand All @@ -911,7 +1000,9 @@ func TestRawOPAEvaluationCommand(t *testing.T) {

cmd, stdout, _ := makeCMD()

cmd.SetArgs(append(tc.Args, "--policy-base-url", svr.URL))
args := append(tc.Args, "--policy-base-url", svr.URL)

cmd.SetArgs(args)

err := cmd.Execute()
if tc.ExpectedErr == "" {
Expand All @@ -920,7 +1011,12 @@ func TestRawOPAEvaluationCommand(t *testing.T) {
assert.ErrorContains(t, err, tc.ExpectedErr)
return
}
assert.Equal(t, stdout.String(), tc.ExpectedOutput)

var actual, expected any
assert.NilError(t, json.Unmarshal(stdout.Bytes(), &actual))
assert.NilError(t, json.Unmarshal([]byte(tc.ExpectedOutput), &expected))

assert.DeepEqual(t, actual, expected)
})
}
}
Expand Down
1 change: 1 addition & 0 deletions cmd/policy/testdata/policy/decide-expected-usage.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ policy decide ./policies --input ./.circleci/config.yml
Flags:
--context string policy context for decision (default "config")
--input string path to input file
--meta string decision metadata (json string)
--metafile string decision metadata file
--owner-id string the id of the policy's owner
--strict return non-zero status code for decision resulting in HARD_FAIL
Expand Down
1 change: 1 addition & 0 deletions cmd/policy/testdata/policy/eval-expected-usage.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ policy eval ./policies --input ./.circleci/config.yml

Flags:
--input string path to input file
--meta string decision metadata (json string)
--metafile string decision metadata file
--query string policy decision query (default "data")

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package org

policy_name["meta_policy_test"]
enable_rule["enabled"] { data.meta.branch == "main" }
enable_rule["enabled"] { data.meta.vcs.branch == "main" }
enable_rule["disabled"] { data.meta.project_id != "test-project-id" }
3 changes: 2 additions & 1 deletion cmd/policy/testdata/test1/meta.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
project_id: test-project-id
branch: main
vcs:
branch: main
2 changes: 1 addition & 1 deletion cmd/policy/testdata/test_policies/policy.rego
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ policy_name["test"]

enable_rule["fail_if_not_main"]

fail_if_not_main = "branch must be main!" { data.meta.branch != "main" }
fail_if_not_main = "branch must be main!" { data.meta.vcs.branch != "main" }
6 changes: 4 additions & 2 deletions cmd/policy/testdata/test_policies/policy_test.yaml
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
test_main:
meta:
branch: main
vcs:
branch: main
decision: &root_decision
status: PASS
enabled_rules:
- fail_if_not_main

test_feature:
meta:
branch: feature
vcs:
branch: feature
decision:
<<: *root_decision
status: SOFT_FAIL
Expand Down
20 changes: 9 additions & 11 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module github.com/CircleCI-Public/circleci-cli

require (
github.com/AlecAivazis/survey/v2 v2.1.1
github.com/CircleCI-Public/circle-policy-agent v0.0.564
github.com/CircleCI-Public/circle-policy-agent v0.0.608
github.com/Masterminds/semver v1.4.2
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
github.com/blang/semver v3.5.1+incompatible
Expand Down Expand Up @@ -35,7 +35,8 @@ require (
github.com/charmbracelet/lipgloss v0.5.0
github.com/erikgeiser/promptkit v0.7.0
github.com/hexops/gotextdiff v1.0.3
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb
github.com/stretchr/testify v1.8.2
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
)

require (
Expand All @@ -52,7 +53,7 @@ require (
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-git/go-billy/v5 v5.0.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02 // indirect
github.com/imdario/mergo v0.3.12 // indirect
Expand All @@ -63,32 +64,29 @@ require (
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mb0/diff v0.0.0-20131118162322-d8d9a906c24d // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 // indirect
github.com/muesli/cancelreader v0.2.0 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.12.0 // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/open-policy-agent/opa v0.49.0 // indirect
github.com/open-policy-agent/opa v0.50.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/sergi/go-diff v1.1.0 // indirect
github.com/stretchr/testify v1.8.2 // indirect
github.com/tchap/go-patricia/v2 v2.3.1 // indirect
github.com/xanzy/ssh-agent v0.2.1 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/yashtewari/glob-intersection v0.1.0 // indirect
github.com/yazgazan/jaydiff v0.3.1 // indirect
golang.org/x/crypto v0.3.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/term v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/term v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
Expand Down
Loading

0 comments on commit 6e5a974

Please sign in to comment.