Skip to content

Commit

Permalink
feat: add plan_requirements capability (#2979)
Browse files Browse the repository at this point in the history
* Add plan_requirements

* Pass pull request fetcher down to plan runner

* Adding tests

* Replace sliceContainsF with slice.Contains

* Remove policy check for plan

Co-authored-by: Casey Miller <camiller@confluent.io>
Co-authored-by: PePe Amengual <jose.amengual@gmail.com>
Co-authored-by: Casey Miller <47993553+camillsir@users.noreply.github.com>
  • Loading branch information
4 people authored Jan 21, 2023
1 parent f8eba4b commit 5d47eeb
Show file tree
Hide file tree
Showing 27 changed files with 445 additions and 65 deletions.
6 changes: 4 additions & 2 deletions cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1045,15 +1045,17 @@ func (s *ServerCmd) deprecationWarnings(userConfig *server.UserConfig) error {
yamlCfg := "---\nrepos:\n- id: /.*/"
jsonCfg := `{"repos":[{"id":"/.*/"`
if len(commandReqs) > 0 {
yamlCfg += fmt.Sprintf("\n plan_requirements: [%s]", strings.Join(commandReqs, ", "))
yamlCfg += fmt.Sprintf("\n apply_requirements: [%s]", strings.Join(commandReqs, ", "))
yamlCfg += fmt.Sprintf("\n import_requirements: [%s]", strings.Join(commandReqs, ", "))
jsonCfg += fmt.Sprintf(`, "plan_requirements":["%s"]`, strings.Join(commandReqs, "\", \""))
jsonCfg += fmt.Sprintf(`, "apply_requirements":["%s"]`, strings.Join(commandReqs, "\", \""))
jsonCfg += fmt.Sprintf(`, "import_requirements":["%s"]`, strings.Join(commandReqs, "\", \""))
}
if userConfig.AllowRepoConfig {
deprecatedFlags = append(deprecatedFlags, AllowRepoConfigFlag)
yamlCfg += "\n allowed_overrides: [apply_requirements, import_requirements, workflow]\n allow_custom_workflows: true"
jsonCfg += `, "allowed_overrides":["apply_requirements","import_requirements","workflow"], "allow_custom_workflows":true`
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`
}
jsonCfg += "}]}"

Expand Down
24 changes: 16 additions & 8 deletions runatlantis.io/docs/command-requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,19 +70,20 @@ Set the `mergeable` requirement by:
apply_requirements: [mergeable]
```

1. Or by allowing an `atlantis.yaml` file to specify `apply_requirements` and `import_requirements` keys in the `repos.yaml` config:
1. Or by allowing an `atlantis.yaml` file to specify `plan_requirements`, `apply_requirements` and `import_requirements` keys in the `repos.yaml` config:
#### repos.yaml
```yaml
repos:
- id: /.*/
allowed_overrides: [apply_requirements, import_requirements]
allowed_overrides: [plan_requirements, apply_requirements, import_requirements]
```

#### atlantis.yaml
```yaml
version: 3
projects:
- dir: .
plan_requirements: [mergeable]
apply_requirements: [mergeable]
import_requirements: [mergeable]
```
Expand Down Expand Up @@ -153,26 +154,28 @@ Applies to `merge` checkout strategy only.

#### Usage
You can set the `undiverged` requirement by:
1. Creating a `repos.yaml` file with `apply_requirements` and `import_requirements` keys:
1. Creating a `repos.yaml` file with `plan_requirements`, `apply_requirements` and `import_requirements` keys:
```yaml
repos:
- id: /.*/
plan_requirements: [undiverged]
apply_requirements: [undiverged]
import_requirements: [undiverged]
```
1. Or by allowing an `atlantis.yaml` file to specify the `apply_requirements` and `import_requirements` keys in your `repos.yaml` config:
1. Or by allowing an `atlantis.yaml` file to specify the `plan_requirements`, `apply_requirements` and `import_requirements` keys in your `repos.yaml` config:
#### repos.yaml
```yaml
repos:
- id: /.*/
allowed_overrides: [apply_requirements, import_requirements]
allowed_overrides: [plan_requirements, apply_requirements, import_requirements]
```

#### atlantis.yaml
```yaml
version: 3
projects:
- dir: .
plan_requirements: [undiverged]
apply_requirements: [undiverged]
import_requirements: [undiverged]
```
Expand All @@ -199,31 +202,34 @@ If you only want some projects/repos to have apply requirements, then you must
```yaml
repos:
- id: /.*/
plan_requirements: [approved]
apply_requirements: [approved]
import_requirements: [approved]
# Regex that defaults all repos to requiring approval
- id: /github.com/runatlantis/.*/
# Regex to match any repo under the atlantis namespace, and not require approval
# except for repos that might match later in the chain
plan_requirements: []
apply_requirements: []
import_requirements: []
- id: github.com/runatlantis/atlantis
plan_requirements: [approved]
apply_requirements: [approved]
import_requirements: [approved]
# Exact string match of the github.com/runatlantis/atlantis repo
# that sets apply_requirements to approved
```

1. Specify which projects have which requirements via an `atlantis.yaml` file, and allowing
`apply_requirements` and `import_requirements` to be set in `atlantis.yaml` by the server side `repos.yaml`
`plan_requirements`, `apply_requirements` and `import_requirements` to be set in `atlantis.yaml` by the server side `repos.yaml`
config.

For example if I have two directories, `staging` and `production`, I might use:
#### repos.yaml
```yaml
repos:
- id: /.*/
allowed_overrides: [apply_requirements, import_requirements]
allowed_overrides: [plan_requirements, apply_requirements, import_requirements]
# Allow any repo to specify apply_requirements in atlantis.yaml
```

Expand All @@ -232,13 +238,15 @@ If you only want some projects/repos to have apply requirements, then you must
version: 3
projects:
- dir: staging
# By default, apply_requirements and import_requirements are empty so this
# By default, plan_requirements, apply_requirements and import_requirements are empty so this
# isn't strictly necessary.
plan_requirements: []
apply_requirements: []
import_requirements: []
- dir: production
# This requirement will only apply to the
# production directory.
plan_requirements: [mergeable]
apply_requirements: [mergeable]
import_requirements: [mergeable]
```
Expand Down
6 changes: 5 additions & 1 deletion runatlantis.io/docs/repo-level-atlantis-yaml.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ projects:
autoplan:
when_modified: ["*.tf", "../modules/**/*.tf"]
enabled: true
plan_requirements: [mergeable, approved, undiverged]
apply_requirements: [mergeable, approved, undiverged]
import_requirements: [mergeable, approved, undiverged]
workflow: myworkflow
Expand Down Expand Up @@ -245,11 +246,12 @@ version: 3
projects:
- dir: staging
- dir: production
plan_requirements: [approved]
apply_requirements: [approved]
import_requirements: [approved]
```
:::warning
`apply_requirements` and `import_requirements` are restricted keys so this repo will need to be configured
`plan_requirements`, `apply_requirements` and `import_requirements` are restricted keys so this repo will need to be configured
to be allowed to set this key. See [Server-Side Repo Config Use Cases](server-side-repo-config.html#repos-can-set-their-own-apply-an-applicable-subcommand).
:::

Expand Down Expand Up @@ -300,6 +302,7 @@ delete_source_branch_on_merge: false
repo_locking: true
autoplan:
terraform_version: 0.11.0
plan_requirements: ["approved"]
apply_requirements: ["approved"]
import_requirements: ["approved"]
workflow: myworkflow
Expand All @@ -316,6 +319,7 @@ workflow: myworkflow
| repo_locking | bool | `true` | no | Get a repository lock in this project when plan. |
| autoplan | [Autoplan](#autoplan) | none | no | A custom autoplan configuration. If not specified, will use the autoplan config. See [Autoplanning](autoplanning.html). |
| terraform_version | string | none | no | A specific Terraform version to use when running commands for this project. Must be [Semver compatible](https://semver.org/), ex. `v0.11.0`, `0.12.0-beta1`. |
| plan_requirements<br />*(restricted)* | array[string] | none | no | Requirements that must be satisfied before `atlantis plan` can be run. Currently the only supported requirements are `approved`, `mergeable`, and `undiverged`. See [Command Requirements](command-requirements.html) for more details. |
| apply_requirements<br />*(restricted)* | array[string] | none | no | Requirements that must be satisfied before `atlantis apply` can be run. Currently the only supported requirements are `approved`, `mergeable`, and `undiverged`. See [Command Requirements](command-requirements.html) for more details. |
| import_requirements<br />*(restricted)* | array[string] | none | no | Requirements that must be satisfied before `atlantis import` can be run. Currently the only supported requirements are `approved`, `mergeable`, and `undiverged`. See [Command Requirements](command-requirements.html) for more details. |
| workflow <br />*(restricted)* | string | none | no | A custom workflow. If not specified, Atlantis will use its default workflow. |
Expand Down
24 changes: 18 additions & 6 deletions runatlantis.io/docs/server-side-repo-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ repos:
# By default, atlantis.yaml is used.
repo_config_file: path/to/atlantis.yaml

# plan_requirements sets the Plan Requirements for all repos that match.
plan_requirements: [approved, mergeable, undiverged]

# apply_requirements sets the Apply Requirements for all repos that match.
apply_requirements: [approved, mergeable, undiverged]

Expand Down Expand Up @@ -106,13 +109,14 @@ Here are some of the reasons you might want to use a repo config.
### Requiring PR Is Approved Before an applicable subcommand
If you want to require that all (or specific) repos must have pull requests
approved before Atlantis will allow running `apply` or `import`, use the `apply_requirements` or `import_requirements` keys.
approved before Atlantis will allow running `apply` or `import`, use the `plan_requirements`, `apply_requirements` or `import_requirements` keys.

For all repos:
```yaml
# repos.yaml
repos:
- id: /.*/
plan_requirements: [approved]
apply_requirements: [approved]
import_requirements: [approved]
```
Expand All @@ -122,6 +126,7 @@ For a specific repo:
# repos.yaml
repos:
- id: github.com/myorg/myrepo
plan_requirements: [approved]
apply_requirements: [approved]
import_requirements: [approved]
```
Expand All @@ -130,13 +135,14 @@ See [Command Requirements](command-requirements.html) for more details.

### Requiring PR Is "Mergeable" Before Apply or Import
If you want to require that all (or specific) repos must have pull requests
in a mergeable state before Atlantis will allow running `apply` or `import`, use the `apply_requirements` or `import_requirements` keys.
in a mergeable state before Atlantis will allow running `apply` or `import`, use the `plan_requirements`, `apply_requirements` or `import_requirements` keys.

For all repos:
```yaml
# repos.yaml
repos:
- id: /.*/
plan_requirements: [mergeable]
apply_requirements: [mergeable]
import_requirements: [mergeable]
```
Expand All @@ -146,6 +152,7 @@ For a specific repo:
# repos.yaml
repos:
- id: github.com/myorg/myrepo
plan_requirements: [mergeable]
apply_requirements: [mergeable]
import_requirements: [mergeable]
```
Expand All @@ -162,33 +169,36 @@ To allow all repos to override the default:
repos:
- id: /.*/
# The default will be approved.
plan_requirements: [approved]
apply_requirements: [approved]
import_requirements: [approved]
# But all repos can set their own using atlantis.yaml
allowed_overrides: [apply_requirements, import_requirements]
allowed_overrides: [plan_requirements, apply_requirements, import_requirements]
```
To allow only a specific repo to override the default:
```yaml
# repos.yaml
repos:
# Set a default for all repos.
- id: /.*/
plan_requirements: [approved]
apply_requirements: [approved]
import_requirements: [approved]
# Allow a specific repo to override.
- id: github.com/myorg/myrepo
allowed_overrides: [apply_requirements, import_requirements]
allowed_overrides: [plan_requirements, apply_requirements, import_requirements]
```

Then each allowed repo can have an `atlantis.yaml` file that
sets `apply_requirements` or `import_requirements` to an empty array (disabling the requirement).
sets `plan_requirements`, `apply_requirements` or `import_requirements` to an empty array (disabling the requirement).
```yaml
# atlantis.yaml in the repo root or set repo_config_file in repos.yaml
version: 3
projects:
- dir: .
plan_requirements: []
apply_requirements: []
import_requirements: []
```
Expand Down Expand Up @@ -440,6 +450,7 @@ Each servers handle different repository config files.
repos:
- id: /.*/
branch: /.*/
plan_requirements: []
apply_requirements: []
import_requirements: []
workflow: default
Expand Down Expand Up @@ -468,7 +479,8 @@ If you set a workflow with the key `default`, it will override this.
| id | string | none | yes | Value can be a regular expression when specified as /&lt;regex&gt;/ or an exact string match. Repo IDs are of the form `{vcs hostname}/{org}/{name}`, ex. `github.com/owner/repo`. Hostname is specified without scheme or port. For Bitbucket Server, {org} is the **name** of the project, not the key. |
| branch | string | none | no | An regex matching pull requests by base branch (the branch the pull request is getting merged into). By default, all branches are matched |
| repo_config_file | string | none | no | Repo config file path in this repo. By default, use `atlantis.yaml` which is located on repository root. When multiple atlantis servers work with the same repo, please set different file names. |
| workflow | string | none | no | A custom workflow. |
| workflow | string | none | no | A custom workflow.
| plan_requirements | []string | none | no | Requirements that must be satisfied before `atlantis plan` can be run. Currently the only supported requirements are `approved`, `mergeable`, and `undiverged`. See [Command Requirements](command-requirements.html) for more details. | |
| apply_requirements | []string | none | no | Requirements that must be satisfied before `atlantis apply` can be run. Currently the only supported requirements are `approved`, `mergeable`, and `undiverged`. See [Command Requirements](command-requirements.html) for more details. |
| import_requirements | []string | none | no | Requirements that must be satisfied before `atlantis import` can be run. Currently the only supported requirements are `approved`, `mergeable`, and `undiverged`. See [Command Requirements](command-requirements.html) for more details. |
| allowed_overrides | []string | none | no | A list of restricted keys that `atlantis.yaml` files can override. The only supported keys are `apply_requirements`, `workflow`, `delete_source_branch_on_merge` and `repo_locking` |
Expand Down
5 changes: 3 additions & 2 deletions server/controllers/events/events_controller_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1257,6 +1257,8 @@ func setupE2E(t *testing.T, repoDir string, opt setupOption) (events_controllers
userConfig.QuietPolicyChecks,
)

e2ePullReqStatusFetcher := vcs.NewPullReqStatusFetcher(e2eVCSClient, "atlantis-test")

planCommandRunner := events.NewPlanCommandRunner(
false,
false,
Expand All @@ -1275,10 +1277,9 @@ func setupE2E(t *testing.T, repoDir string, opt setupOption) (events_controllers
boltdb,
lockingClient,
discardApprovalOnPlan,
e2ePullReqStatusFetcher,
)

e2ePullReqStatusFetcher := vcs.NewPullReqStatusFetcher(e2eVCSClient, "atlantis-test")

applyCommandRunner := events.NewApplyCommandRunner(
e2eVCSClient,
false,
Expand Down
13 changes: 10 additions & 3 deletions server/core/config/parser_validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1308,7 +1308,13 @@ func TestParseGlobalCfg(t *testing.T) {
input: `repos:
- id: /.*/
allowed_overrides: [invalid]`,
expErr: "repos: (0: (allowed_overrides: \"invalid\" is not a valid override, only \"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\" and \"repo_locking\" are supported.).).",
},
"invalid plan_requirement": {
input: `repos:
- id: /.*/
plan_requirements: [invalid]`,
expErr: "repos: (0: (plan_requirements: \"invalid\" is not a valid plan_requirement, only \"approved\", \"mergeable\" and \"undiverged\" are supported.).).",
},
"invalid apply_requirement": {
input: `repos:
Expand Down Expand Up @@ -1394,7 +1400,7 @@ repos:
workflow: custom1
post_workflow_hooks:
- run: custom workflow command
allowed_overrides: [apply_requirements, import_requirements, workflow, delete_source_branch_on_merge]
allowed_overrides: [plan_requirements, apply_requirements, import_requirements, workflow, delete_source_branch_on_merge]
allow_custom_workflows: true
- id: /.*/
branch: /(master|main)/
Expand Down Expand Up @@ -1445,7 +1451,7 @@ policies:
PreWorkflowHooks: preWorkflowHooks,
Workflow: &customWorkflow1,
PostWorkflowHooks: postWorkflowHooks,
AllowedOverrides: []string{"apply_requirements", "import_requirements", "workflow", "delete_source_branch_on_merge"},
AllowedOverrides: []string{"plan_requirements", "apply_requirements", "import_requirements", "workflow", "delete_source_branch_on_merge"},
AllowCustomWorkflows: Bool(true),
},
{
Expand Down Expand Up @@ -1528,6 +1534,7 @@ workflows:
{
IDRegex: regexp.MustCompile(".*"),
BranchRegex: regexp.MustCompile(".*"),
PlanRequirements: []string{},
ApplyRequirements: []string{},
ImportRequirements: []string{},
Workflow: &valid.Workflow{
Expand Down
Loading

0 comments on commit 5d47eeb

Please sign in to comment.