diff --git a/cmd/server.go b/cmd/server.go index b2c9827a39..0e3aba366a 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -1086,8 +1086,8 @@ func (s *ServerCmd) deprecationWarnings(userConfig *server.UserConfig) error { } if userConfig.AllowRepoConfig { deprecatedFlags = append(deprecatedFlags, AllowRepoConfigFlag) - yamlCfg += "\n allowed_overrides: [plan_requirements, apply_requirements, import_requirements, workflow]\n allow_custom_workflows: true" - jsonCfg += `, "allowed_overrides":["plan_requirements","apply_requirements","import_requirements","workflow"], "allow_custom_workflows":true` + yamlCfg += "\n allowed_overrides: [plan_requirements, apply_requirements, import_requirements, workflow, policy_check]\n allow_custom_workflows: true" + jsonCfg += `, "allowed_overrides":["plan_requirements","apply_requirements","import_requirements","workflow", "policy_check"], "allow_custom_workflows":true` } jsonCfg += "}]}" diff --git a/runatlantis.io/docs/policy-checking.md b/runatlantis.io/docs/policy-checking.md index 65b9667c88..2530c5969f 100644 --- a/runatlantis.io/docs/policy-checking.md +++ b/runatlantis.io/docs/policy-checking.md @@ -38,6 +38,10 @@ This section will provide a guide on how to get set up with a simple policy that Enable the workflow using the following server configuration flag `--enable-policy-checks` +::: warning +All repositories will have policy checking enabled. +::: + ### Step 2: Define the policy configuration Policy Configuration is defined in the [server-side repo configuration](https://www.runatlantis.io/docs/server-side-repo-config.html#reference). @@ -191,3 +195,33 @@ When the policy check workflow runs, a file is created in the working directory ] ``` + +## Running policy check only on some repositories + +When policy checking is enabled it will be enforced on all repositories, in order to disable policy checking on some repositories first [enable policy checks](https://www.runatlantis.io/docs/policy-checking.html#getting-started) and then disable it explicitly on each repository with the `policy_check` flag. + +For server side config: +```yml +# repos.yaml +repos: +- id: /.*/ + plan_requirements: [approved] + apply_requirements: [approved] + import_requirements: [approved] +- id: /special-repo/ + plan_requirements: [approved] + apply_requirements: [approved] + import_requirements: [approved] + policy_check: false +``` + +For repo level `atlantis.yaml` config: +```yml +version: 3 +projects: +- dir: project1 + workspace: staging +- dir: project1 + workspace: production + policy_check: false +``` diff --git a/runatlantis.io/docs/server-side-repo-config.md b/runatlantis.io/docs/server-side-repo-config.md index c44cddff99..4c616da66c 100644 --- a/runatlantis.io/docs/server-side-repo-config.md +++ b/runatlantis.io/docs/server-side-repo-config.md @@ -81,6 +81,9 @@ repos: post_workflow_hooks: - run: my-post-workflow-hook-command arg1 + # policy_check defines if policy checking should be enable on this repository. + policy_check: false + # id can also be an exact match. - id: github.com/myorg/specific-repo @@ -483,7 +486,8 @@ If you set a workflow with the key `default`, it will override this. | allowed_workflows | []string | none | no | A list of workflows that `atlantis.yaml` files can select from. | | allow_custom_workflows | bool | false | no | Whether or not to allow [Custom Workflows](custom-workflows.html). | | delete_source_branch_on_merge | bool | false | no | Whether or not to delete the source branch on merge. | -| repo_locking | bool | false | no | Whether or not to get a lock | +| repo_locking | bool | false | no | Whether or not to get a lock. | +| policy_check | bool | false | no | Whether or not to run policy checks on this repository. | :::tip Notes diff --git a/server/controllers/events/events_controller_e2e_test.go b/server/controllers/events/events_controller_e2e_test.go index d46b485e09..b5485e22c9 100644 --- a/server/controllers/events/events_controller_e2e_test.go +++ b/server/controllers/events/events_controller_e2e_test.go @@ -828,10 +828,14 @@ func TestGitHubWorkflowWithPolicyCheck(t *testing.T) { ModifiedFiles []string // Comments are what our mock user writes to the pull request. Comments []string + // PolicyCheck is true if we expect Atlantis to run policy checking + PolicyCheck bool // ExpAutomerge is true if we expect Atlantis to automerge. ExpAutomerge bool // ExpAutoplan is true if we expect Atlantis to autoplan. ExpAutoplan bool + // ExpPolicyChecks is true if we expect Atlantis to execute policy checks + ExpPolicyChecks bool // ExpQuietPolicyChecks is true if we expect Atlantis to exclude policy check output // when there's no error ExpQuietPolicyChecks bool @@ -846,10 +850,12 @@ func TestGitHubWorkflowWithPolicyCheck(t *testing.T) { ExpReplies [][]string }{ { - Description: "1 failing policy and 1 passing policy ", - RepoDir: "policy-checks-multi-projects", - ModifiedFiles: []string{"dir1/main.tf,", "dir2/main.tf"}, - ExpAutoplan: true, + Description: "1 failing policy and 1 passing policy ", + RepoDir: "policy-checks-multi-projects", + ModifiedFiles: []string{"dir1/main.tf,", "dir2/main.tf"}, + PolicyCheck: true, + ExpAutoplan: true, + ExpPolicyChecks: true, Comments: []string{ "atlantis apply", }, @@ -861,10 +867,12 @@ func TestGitHubWorkflowWithPolicyCheck(t *testing.T) { }, }, { - Description: "failing policy without policies passing using extra args", - RepoDir: "policy-checks-extra-args", - ModifiedFiles: []string{"main.tf"}, - ExpAutoplan: true, + Description: "failing policy without policies passing using extra args", + RepoDir: "policy-checks-extra-args", + ModifiedFiles: []string{"main.tf"}, + PolicyCheck: true, + ExpAutoplan: true, + ExpPolicyChecks: true, Comments: []string{ "atlantis apply", }, @@ -876,10 +884,12 @@ func TestGitHubWorkflowWithPolicyCheck(t *testing.T) { }, }, { - Description: "failing policy without policies passing", - RepoDir: "policy-checks", - ModifiedFiles: []string{"main.tf"}, - ExpAutoplan: true, + Description: "failing policy without policies passing", + RepoDir: "policy-checks", + ModifiedFiles: []string{"main.tf"}, + PolicyCheck: true, + ExpAutoplan: true, + ExpPolicyChecks: true, Comments: []string{ "atlantis apply", }, @@ -906,10 +916,12 @@ func TestGitHubWorkflowWithPolicyCheck(t *testing.T) { }, }, { - Description: "failing policy additional apply requirements specified", - RepoDir: "policy-checks-apply-reqs", - ModifiedFiles: []string{"main.tf"}, - ExpAutoplan: true, + Description: "failing policy additional apply requirements specified", + RepoDir: "policy-checks-apply-reqs", + ModifiedFiles: []string{"main.tf"}, + PolicyCheck: true, + ExpAutoplan: true, + ExpPolicyChecks: true, Comments: []string{ "atlantis apply", }, @@ -921,10 +933,12 @@ func TestGitHubWorkflowWithPolicyCheck(t *testing.T) { }, }, { - Description: "failing policy approved by non owner", - RepoDir: "policy-checks-diff-owner", - ModifiedFiles: []string{"main.tf"}, - ExpAutoplan: true, + Description: "failing policy approved by non owner", + RepoDir: "policy-checks-diff-owner", + ModifiedFiles: []string{"main.tf"}, + PolicyCheck: true, + ExpAutoplan: true, + ExpPolicyChecks: true, Comments: []string{ "atlantis approve_policies", "atlantis apply", @@ -941,7 +955,9 @@ func TestGitHubWorkflowWithPolicyCheck(t *testing.T) { Description: "successful policy checks with quiet flag enabled", RepoDir: "policy-checks-success-silent", ModifiedFiles: []string{"main.tf"}, + PolicyCheck: true, ExpAutoplan: true, + ExpPolicyChecks: true, ExpQuietPolicyChecks: true, Comments: []string{ "atlantis apply", @@ -956,7 +972,9 @@ func TestGitHubWorkflowWithPolicyCheck(t *testing.T) { Description: "failing policy checks with quiet flag enabled", RepoDir: "policy-checks", ModifiedFiles: []string{"main.tf"}, + PolicyCheck: true, ExpAutoplan: true, + ExpPolicyChecks: true, ExpQuietPolicyChecks: true, ExpQuietPolicyCheckFailure: true, Comments: []string{ @@ -970,10 +988,12 @@ func TestGitHubWorkflowWithPolicyCheck(t *testing.T) { }, }, { - Description: "failing policy with approval and policy approval clear", - RepoDir: "policy-checks-clear-approval", - ModifiedFiles: []string{"main.tf"}, - ExpAutoplan: true, + Description: "failing policy with approval and policy approval clear", + RepoDir: "policy-checks-clear-approval", + ModifiedFiles: []string{"main.tf"}, + PolicyCheck: true, + ExpAutoplan: true, + ExpPolicyChecks: true, Comments: []string{ "atlantis approve_policies", "atlantis approve_policies --clear-policy-approval", @@ -988,6 +1008,86 @@ func TestGitHubWorkflowWithPolicyCheck(t *testing.T) { {"exp-output-merge.txt"}, }, }, + { + Description: "policy checking disabled on specific repo", + RepoDir: "policy-checks-disabled-repo", + ModifiedFiles: []string{"main.tf"}, + PolicyCheck: true, + ExpAutoplan: true, + ExpPolicyChecks: false, + Comments: []string{ + "atlantis apply", + }, + ExpReplies: [][]string{ + {"exp-output-autoplan.txt"}, + {"exp-output-apply.txt"}, + {"exp-output-merge.txt"}, + }, + }, + { + Description: "policy checking disabled on specific repo server side", + RepoDir: "policy-checks-disabled-repo-server-side", + ModifiedFiles: []string{"main.tf"}, + PolicyCheck: true, + ExpAutoplan: true, + ExpPolicyChecks: false, + Comments: []string{ + "atlantis apply", + }, + ExpReplies: [][]string{ + {"exp-output-autoplan.txt"}, + {"exp-output-apply.txt"}, + {"exp-output-merge.txt"}, + }, + }, + { + Description: "policy checking enabled on specific repo but disabled globally", + RepoDir: "policy-checks-enabled-repo", + ModifiedFiles: []string{"main.tf"}, + PolicyCheck: false, + ExpAutoplan: true, + ExpPolicyChecks: false, + Comments: []string{ + "atlantis apply", + }, + ExpReplies: [][]string{ + {"exp-output-autoplan.txt"}, + {"exp-output-apply.txt"}, + {"exp-output-merge.txt"}, + }, + }, + { + Description: "policy checking enabled on specific repo server side but disabled globally", + RepoDir: "policy-checks-enabled-repo-server-side", + ModifiedFiles: []string{"main.tf"}, + PolicyCheck: false, + ExpAutoplan: true, + ExpPolicyChecks: false, + Comments: []string{ + "atlantis apply", + }, + ExpReplies: [][]string{ + {"exp-output-autoplan.txt"}, + {"exp-output-apply.txt"}, + {"exp-output-merge.txt"}, + }, + }, + { + Description: "policy checking disabled on previous regex match but not on repo", + RepoDir: "policy-checks-disabled-previous-match", + ModifiedFiles: []string{"main.tf"}, + PolicyCheck: true, + ExpAutoplan: true, + ExpPolicyChecks: false, + Comments: []string{ + "atlantis apply", + }, + ExpReplies: [][]string{ + {"exp-output-autoplan.txt"}, + {"exp-output-apply.txt"}, + {"exp-output-merge.txt"}, + }, + }, } for _, c := range cases { @@ -996,7 +1096,7 @@ func TestGitHubWorkflowWithPolicyCheck(t *testing.T) { // reset userConfig userConfig = server.UserConfig{} - userConfig.EnablePolicyChecksFlag = true + userConfig.EnablePolicyChecksFlag = c.PolicyCheck userConfig.QuietPolicyChecks = c.ExpQuietPolicyChecks ctrl, vcsClient, githubGetter, atlantisWorkspace := setupE2E(t, c.RepoDir, setupOption{}) @@ -1061,6 +1161,10 @@ func TestGitHubWorkflowWithPolicyCheck(t *testing.T) { } _, _, actReplies, _ := vcsClient.VerifyWasCalled(Times(expNumReplies)).CreateComment(Any[models.Repo](), Any[int](), Any[string](), Any[string]()).GetAllCapturedArguments() + if !c.ExpPolicyChecks { + expNumReplies-- + } + Assert(t, len(c.ExpReplies) == len(actReplies), "missing expected replies, got %d but expected %d", len(actReplies), len(c.ExpReplies)) for i, expReply := range c.ExpReplies { assertCommentEquals(t, expReply, actReplies[i], c.RepoDir, c.ExpParallel) diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/atlantis.yaml b/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/atlantis.yaml new file mode 100644 index 0000000000..8435733cd2 --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/atlantis.yaml @@ -0,0 +1,4 @@ +version: 3 +projects: +- dir: . + workspace: default diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/exp-output-apply-failed.txt b/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/exp-output-apply-failed.txt new file mode 100644 index 0000000000..2ae26e9fe5 --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/exp-output-apply-failed.txt @@ -0,0 +1,3 @@ +Ran Apply for dir: `.` workspace: `default` + +**Apply Failed**: All policies must pass for project before running apply. \ No newline at end of file diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/exp-output-apply.txt b/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/exp-output-apply.txt new file mode 100644 index 0000000000..a54764c3bd --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/exp-output-apply.txt @@ -0,0 +1,12 @@ +Ran Apply for dir: `.` workspace: `default` + +```diff +null_resource.simple: +null_resource.simple: + +Apply complete! Resources: 1 added, 0 changed, 0 destroyed. + +Outputs: + +workspace = "default" +``` \ No newline at end of file diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/exp-output-approve-policies.txt b/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/exp-output-approve-policies.txt new file mode 100644 index 0000000000..f5e100c23e --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/exp-output-approve-policies.txt @@ -0,0 +1,5 @@ +Approved Policies for 1 projects: + +1. dir: `.` workspace: `default` + + diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/exp-output-auto-policy-check.txt b/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/exp-output-auto-policy-check.txt new file mode 100644 index 0000000000..8faa2e036b --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/exp-output-auto-policy-check.txt @@ -0,0 +1,29 @@ +Ran Policy Check for dir: `.` workspace: `default` + +**Policy Check Failed**: Some policy sets did not pass. +#### Policy Set: `test_policy` +```diff +FAIL - - main - WARNING: Null Resource creation is prohibited. + +1 test, 0 passed, 0 warnings, 1 failure, 0 exceptions + +``` + + +#### Policy Approval Status: +``` +policy set: test_policy: requires: 1 approval(s), have: 0. +``` +* :heavy_check_mark: To **approve** this project, comment: + * `atlantis approve_policies -d .` +* :put_litter_in_its_place: To **delete** this plan click [here](lock-url) +* :repeat: To re-run policies **plan** this project again by commenting: + * `atlantis plan -d .` + +--- +* :heavy_check_mark: To **approve** all unapplied plans from this pull request, comment: + * `atlantis approve_policies` +* :put_litter_in_its_place: To delete all plans and locks for the PR, comment: + * `atlantis unlock` +* :repeat: To re-run policies **plan** this project again by commenting: + * `atlantis plan` \ No newline at end of file diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/exp-output-autoplan.txt b/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/exp-output-autoplan.txt new file mode 100644 index 0000000000..ace509e6e7 --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/exp-output-autoplan.txt @@ -0,0 +1,35 @@ +Ran Plan for dir: `.` workspace: `default` + +
Show Output + +```diff +Terraform used the selected providers to generate the following execution +plan. Resource actions are indicated with the following symbols: ++ create + +Terraform will perform the following actions: + + # null_resource.simple[0] will be created ++ resource "null_resource" "simple" { + + id = (known after apply) + } + +Plan: 1 to add, 0 to change, 0 to destroy. + +Changes to Outputs: ++ workspace = "default" +``` + +* :arrow_forward: To **apply** this plan, comment: + * `atlantis apply -d .` +* :put_litter_in_its_place: To **delete** this plan click [here](lock-url) +* :repeat: To **plan** this project again, comment: + * `atlantis plan -d .` +
+Plan: 1 to add, 0 to change, 0 to destroy. + +--- +* :fast_forward: To **apply** all unapplied plans from this pull request, comment: + * `atlantis apply` +* :put_litter_in_its_place: To delete all plans and locks for the PR, comment: + * `atlantis unlock` \ No newline at end of file diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/exp-output-merge.txt b/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/exp-output-merge.txt new file mode 100644 index 0000000000..872c5ee40c --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/exp-output-merge.txt @@ -0,0 +1,3 @@ +Locks and plans deleted for the projects and workspaces modified in this pull request: + +- dir: `.` workspace: `default` diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/main.tf b/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/main.tf new file mode 100644 index 0000000000..582f9ea01d --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/main.tf @@ -0,0 +1,7 @@ +resource "null_resource" "simple" { + count = 1 +} + +output "workspace" { + value = terraform.workspace +} diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/policies/policy.rego b/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/policies/policy.rego new file mode 100644 index 0000000000..126c2e4591 --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/policies/policy.rego @@ -0,0 +1,28 @@ +package main + +import input as tfplan + +deny[reason] { + num_deletes.null_resource > 0 + reason := "WARNING: Null Resource creation is prohibited." +} + +resource_types = {"null_resource"} + +resources[resource_type] = all { + some resource_type + resource_types[resource_type] + all := [name | + name := tfplan.resource_changes[_] + name.type == resource_type + ] +} + +# number of deletions of resources of a given type +num_deletes[resource_type] = num { + some resource_type + resource_types[resource_type] + all := resources[resource_type] + deletions := [res | res := all[_]; res.change.actions[_] == "create"] + num := count(deletions) +} diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/repos.yaml b/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/repos.yaml new file mode 100644 index 0000000000..96716637e1 --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-previous-match/repos.yaml @@ -0,0 +1,13 @@ +repos: + - id: "/.*/" + policy_check: false + - id: github.com/runatlantis/atlantis-tests +policies: + owners: + users: + - runatlantis + policy_sets: + - name: test_policy + path: policies/policy.rego + source: local + diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/atlantis.yaml b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/atlantis.yaml new file mode 100644 index 0000000000..8435733cd2 --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/atlantis.yaml @@ -0,0 +1,4 @@ +version: 3 +projects: +- dir: . + workspace: default diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/exp-output-apply-failed.txt b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/exp-output-apply-failed.txt new file mode 100644 index 0000000000..2ae26e9fe5 --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/exp-output-apply-failed.txt @@ -0,0 +1,3 @@ +Ran Apply for dir: `.` workspace: `default` + +**Apply Failed**: All policies must pass for project before running apply. \ No newline at end of file diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/exp-output-apply.txt b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/exp-output-apply.txt new file mode 100644 index 0000000000..a54764c3bd --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/exp-output-apply.txt @@ -0,0 +1,12 @@ +Ran Apply for dir: `.` workspace: `default` + +```diff +null_resource.simple: +null_resource.simple: + +Apply complete! Resources: 1 added, 0 changed, 0 destroyed. + +Outputs: + +workspace = "default" +``` \ No newline at end of file diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/exp-output-approve-policies.txt b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/exp-output-approve-policies.txt new file mode 100644 index 0000000000..f5e100c23e --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/exp-output-approve-policies.txt @@ -0,0 +1,5 @@ +Approved Policies for 1 projects: + +1. dir: `.` workspace: `default` + + diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/exp-output-auto-policy-check.txt b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/exp-output-auto-policy-check.txt new file mode 100644 index 0000000000..8faa2e036b --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/exp-output-auto-policy-check.txt @@ -0,0 +1,29 @@ +Ran Policy Check for dir: `.` workspace: `default` + +**Policy Check Failed**: Some policy sets did not pass. +#### Policy Set: `test_policy` +```diff +FAIL - - main - WARNING: Null Resource creation is prohibited. + +1 test, 0 passed, 0 warnings, 1 failure, 0 exceptions + +``` + + +#### Policy Approval Status: +``` +policy set: test_policy: requires: 1 approval(s), have: 0. +``` +* :heavy_check_mark: To **approve** this project, comment: + * `atlantis approve_policies -d .` +* :put_litter_in_its_place: To **delete** this plan click [here](lock-url) +* :repeat: To re-run policies **plan** this project again by commenting: + * `atlantis plan -d .` + +--- +* :heavy_check_mark: To **approve** all unapplied plans from this pull request, comment: + * `atlantis approve_policies` +* :put_litter_in_its_place: To delete all plans and locks for the PR, comment: + * `atlantis unlock` +* :repeat: To re-run policies **plan** this project again by commenting: + * `atlantis plan` \ No newline at end of file diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/exp-output-autoplan.txt b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/exp-output-autoplan.txt new file mode 100644 index 0000000000..ace509e6e7 --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/exp-output-autoplan.txt @@ -0,0 +1,35 @@ +Ran Plan for dir: `.` workspace: `default` + +
Show Output + +```diff +Terraform used the selected providers to generate the following execution +plan. Resource actions are indicated with the following symbols: ++ create + +Terraform will perform the following actions: + + # null_resource.simple[0] will be created ++ resource "null_resource" "simple" { + + id = (known after apply) + } + +Plan: 1 to add, 0 to change, 0 to destroy. + +Changes to Outputs: ++ workspace = "default" +``` + +* :arrow_forward: To **apply** this plan, comment: + * `atlantis apply -d .` +* :put_litter_in_its_place: To **delete** this plan click [here](lock-url) +* :repeat: To **plan** this project again, comment: + * `atlantis plan -d .` +
+Plan: 1 to add, 0 to change, 0 to destroy. + +--- +* :fast_forward: To **apply** all unapplied plans from this pull request, comment: + * `atlantis apply` +* :put_litter_in_its_place: To delete all plans and locks for the PR, comment: + * `atlantis unlock` \ No newline at end of file diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/exp-output-merge.txt b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/exp-output-merge.txt new file mode 100644 index 0000000000..872c5ee40c --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/exp-output-merge.txt @@ -0,0 +1,3 @@ +Locks and plans deleted for the projects and workspaces modified in this pull request: + +- dir: `.` workspace: `default` diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/main.tf b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/main.tf new file mode 100644 index 0000000000..582f9ea01d --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/main.tf @@ -0,0 +1,7 @@ +resource "null_resource" "simple" { + count = 1 +} + +output "workspace" { + value = terraform.workspace +} diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/policies/policy.rego b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/policies/policy.rego new file mode 100644 index 0000000000..126c2e4591 --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/policies/policy.rego @@ -0,0 +1,28 @@ +package main + +import input as tfplan + +deny[reason] { + num_deletes.null_resource > 0 + reason := "WARNING: Null Resource creation is prohibited." +} + +resource_types = {"null_resource"} + +resources[resource_type] = all { + some resource_type + resource_types[resource_type] + all := [name | + name := tfplan.resource_changes[_] + name.type == resource_type + ] +} + +# number of deletions of resources of a given type +num_deletes[resource_type] = num { + some resource_type + resource_types[resource_type] + all := resources[resource_type] + deletions := [res | res := all[_]; res.change.actions[_] == "create"] + num := count(deletions) +} diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/repos.yaml b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/repos.yaml new file mode 100644 index 0000000000..c2e2dd03dd --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo-server-side/repos.yaml @@ -0,0 +1,13 @@ +repos: + - id: /.*/ + policy_check: false + +policies: + owners: + users: + - runatlantis + policy_sets: + - name: test_policy + path: policies/policy.rego + source: local + diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/atlantis.yaml b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/atlantis.yaml new file mode 100644 index 0000000000..e4541ef872 --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/atlantis.yaml @@ -0,0 +1,5 @@ +version: 3 +projects: +- dir: . + workspace: default + policy_check: false diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/exp-output-apply-failed.txt b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/exp-output-apply-failed.txt new file mode 100644 index 0000000000..2ae26e9fe5 --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/exp-output-apply-failed.txt @@ -0,0 +1,3 @@ +Ran Apply for dir: `.` workspace: `default` + +**Apply Failed**: All policies must pass for project before running apply. \ No newline at end of file diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/exp-output-apply.txt b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/exp-output-apply.txt new file mode 100644 index 0000000000..a54764c3bd --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/exp-output-apply.txt @@ -0,0 +1,12 @@ +Ran Apply for dir: `.` workspace: `default` + +```diff +null_resource.simple: +null_resource.simple: + +Apply complete! Resources: 1 added, 0 changed, 0 destroyed. + +Outputs: + +workspace = "default" +``` \ No newline at end of file diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/exp-output-approve-policies.txt b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/exp-output-approve-policies.txt new file mode 100644 index 0000000000..f5e100c23e --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/exp-output-approve-policies.txt @@ -0,0 +1,5 @@ +Approved Policies for 1 projects: + +1. dir: `.` workspace: `default` + + diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/exp-output-auto-policy-check.txt b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/exp-output-auto-policy-check.txt new file mode 100644 index 0000000000..8faa2e036b --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/exp-output-auto-policy-check.txt @@ -0,0 +1,29 @@ +Ran Policy Check for dir: `.` workspace: `default` + +**Policy Check Failed**: Some policy sets did not pass. +#### Policy Set: `test_policy` +```diff +FAIL - - main - WARNING: Null Resource creation is prohibited. + +1 test, 0 passed, 0 warnings, 1 failure, 0 exceptions + +``` + + +#### Policy Approval Status: +``` +policy set: test_policy: requires: 1 approval(s), have: 0. +``` +* :heavy_check_mark: To **approve** this project, comment: + * `atlantis approve_policies -d .` +* :put_litter_in_its_place: To **delete** this plan click [here](lock-url) +* :repeat: To re-run policies **plan** this project again by commenting: + * `atlantis plan -d .` + +--- +* :heavy_check_mark: To **approve** all unapplied plans from this pull request, comment: + * `atlantis approve_policies` +* :put_litter_in_its_place: To delete all plans and locks for the PR, comment: + * `atlantis unlock` +* :repeat: To re-run policies **plan** this project again by commenting: + * `atlantis plan` \ No newline at end of file diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/exp-output-autoplan.txt b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/exp-output-autoplan.txt new file mode 100644 index 0000000000..ace509e6e7 --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/exp-output-autoplan.txt @@ -0,0 +1,35 @@ +Ran Plan for dir: `.` workspace: `default` + +
Show Output + +```diff +Terraform used the selected providers to generate the following execution +plan. Resource actions are indicated with the following symbols: ++ create + +Terraform will perform the following actions: + + # null_resource.simple[0] will be created ++ resource "null_resource" "simple" { + + id = (known after apply) + } + +Plan: 1 to add, 0 to change, 0 to destroy. + +Changes to Outputs: ++ workspace = "default" +``` + +* :arrow_forward: To **apply** this plan, comment: + * `atlantis apply -d .` +* :put_litter_in_its_place: To **delete** this plan click [here](lock-url) +* :repeat: To **plan** this project again, comment: + * `atlantis plan -d .` +
+Plan: 1 to add, 0 to change, 0 to destroy. + +--- +* :fast_forward: To **apply** all unapplied plans from this pull request, comment: + * `atlantis apply` +* :put_litter_in_its_place: To delete all plans and locks for the PR, comment: + * `atlantis unlock` \ No newline at end of file diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/exp-output-merge.txt b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/exp-output-merge.txt new file mode 100644 index 0000000000..872c5ee40c --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/exp-output-merge.txt @@ -0,0 +1,3 @@ +Locks and plans deleted for the projects and workspaces modified in this pull request: + +- dir: `.` workspace: `default` diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/main.tf b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/main.tf new file mode 100644 index 0000000000..582f9ea01d --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/main.tf @@ -0,0 +1,7 @@ +resource "null_resource" "simple" { + count = 1 +} + +output "workspace" { + value = terraform.workspace +} diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/policies/policy.rego b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/policies/policy.rego new file mode 100644 index 0000000000..126c2e4591 --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/policies/policy.rego @@ -0,0 +1,28 @@ +package main + +import input as tfplan + +deny[reason] { + num_deletes.null_resource > 0 + reason := "WARNING: Null Resource creation is prohibited." +} + +resource_types = {"null_resource"} + +resources[resource_type] = all { + some resource_type + resource_types[resource_type] + all := [name | + name := tfplan.resource_changes[_] + name.type == resource_type + ] +} + +# number of deletions of resources of a given type +num_deletes[resource_type] = num { + some resource_type + resource_types[resource_type] + all := resources[resource_type] + deletions := [res | res := all[_]; res.change.actions[_] == "create"] + num := count(deletions) +} diff --git a/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/repos.yaml b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/repos.yaml new file mode 100644 index 0000000000..a5fa0cb9e2 --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-disabled-repo/repos.yaml @@ -0,0 +1,9 @@ +policies: + owners: + users: + - runatlantis + policy_sets: + - name: test_policy + path: policies/policy.rego + source: local + diff --git a/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/atlantis.yaml b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/atlantis.yaml new file mode 100644 index 0000000000..8435733cd2 --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/atlantis.yaml @@ -0,0 +1,4 @@ +version: 3 +projects: +- dir: . + workspace: default diff --git a/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/exp-output-apply-failed.txt b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/exp-output-apply-failed.txt new file mode 100644 index 0000000000..2ae26e9fe5 --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/exp-output-apply-failed.txt @@ -0,0 +1,3 @@ +Ran Apply for dir: `.` workspace: `default` + +**Apply Failed**: All policies must pass for project before running apply. \ No newline at end of file diff --git a/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/exp-output-apply.txt b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/exp-output-apply.txt new file mode 100644 index 0000000000..a54764c3bd --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/exp-output-apply.txt @@ -0,0 +1,12 @@ +Ran Apply for dir: `.` workspace: `default` + +```diff +null_resource.simple: +null_resource.simple: + +Apply complete! Resources: 1 added, 0 changed, 0 destroyed. + +Outputs: + +workspace = "default" +``` \ No newline at end of file diff --git a/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/exp-output-approve-policies.txt b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/exp-output-approve-policies.txt new file mode 100644 index 0000000000..f5e100c23e --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/exp-output-approve-policies.txt @@ -0,0 +1,5 @@ +Approved Policies for 1 projects: + +1. dir: `.` workspace: `default` + + diff --git a/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/exp-output-auto-policy-check.txt b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/exp-output-auto-policy-check.txt new file mode 100644 index 0000000000..8faa2e036b --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/exp-output-auto-policy-check.txt @@ -0,0 +1,29 @@ +Ran Policy Check for dir: `.` workspace: `default` + +**Policy Check Failed**: Some policy sets did not pass. +#### Policy Set: `test_policy` +```diff +FAIL - - main - WARNING: Null Resource creation is prohibited. + +1 test, 0 passed, 0 warnings, 1 failure, 0 exceptions + +``` + + +#### Policy Approval Status: +``` +policy set: test_policy: requires: 1 approval(s), have: 0. +``` +* :heavy_check_mark: To **approve** this project, comment: + * `atlantis approve_policies -d .` +* :put_litter_in_its_place: To **delete** this plan click [here](lock-url) +* :repeat: To re-run policies **plan** this project again by commenting: + * `atlantis plan -d .` + +--- +* :heavy_check_mark: To **approve** all unapplied plans from this pull request, comment: + * `atlantis approve_policies` +* :put_litter_in_its_place: To delete all plans and locks for the PR, comment: + * `atlantis unlock` +* :repeat: To re-run policies **plan** this project again by commenting: + * `atlantis plan` \ No newline at end of file diff --git a/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/exp-output-autoplan.txt b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/exp-output-autoplan.txt new file mode 100644 index 0000000000..ace509e6e7 --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/exp-output-autoplan.txt @@ -0,0 +1,35 @@ +Ran Plan for dir: `.` workspace: `default` + +
Show Output + +```diff +Terraform used the selected providers to generate the following execution +plan. Resource actions are indicated with the following symbols: ++ create + +Terraform will perform the following actions: + + # null_resource.simple[0] will be created ++ resource "null_resource" "simple" { + + id = (known after apply) + } + +Plan: 1 to add, 0 to change, 0 to destroy. + +Changes to Outputs: ++ workspace = "default" +``` + +* :arrow_forward: To **apply** this plan, comment: + * `atlantis apply -d .` +* :put_litter_in_its_place: To **delete** this plan click [here](lock-url) +* :repeat: To **plan** this project again, comment: + * `atlantis plan -d .` +
+Plan: 1 to add, 0 to change, 0 to destroy. + +--- +* :fast_forward: To **apply** all unapplied plans from this pull request, comment: + * `atlantis apply` +* :put_litter_in_its_place: To delete all plans and locks for the PR, comment: + * `atlantis unlock` \ No newline at end of file diff --git a/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/exp-output-merge.txt b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/exp-output-merge.txt new file mode 100644 index 0000000000..872c5ee40c --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/exp-output-merge.txt @@ -0,0 +1,3 @@ +Locks and plans deleted for the projects and workspaces modified in this pull request: + +- dir: `.` workspace: `default` diff --git a/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/main.tf b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/main.tf new file mode 100644 index 0000000000..582f9ea01d --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/main.tf @@ -0,0 +1,7 @@ +resource "null_resource" "simple" { + count = 1 +} + +output "workspace" { + value = terraform.workspace +} diff --git a/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/policies/policy.rego b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/policies/policy.rego new file mode 100644 index 0000000000..126c2e4591 --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/policies/policy.rego @@ -0,0 +1,28 @@ +package main + +import input as tfplan + +deny[reason] { + num_deletes.null_resource > 0 + reason := "WARNING: Null Resource creation is prohibited." +} + +resource_types = {"null_resource"} + +resources[resource_type] = all { + some resource_type + resource_types[resource_type] + all := [name | + name := tfplan.resource_changes[_] + name.type == resource_type + ] +} + +# number of deletions of resources of a given type +num_deletes[resource_type] = num { + some resource_type + resource_types[resource_type] + all := resources[resource_type] + deletions := [res | res := all[_]; res.change.actions[_] == "create"] + num := count(deletions) +} diff --git a/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/repos.yaml b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/repos.yaml new file mode 100644 index 0000000000..2053ab2548 --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo-server-side/repos.yaml @@ -0,0 +1,13 @@ +repos: + - id: /.*/ + policy_check: true + +policies: + owners: + users: + - runatlantis + policy_sets: + - name: test_policy + path: policies/policy.rego + source: local + diff --git a/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/atlantis.yaml b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/atlantis.yaml new file mode 100644 index 0000000000..42d53db22d --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/atlantis.yaml @@ -0,0 +1,5 @@ +version: 3 +projects: +- dir: . + workspace: default + policy_check: true diff --git a/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/exp-output-apply-failed.txt b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/exp-output-apply-failed.txt new file mode 100644 index 0000000000..2ae26e9fe5 --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/exp-output-apply-failed.txt @@ -0,0 +1,3 @@ +Ran Apply for dir: `.` workspace: `default` + +**Apply Failed**: All policies must pass for project before running apply. \ No newline at end of file diff --git a/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/exp-output-apply.txt b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/exp-output-apply.txt new file mode 100644 index 0000000000..a54764c3bd --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/exp-output-apply.txt @@ -0,0 +1,12 @@ +Ran Apply for dir: `.` workspace: `default` + +```diff +null_resource.simple: +null_resource.simple: + +Apply complete! Resources: 1 added, 0 changed, 0 destroyed. + +Outputs: + +workspace = "default" +``` \ No newline at end of file diff --git a/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/exp-output-approve-policies.txt b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/exp-output-approve-policies.txt new file mode 100644 index 0000000000..f5e100c23e --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/exp-output-approve-policies.txt @@ -0,0 +1,5 @@ +Approved Policies for 1 projects: + +1. dir: `.` workspace: `default` + + diff --git a/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/exp-output-auto-policy-check.txt b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/exp-output-auto-policy-check.txt new file mode 100644 index 0000000000..8faa2e036b --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/exp-output-auto-policy-check.txt @@ -0,0 +1,29 @@ +Ran Policy Check for dir: `.` workspace: `default` + +**Policy Check Failed**: Some policy sets did not pass. +#### Policy Set: `test_policy` +```diff +FAIL - - main - WARNING: Null Resource creation is prohibited. + +1 test, 0 passed, 0 warnings, 1 failure, 0 exceptions + +``` + + +#### Policy Approval Status: +``` +policy set: test_policy: requires: 1 approval(s), have: 0. +``` +* :heavy_check_mark: To **approve** this project, comment: + * `atlantis approve_policies -d .` +* :put_litter_in_its_place: To **delete** this plan click [here](lock-url) +* :repeat: To re-run policies **plan** this project again by commenting: + * `atlantis plan -d .` + +--- +* :heavy_check_mark: To **approve** all unapplied plans from this pull request, comment: + * `atlantis approve_policies` +* :put_litter_in_its_place: To delete all plans and locks for the PR, comment: + * `atlantis unlock` +* :repeat: To re-run policies **plan** this project again by commenting: + * `atlantis plan` \ No newline at end of file diff --git a/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/exp-output-autoplan.txt b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/exp-output-autoplan.txt new file mode 100644 index 0000000000..ace509e6e7 --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/exp-output-autoplan.txt @@ -0,0 +1,35 @@ +Ran Plan for dir: `.` workspace: `default` + +
Show Output + +```diff +Terraform used the selected providers to generate the following execution +plan. Resource actions are indicated with the following symbols: ++ create + +Terraform will perform the following actions: + + # null_resource.simple[0] will be created ++ resource "null_resource" "simple" { + + id = (known after apply) + } + +Plan: 1 to add, 0 to change, 0 to destroy. + +Changes to Outputs: ++ workspace = "default" +``` + +* :arrow_forward: To **apply** this plan, comment: + * `atlantis apply -d .` +* :put_litter_in_its_place: To **delete** this plan click [here](lock-url) +* :repeat: To **plan** this project again, comment: + * `atlantis plan -d .` +
+Plan: 1 to add, 0 to change, 0 to destroy. + +--- +* :fast_forward: To **apply** all unapplied plans from this pull request, comment: + * `atlantis apply` +* :put_litter_in_its_place: To delete all plans and locks for the PR, comment: + * `atlantis unlock` \ No newline at end of file diff --git a/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/exp-output-merge.txt b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/exp-output-merge.txt new file mode 100644 index 0000000000..872c5ee40c --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/exp-output-merge.txt @@ -0,0 +1,3 @@ +Locks and plans deleted for the projects and workspaces modified in this pull request: + +- dir: `.` workspace: `default` diff --git a/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/main.tf b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/main.tf new file mode 100644 index 0000000000..582f9ea01d --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/main.tf @@ -0,0 +1,7 @@ +resource "null_resource" "simple" { + count = 1 +} + +output "workspace" { + value = terraform.workspace +} diff --git a/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/policies/policy.rego b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/policies/policy.rego new file mode 100644 index 0000000000..126c2e4591 --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/policies/policy.rego @@ -0,0 +1,28 @@ +package main + +import input as tfplan + +deny[reason] { + num_deletes.null_resource > 0 + reason := "WARNING: Null Resource creation is prohibited." +} + +resource_types = {"null_resource"} + +resources[resource_type] = all { + some resource_type + resource_types[resource_type] + all := [name | + name := tfplan.resource_changes[_] + name.type == resource_type + ] +} + +# number of deletions of resources of a given type +num_deletes[resource_type] = num { + some resource_type + resource_types[resource_type] + all := resources[resource_type] + deletions := [res | res := all[_]; res.change.actions[_] == "create"] + num := count(deletions) +} diff --git a/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/repos.yaml b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/repos.yaml new file mode 100644 index 0000000000..a5fa0cb9e2 --- /dev/null +++ b/server/controllers/events/testdata/test-repos/policy-checks-enabled-repo/repos.yaml @@ -0,0 +1,9 @@ +policies: + owners: + users: + - runatlantis + policy_sets: + - name: test_policy + path: policies/policy.rego + source: local + diff --git a/server/core/config/parser_validator_test.go b/server/core/config/parser_validator_test.go index d041076f11..5959a7d320 100644 --- a/server/core/config/parser_validator_test.go +++ b/server/core/config/parser_validator_test.go @@ -1308,7 +1308,7 @@ func TestParseGlobalCfg(t *testing.T) { input: `repos: - id: /.*/ allowed_overrides: [invalid]`, - expErr: "repos: (0: (allowed_overrides: \"invalid\" is not a valid override, only \"plan_requirements\", \"apply_requirements\", \"import_requirements\", \"workflow\", \"delete_source_branch_on_merge\" and \"repo_locking\" are supported.).).", + expErr: "repos: (0: (allowed_overrides: \"invalid\" is not a valid override, only \"plan_requirements\", \"apply_requirements\", \"import_requirements\", \"workflow\", \"delete_source_branch_on_merge\", \"repo_locking\" and \"policy_check\" are supported.).).", }, "invalid plan_requirement": { input: `repos: @@ -1402,12 +1402,14 @@ repos: - run: custom workflow command allowed_overrides: [plan_requirements, apply_requirements, import_requirements, workflow, delete_source_branch_on_merge] allow_custom_workflows: true + policy_check: true - id: /.*/ branch: /(master|main)/ pre_workflow_hooks: - run: custom workflow command post_workflow_hooks: - run: custom workflow command + policy_check: false workflows: custom1: plan: @@ -1453,12 +1455,14 @@ policies: PostWorkflowHooks: postWorkflowHooks, AllowedOverrides: []string{"plan_requirements", "apply_requirements", "import_requirements", "workflow", "delete_source_branch_on_merge"}, AllowCustomWorkflows: Bool(true), + PolicyCheck: Bool(true), }, { IDRegex: regexp.MustCompile(".*"), BranchRegex: regexp.MustCompile("(master|main)"), PreWorkflowHooks: preWorkflowHooks, PostWorkflowHooks: postWorkflowHooks, + PolicyCheck: Bool(false), }, }, Workflows: map[string]valid.Workflow{ @@ -1567,6 +1571,7 @@ workflows: AllowCustomWorkflows: Bool(false), DeleteSourceBranchOnMerge: Bool(false), RepoLocking: Bool(true), + PolicyCheck: Bool(false), }, }, Workflows: map[string]valid.Workflow{ @@ -1597,10 +1602,11 @@ workflows: Ok(t, os.WriteFile(path, []byte(c.input), 0600)) globalCfgArgs := valid.GlobalCfgArgs{ - AllowRepoCfg: false, - MergeableReq: false, - ApprovedReq: false, - UnDivergedReq: false, + AllowRepoCfg: false, + MergeableReq: false, + ApprovedReq: false, + UnDivergedReq: false, + PolicyCheckEnabled: false, } act, err := r.ParseGlobalCfg(path, valid.NewGlobalCfgFromArgs(globalCfgArgs)) diff --git a/server/core/config/raw/global_cfg.go b/server/core/config/raw/global_cfg.go index d09361fea1..9909b6ed59 100644 --- a/server/core/config/raw/global_cfg.go +++ b/server/core/config/raw/global_cfg.go @@ -34,6 +34,7 @@ type Repo struct { AllowCustomWorkflows *bool `yaml:"allow_custom_workflows,omitempty" json:"allow_custom_workflows,omitempty"` DeleteSourceBranchOnMerge *bool `yaml:"delete_source_branch_on_merge,omitempty" json:"delete_source_branch_on_merge,omitempty"` RepoLocking *bool `yaml:"repo_locking,omitempty" json:"repo_locking,omitempty"` + PolicyCheck *bool `yaml:"policy_check,omitempty" json:"policy_check,omitempty"` } func (g GlobalCfg) Validate() error { @@ -191,8 +192,8 @@ func (r Repo) Validate() error { overridesValid := func(value interface{}) error { overrides := value.([]string) for _, o := range overrides { - if o != valid.PlanRequirementsKey && o != valid.ApplyRequirementsKey && o != valid.ImportRequirementsKey && o != valid.WorkflowKey && o != valid.DeleteSourceBranchOnMergeKey && o != valid.RepoLockingKey { - return fmt.Errorf("%q is not a valid override, only %q, %q, %q, %q, %q and %q are supported", o, valid.PlanRequirementsKey, valid.ApplyRequirementsKey, valid.ImportRequirementsKey, valid.WorkflowKey, valid.DeleteSourceBranchOnMergeKey, valid.RepoLockingKey) + if o != valid.PlanRequirementsKey && o != valid.ApplyRequirementsKey && o != valid.ImportRequirementsKey && o != valid.WorkflowKey && o != valid.DeleteSourceBranchOnMergeKey && o != valid.RepoLockingKey && o != valid.PolicyCheckKey { + return fmt.Errorf("%q is not a valid override, only %q, %q, %q, %q, %q, %q and %q are supported", o, valid.PlanRequirementsKey, valid.ApplyRequirementsKey, valid.ImportRequirementsKey, valid.WorkflowKey, valid.DeleteSourceBranchOnMergeKey, valid.RepoLockingKey, valid.PolicyCheckKey) } } return nil @@ -277,6 +278,11 @@ OuterGlobalPlanReqs: continue OuterGlobalPlanReqs } } + + // dont add policy_check step if repo have it explicitly disabled + if globalReq == valid.PoliciesPassedCommandReq && r.PolicyCheck != nil && *r.PolicyCheck == false { + continue + } mergedPlanReqs = append(mergedPlanReqs, globalReq) } OuterGlobalApplyReqs: @@ -286,6 +292,11 @@ OuterGlobalApplyReqs: continue OuterGlobalApplyReqs } } + + // dont add policy_check step if repo have it explicitly disabled + if globalReq == valid.PoliciesPassedCommandReq && r.PolicyCheck != nil && *r.PolicyCheck == false { + continue + } mergedApplyReqs = append(mergedApplyReqs, globalReq) } OuterGlobalImportReqs: @@ -295,6 +306,11 @@ OuterGlobalImportReqs: continue OuterGlobalImportReqs } } + + // dont add policy_check step if repo have it explicitly disabled + if globalReq == valid.PoliciesPassedCommandReq && r.PolicyCheck != nil && *r.PolicyCheck == false { + continue + } mergedImportReqs = append(mergedImportReqs, globalReq) } @@ -314,5 +330,6 @@ OuterGlobalImportReqs: AllowCustomWorkflows: r.AllowCustomWorkflows, DeleteSourceBranchOnMerge: r.DeleteSourceBranchOnMerge, RepoLocking: r.RepoLocking, + PolicyCheck: r.PolicyCheck, } } diff --git a/server/core/config/raw/project.go b/server/core/config/raw/project.go index 1875975429..12eb6a2b33 100644 --- a/server/core/config/raw/project.go +++ b/server/core/config/raw/project.go @@ -34,6 +34,7 @@ type Project struct { DeleteSourceBranchOnMerge *bool `yaml:"delete_source_branch_on_merge,omitempty"` RepoLocking *bool `yaml:"repo_locking,omitempty"` ExecutionOrderGroup *int `yaml:"execution_order_group,omitempty"` + PolicyCheck *bool `yaml:"policy_check,omitempty"` } func (p Project) Validate() error { @@ -133,6 +134,10 @@ func (p Project) ToValid() valid.Project { v.ExecutionOrderGroup = *p.ExecutionOrderGroup } + if p.PolicyCheck != nil { + v.PolicyCheck = p.PolicyCheck + } + return v } diff --git a/server/core/config/valid/global_cfg.go b/server/core/config/valid/global_cfg.go index 2a327b0160..614c71d7e0 100644 --- a/server/core/config/valid/global_cfg.go +++ b/server/core/config/valid/global_cfg.go @@ -26,6 +26,7 @@ const AllowCustomWorkflowsKey = "allow_custom_workflows" const DefaultWorkflowName = "default" const DeleteSourceBranchOnMergeKey = "delete_source_branch_on_merge" const RepoLockingKey = "repo_locking" +const PolicyCheckKey = "policy_check" // DefaultAtlantisFile is the default name of the config file for each repo. const DefaultAtlantisFile = "atlantis.yaml" @@ -80,6 +81,7 @@ type Repo struct { AllowCustomWorkflows *bool DeleteSourceBranchOnMerge *bool RepoLocking *bool + PolicyCheck *bool } type MergedProjectCfg struct { @@ -99,6 +101,7 @@ type MergedProjectCfg struct { DeleteSourceBranchOnMerge bool ExecutionOrderGroup int RepoLocking bool + PolicyCheck bool } // WorkflowHook is a map of custom run commands to run before or after workflows. @@ -217,6 +220,7 @@ func NewGlobalCfgFromArgs(args GlobalCfgArgs) GlobalCfg { commandReqs := []string{} allowedOverrides := []string{} allowedWorkflows := []string{} + policyCheck := false if args.MergeableReq { commandReqs = append(commandReqs, MergeableCommandReq) } @@ -228,13 +232,14 @@ func NewGlobalCfgFromArgs(args GlobalCfgArgs) GlobalCfg { } if args.PolicyCheckEnabled { commandReqs = append(commandReqs, PoliciesPassedCommandReq) + policyCheck = true } allowCustomWorkflows := false deleteSourceBranchOnMerge := false repoLockingKey := true if args.AllowRepoCfg { - allowedOverrides = []string{PlanRequirementsKey, ApplyRequirementsKey, ImportRequirementsKey, WorkflowKey, DeleteSourceBranchOnMergeKey, RepoLockingKey} + allowedOverrides = []string{PlanRequirementsKey, ApplyRequirementsKey, ImportRequirementsKey, WorkflowKey, DeleteSourceBranchOnMergeKey, RepoLockingKey, PolicyCheckKey} allowCustomWorkflows = true } @@ -255,6 +260,7 @@ func NewGlobalCfgFromArgs(args GlobalCfgArgs) GlobalCfg { AllowCustomWorkflows: &allowCustomWorkflows, DeleteSourceBranchOnMerge: &deleteSourceBranchOnMerge, RepoLocking: &repoLockingKey, + PolicyCheck: &policyCheck, }, }, Workflows: map[string]Workflow{ @@ -291,7 +297,7 @@ func (r Repo) IDString() string { // final config. It assumes that all configs have been validated. func (g GlobalCfg) MergeProjectCfg(log logging.SimpleLogging, repoID string, proj Project, rCfg RepoCfg) MergedProjectCfg { log.Debug("MergeProjectCfg started") - planReqs, applyReqs, importReqs, workflow, allowedOverrides, allowCustomWorkflows, deleteSourceBranchOnMerge, repoLocking := g.getMatchingCfg(log, repoID) + planReqs, applyReqs, importReqs, workflow, allowedOverrides, allowCustomWorkflows, deleteSourceBranchOnMerge, repoLocking, policyCheck := g.getMatchingCfg(log, repoID) // If repos are allowed to override certain keys then override them. for _, key := range allowedOverrides { @@ -352,6 +358,11 @@ func (g GlobalCfg) MergeProjectCfg(log logging.SimpleLogging, repoID string, pro log.Debug("overriding server-defined %s with repo settings: [%t]", RepoLockingKey, *proj.RepoLocking) repoLocking = *proj.RepoLocking } + case PolicyCheckKey: + if proj.PolicyCheck != nil { + log.Debug("overriding server-defined %s with repo settings: [%t]", PolicyCheckKey, *proj.PolicyCheck) + policyCheck = *proj.PolicyCheck + } } log.Debug("MergeProjectCfg completed") } @@ -374,6 +385,7 @@ func (g GlobalCfg) MergeProjectCfg(log logging.SimpleLogging, repoID string, pro DeleteSourceBranchOnMerge: deleteSourceBranchOnMerge, ExecutionOrderGroup: proj.ExecutionOrderGroup, RepoLocking: repoLocking, + PolicyCheck: policyCheck, } } @@ -381,7 +393,7 @@ func (g GlobalCfg) MergeProjectCfg(log logging.SimpleLogging, repoID string, pro // repo with id repoID. It is used when there is no repo config. func (g GlobalCfg) DefaultProjCfg(log logging.SimpleLogging, repoID string, repoRelDir string, workspace string) MergedProjectCfg { log.Debug("building config based on server-side config") - planReqs, applyReqs, importReqs, workflow, _, _, deleteSourceBranchOnMerge, repoLocking := g.getMatchingCfg(log, repoID) + planReqs, applyReqs, importReqs, workflow, _, _, deleteSourceBranchOnMerge, repoLocking, policyCheck := g.getMatchingCfg(log, repoID) return MergedProjectCfg{ PlanRequirements: planReqs, ApplyRequirements: applyReqs, @@ -395,6 +407,7 @@ func (g GlobalCfg) DefaultProjCfg(log logging.SimpleLogging, repoID string, repo PolicySets: g.PolicySets, DeleteSourceBranchOnMerge: deleteSourceBranchOnMerge, RepoLocking: repoLocking, + PolicyCheck: policyCheck, } } @@ -497,7 +510,7 @@ func (g GlobalCfg) ValidateRepoCfg(rCfg RepoCfg, repoID string) error { } // getMatchingCfg returns the key settings for repoID. -func (g GlobalCfg) getMatchingCfg(log logging.SimpleLogging, repoID string) (planReqs []string, applyReqs []string, importReqs []string, workflow Workflow, allowedOverrides []string, allowCustomWorkflows bool, deleteSourceBranchOnMerge bool, repoLocking bool) { +func (g GlobalCfg) getMatchingCfg(log logging.SimpleLogging, repoID string) (planReqs []string, applyReqs []string, importReqs []string, workflow Workflow, allowedOverrides []string, allowCustomWorkflows bool, deleteSourceBranchOnMerge bool, repoLocking bool, policyCheck bool) { toLog := make(map[string]string) traceF := func(repoIdx int, repoID string, key string, val interface{}) string { from := "default server config" @@ -519,7 +532,7 @@ func (g GlobalCfg) getMatchingCfg(log logging.SimpleLogging, repoID string) (pla return fmt.Sprintf("setting %s: %s from %s", key, valStr, from) } - for _, key := range []string{PlanRequirementsKey, ApplyRequirementsKey, ImportRequirementsKey, WorkflowKey, AllowedOverridesKey, AllowCustomWorkflowsKey, DeleteSourceBranchOnMergeKey, RepoLockingKey} { + for _, key := range []string{PlanRequirementsKey, ApplyRequirementsKey, ImportRequirementsKey, WorkflowKey, AllowedOverridesKey, AllowCustomWorkflowsKey, DeleteSourceBranchOnMergeKey, RepoLockingKey, PolicyCheckKey} { for i, repo := range g.Repos { if repo.IDMatches(repoID) { switch key { @@ -563,6 +576,11 @@ func (g GlobalCfg) getMatchingCfg(log logging.SimpleLogging, repoID string) (pla toLog[RepoLockingKey] = traceF(i, repo.IDString(), RepoLockingKey, *repo.RepoLocking) repoLocking = *repo.RepoLocking } + case PolicyCheckKey: + if repo.PolicyCheck != nil { + toLog[PolicyCheckKey] = traceF(i, repo.IDString(), PolicyCheckKey, *repo.PolicyCheck) + policyCheck = *repo.PolicyCheck + } } } } diff --git a/server/core/config/valid/global_cfg_test.go b/server/core/config/valid/global_cfg_test.go index 818c744c62..1efa610f8f 100644 --- a/server/core/config/valid/global_cfg_test.go +++ b/server/core/config/valid/global_cfg_test.go @@ -80,6 +80,7 @@ func TestNewGlobalCfg(t *testing.T) { AllowCustomWorkflows: Bool(false), DeleteSourceBranchOnMerge: Bool(false), RepoLocking: Bool(true), + PolicyCheck: Bool(false), }, }, Workflows: map[string]valid.Workflow{ @@ -88,94 +89,115 @@ func TestNewGlobalCfg(t *testing.T) { } cases := []struct { - allowRepoCfg bool - approvedReq bool - mergeableReq bool - unDivergedReq bool + allowRepoCfg bool + approvedReq bool + mergeableReq bool + unDivergedReq bool + policyCheckEnabled bool }{ { - allowRepoCfg: false, - approvedReq: false, - mergeableReq: false, - unDivergedReq: false, + allowRepoCfg: false, + approvedReq: false, + mergeableReq: false, + unDivergedReq: false, + policyCheckEnabled: false, }, { - allowRepoCfg: true, - approvedReq: false, - mergeableReq: false, - unDivergedReq: false, + allowRepoCfg: true, + approvedReq: false, + mergeableReq: false, + unDivergedReq: false, + policyCheckEnabled: false, }, { - allowRepoCfg: false, - approvedReq: true, - mergeableReq: false, - unDivergedReq: false, + allowRepoCfg: false, + approvedReq: true, + mergeableReq: false, + unDivergedReq: false, + policyCheckEnabled: false, }, { - allowRepoCfg: false, - approvedReq: false, - mergeableReq: true, - unDivergedReq: false, + allowRepoCfg: false, + approvedReq: false, + mergeableReq: true, + unDivergedReq: false, + policyCheckEnabled: false, }, { - allowRepoCfg: false, - approvedReq: true, - mergeableReq: true, - unDivergedReq: false, + allowRepoCfg: false, + approvedReq: true, + mergeableReq: true, + unDivergedReq: false, + policyCheckEnabled: false, }, { - allowRepoCfg: true, - approvedReq: true, - mergeableReq: true, - unDivergedReq: false, + allowRepoCfg: true, + approvedReq: true, + mergeableReq: true, + unDivergedReq: false, + policyCheckEnabled: false, }, { - allowRepoCfg: false, - approvedReq: false, - mergeableReq: false, - unDivergedReq: true, + allowRepoCfg: false, + approvedReq: false, + mergeableReq: false, + unDivergedReq: true, + policyCheckEnabled: false, }, { - allowRepoCfg: true, - approvedReq: false, - mergeableReq: false, - unDivergedReq: true, + allowRepoCfg: true, + approvedReq: false, + mergeableReq: false, + unDivergedReq: true, + policyCheckEnabled: false, }, { - allowRepoCfg: false, - approvedReq: true, - mergeableReq: false, - unDivergedReq: true, + allowRepoCfg: false, + approvedReq: true, + mergeableReq: false, + unDivergedReq: true, + policyCheckEnabled: false, }, { - allowRepoCfg: false, - approvedReq: false, - mergeableReq: true, - unDivergedReq: true, + allowRepoCfg: false, + approvedReq: false, + mergeableReq: true, + unDivergedReq: true, + policyCheckEnabled: false, }, { - allowRepoCfg: false, - approvedReq: true, - mergeableReq: true, - unDivergedReq: true, + allowRepoCfg: false, + approvedReq: true, + mergeableReq: true, + unDivergedReq: true, + policyCheckEnabled: false, }, { - allowRepoCfg: true, - approvedReq: true, - mergeableReq: true, - unDivergedReq: true, + allowRepoCfg: true, + approvedReq: true, + mergeableReq: true, + unDivergedReq: true, + policyCheckEnabled: false, + }, + { + allowRepoCfg: true, + approvedReq: true, + mergeableReq: true, + unDivergedReq: true, + policyCheckEnabled: true, }, } for _, c := range cases { - caseName := fmt.Sprintf("allow_repo: %t, approved: %t, mergeable: %t, undiverged: %t", - c.allowRepoCfg, c.approvedReq, c.mergeableReq, c.unDivergedReq) + caseName := fmt.Sprintf("allow_repo: %t, approved: %t, mergeable: %t, undiverged: %t, policy_check: %t", + c.allowRepoCfg, c.approvedReq, c.mergeableReq, c.unDivergedReq, c.policyCheckEnabled) t.Run(caseName, func(t *testing.T) { globalCfgArgs := valid.GlobalCfgArgs{ - AllowRepoCfg: c.allowRepoCfg, - MergeableReq: c.mergeableReq, - ApprovedReq: c.approvedReq, - UnDivergedReq: c.unDivergedReq, + AllowRepoCfg: c.allowRepoCfg, + MergeableReq: c.mergeableReq, + ApprovedReq: c.approvedReq, + UnDivergedReq: c.unDivergedReq, + PolicyCheckEnabled: c.policyCheckEnabled, } act := valid.NewGlobalCfgFromArgs(globalCfgArgs) @@ -186,7 +208,7 @@ func TestNewGlobalCfg(t *testing.T) { if c.allowRepoCfg { exp.Repos[0].AllowCustomWorkflows = Bool(true) - exp.Repos[0].AllowedOverrides = []string{"plan_requirements", "apply_requirements", "import_requirements", "workflow", "delete_source_branch_on_merge", "repo_locking"} + exp.Repos[0].AllowedOverrides = []string{"plan_requirements", "apply_requirements", "import_requirements", "workflow", "delete_source_branch_on_merge", "repo_locking", "policy_check"} } if c.mergeableReq { exp.Repos[0].PlanRequirements = append(exp.Repos[0].PlanRequirements, "mergeable") @@ -203,6 +225,12 @@ func TestNewGlobalCfg(t *testing.T) { exp.Repos[0].ApplyRequirements = append(exp.Repos[0].ApplyRequirements, "undiverged") exp.Repos[0].ImportRequirements = append(exp.Repos[0].ImportRequirements, "undiverged") } + if c.policyCheckEnabled { + exp.Repos[0].PlanRequirements = append(exp.Repos[0].PlanRequirements, "policies_passed") + exp.Repos[0].ApplyRequirements = append(exp.Repos[0].ApplyRequirements, "policies_passed") + exp.Repos[0].ImportRequirements = append(exp.Repos[0].ImportRequirements, "policies_passed") + exp.Repos[0].PolicyCheck = Bool(true) + } Equals(t, exp, act) @@ -1170,6 +1198,248 @@ func TestGlobalCfg_MatchingRepo(t *testing.T) { } } +func TestGlobalCfg_PolicyCheckOverride(t *testing.T) { + var emptyPolicySets valid.PolicySets + + defaultWorkflow := valid.Workflow{ + Name: "default", + Apply: valid.DefaultApplyStage, + PolicyCheck: valid.DefaultPolicyCheckStage, + Plan: valid.DefaultPlanStage, + Import: valid.DefaultImportStage, + StateRm: valid.DefaultStateRmStage, + } + cases := map[string]struct { + gPolicyCheck bool + gCfg string + repoID string + proj valid.Project + repoWorkflows map[string]valid.Workflow + exp valid.MergedProjectCfg + }{ + "global policy check disabled": { + gPolicyCheck: false, + gCfg: ` +repos: +- id: /.*/ + plan_requirements: [approved] + apply_requirements: [approved] + import_requirements: [approved] +- id: /github.com/.*/ + plan_requirements: [mergeable] + apply_requirements: [mergeable] + import_requirements: [mergeable] +- id: github.com/owner/repo + plan_requirements: [approved, mergeable] + apply_requirements: [approved, mergeable] + import_requirements: [approved, mergeable] +`, + repoID: "github.com/owner/repo", + proj: valid.Project{ + Dir: "mydir", + Workspace: "myworkspace", + Name: String("myname"), + PolicyCheck: Bool(false), + }, + repoWorkflows: nil, + exp: valid.MergedProjectCfg{ + PlanRequirements: []string{"approved", "mergeable"}, + ApplyRequirements: []string{"approved", "mergeable"}, + ImportRequirements: []string{"approved", "mergeable"}, + Workflow: defaultWorkflow, + RepoRelDir: "mydir", + Workspace: "myworkspace", + Name: "myname", + AutoplanEnabled: false, + PolicySets: emptyPolicySets, + RepoLocking: true, + PolicyCheck: false, + }, + }, + "global policy check enabled": { + gPolicyCheck: true, + gCfg: ` +repos: +- id: /.*/ + plan_requirements: [approved] + apply_requirements: [approved] + import_requirements: [approved] +- id: /github.com/.*/ + plan_requirements: [mergeable] + apply_requirements: [mergeable] + import_requirements: [mergeable] +- id: github.com/owner/repo + plan_requirements: [approved, mergeable] + apply_requirements: [approved, mergeable] + import_requirements: [approved, mergeable] +`, + repoID: "github.com/owner/repo", + proj: valid.Project{ + Dir: "mydir", + Workspace: "myworkspace", + Name: String("myname"), + PolicyCheck: Bool(true), + }, + repoWorkflows: nil, + exp: valid.MergedProjectCfg{ + PlanRequirements: []string{"approved", "mergeable", "policies_passed"}, + ApplyRequirements: []string{"approved", "mergeable", "policies_passed"}, + ImportRequirements: []string{"approved", "mergeable", "policies_passed"}, + Workflow: defaultWorkflow, + RepoRelDir: "mydir", + Workspace: "myworkspace", + Name: "myname", + AutoplanEnabled: false, + PolicySets: emptyPolicySets, + RepoLocking: true, + PolicyCheck: true, + }, + }, + "global policy check enabled except current repo": { + gPolicyCheck: true, + gCfg: ` +repos: +- id: /.*/ + plan_requirements: [approved] + apply_requirements: [approved] + import_requirements: [approved] +- id: /github.com/.*/ + plan_requirements: [mergeable] + apply_requirements: [mergeable] + import_requirements: [mergeable] +- id: github.com/owner/repo + plan_requirements: [approved, mergeable] + apply_requirements: [approved, mergeable] + import_requirements: [approved, mergeable] + policy_check: false +`, + repoID: "github.com/owner/repo", + proj: valid.Project{ + Dir: "mydir", + Workspace: "myworkspace", + Name: String("myname"), + PolicyCheck: Bool(false), + }, + repoWorkflows: nil, + exp: valid.MergedProjectCfg{ + PlanRequirements: []string{"approved", "mergeable"}, + ApplyRequirements: []string{"approved", "mergeable"}, + ImportRequirements: []string{"approved", "mergeable"}, + Workflow: defaultWorkflow, + RepoRelDir: "mydir", + Workspace: "myworkspace", + Name: "myname", + AutoplanEnabled: false, + PolicySets: emptyPolicySets, + RepoLocking: true, + PolicyCheck: false, + }, + }, + "global policy check disabled and disabled on current repo": { + gPolicyCheck: false, + gCfg: ` +repos: +- id: /.*/ + plan_requirements: [approved] + apply_requirements: [approved] + import_requirements: [approved] +- id: /github.com/.*/ + plan_requirements: [mergeable] + apply_requirements: [mergeable] + import_requirements: [mergeable] +- id: github.com/owner/repo + plan_requirements: [approved, mergeable] + apply_requirements: [approved, mergeable] + import_requirements: [approved, mergeable] + policy_check: false +`, + repoID: "github.com/owner/repo", + proj: valid.Project{ + Dir: "mydir", + Workspace: "myworkspace", + Name: String("myname"), + PolicyCheck: Bool(false), + }, + repoWorkflows: nil, + exp: valid.MergedProjectCfg{ + PlanRequirements: []string{"approved", "mergeable"}, + ApplyRequirements: []string{"approved", "mergeable"}, + ImportRequirements: []string{"approved", "mergeable"}, + Workflow: defaultWorkflow, + RepoRelDir: "mydir", + Workspace: "myworkspace", + Name: "myname", + AutoplanEnabled: false, + PolicySets: emptyPolicySets, + RepoLocking: true, + PolicyCheck: false, + }, + }, + "global policy check disabled and enabled on current repo": { + gPolicyCheck: false, + gCfg: ` +repos: +- id: /.*/ + plan_requirements: [approved] + apply_requirements: [approved] + import_requirements: [approved] +- id: /github.com/.*/ + plan_requirements: [mergeable] + apply_requirements: [mergeable] + import_requirements: [mergeable] +- id: github.com/owner/repo + plan_requirements: [approved, mergeable] + apply_requirements: [approved, mergeable] + import_requirements: [approved, mergeable] + policy_check: true +`, + repoID: "github.com/owner/repo", + proj: valid.Project{ + Dir: "mydir", + Workspace: "myworkspace", + Name: String("myname"), + PolicyCheck: Bool(false), + }, + repoWorkflows: nil, + exp: valid.MergedProjectCfg{ + PlanRequirements: []string{"approved", "mergeable"}, + ApplyRequirements: []string{"approved", "mergeable"}, + ImportRequirements: []string{"approved", "mergeable"}, + Workflow: defaultWorkflow, + RepoRelDir: "mydir", + Workspace: "myworkspace", + Name: "myname", + AutoplanEnabled: false, + PolicySets: emptyPolicySets, + RepoLocking: true, + PolicyCheck: true, // Project will have policy check as true but since it is globally disable it wont actually run + }, + }, + } + for name, c := range cases { + t.Run(name, func(t *testing.T) { + tmp := t.TempDir() + var global valid.GlobalCfg + path := filepath.Join(tmp, "config.yaml") + Ok(t, os.WriteFile(path, []byte(c.gCfg), 0600)) + var err error + globalCfgArgs := valid.GlobalCfgArgs{ + AllowRepoCfg: false, + MergeableReq: false, + ApprovedReq: false, + UnDivergedReq: false, + PolicyCheckEnabled: c.gPolicyCheck, + } + + global, err = (&config.ParserValidator{}).ParseGlobalCfg(path, valid.NewGlobalCfgFromArgs(globalCfgArgs)) + Ok(t, err) + + global.PolicySets = emptyPolicySets + Equals(t, c.exp, global.MergeProjectCfg(logging.NewNoopLogger(t), c.repoID, c.proj, valid.RepoCfg{Workflows: c.repoWorkflows})) + }) + } +} + // String is a helper routine that allocates a new string value // to store v and returns a pointer to it. func String(v string) *string { return &v } diff --git a/server/core/config/valid/repo_cfg.go b/server/core/config/valid/repo_cfg.go index 608a92e910..4f3a2b82f1 100644 --- a/server/core/config/valid/repo_cfg.go +++ b/server/core/config/valid/repo_cfg.go @@ -134,6 +134,7 @@ type Project struct { DeleteSourceBranchOnMerge *bool RepoLocking *bool ExecutionOrderGroup int + PolicyCheck *bool } // GetName returns the name of the project or an empty string if there is no diff --git a/server/events/project_command_context_builder.go b/server/events/project_command_context_builder.go index b0e5d9f404..c4e2b03cc6 100644 --- a/server/events/project_command_context_builder.go +++ b/server/events/project_command_context_builder.go @@ -166,7 +166,12 @@ func (cb *PolicyCheckProjectCommandContextBuilder) BuildProjectContext( automerge, parallelApply, parallelPlan, verbose, abortOnExcecutionOrderFail bool, terraformClient terraform.Client, ) (projectCmds []command.ProjectContext) { - ctx.Log.Debug("PolicyChecks are enabled") + if prjCfg.PolicyCheck { + ctx.Log.Debug("PolicyChecks are enabled") + } else { + // PolicyCheck is disabled at repository level + ctx.Log.Debug("PolicyChecks are disabled on this repository") + } // If TerraformVersion not defined in config file look for a // terraform.require_version block. @@ -189,7 +194,7 @@ func (cb *PolicyCheckProjectCommandContextBuilder) BuildProjectContext( terraformClient, ) - if cmdName == command.Plan { + if cmdName == command.Plan && prjCfg.PolicyCheck != false { ctx.Log.Debug("Building project command context for %s", command.PolicyCheck) steps := prjCfg.Workflow.PolicyCheck.Steps