mirror of
https://git.vectorsigma.ru/public/atlantis.git
synced 2026-07-28 23:18:15 +00:00
feat: state rm (#2880)
* feat: state rm * review feedback * fix conflict for pegomock generation code * adopt state command into allow-commands * fix conflicts * fix: state rm works on workspace * notify import/state rm discard plan file * fix lint * use repeat instead warning for re-plan * perl -pi -e 's!\* 🔁 plan file was discarded. to!🚮 A plan file was discarded. Re-plan would be required before applying.\n\n\* 🔁 To!g' server/**/* * follow main branch
This commit is contained in:
1
go.mod
1
go.mod
@@ -43,6 +43,7 @@ require (
|
|||||||
github.com/xanzy/go-gitlab v0.78.0
|
github.com/xanzy/go-gitlab v0.78.0
|
||||||
go.etcd.io/bbolt v1.3.6
|
go.etcd.io/bbolt v1.3.6
|
||||||
go.uber.org/zap v1.24.0
|
go.uber.org/zap v1.24.0
|
||||||
|
golang.org/x/exp v0.0.0-20230113152452-c42ee1cf562e
|
||||||
golang.org/x/term v0.4.0
|
golang.org/x/term v0.4.0
|
||||||
golang.org/x/text v0.6.0
|
golang.org/x/text v0.6.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -508,6 +508,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
|||||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||||
|
golang.org/x/exp v0.0.0-20230113152452-c42ee1cf562e h1:uGuXqQsI2BAE8xNqSqNxhTdDdhlvpBvWFw/KBwtCtjI=
|
||||||
|
golang.org/x/exp v0.0.0-20230113152452-c42ee1cf562e/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
|||||||
@@ -56,6 +56,11 @@ workflows:
|
|||||||
- init
|
- init
|
||||||
- import:
|
- import:
|
||||||
extra_args: ["-var-file", "production.tfvars"]
|
extra_args: ["-var-file", "production.tfvars"]
|
||||||
|
state_rm:
|
||||||
|
steps:
|
||||||
|
- init
|
||||||
|
- state_rm:
|
||||||
|
extra_args: ["-lock=false"]
|
||||||
```
|
```
|
||||||
Then in your repo-level `atlantis.yaml` file, you would reference the workflows:
|
Then in your repo-level `atlantis.yaml` file, you would reference the workflows:
|
||||||
```yaml
|
```yaml
|
||||||
@@ -374,13 +379,15 @@ projects:
|
|||||||
plan:
|
plan:
|
||||||
apply:
|
apply:
|
||||||
import:
|
import:
|
||||||
|
state_rm:
|
||||||
```
|
```
|
||||||
|
|
||||||
| Key | Type | Default | Required | Description |
|
| Key | Type | Default | Required | Description |
|
||||||
|--------|-----------------|-------------------------|----------|----------------------------------|
|
|----------|-----------------|---------------------------|----------|---------------------------------------|
|
||||||
| plan | [Stage](#stage) | `steps: [init, plan]` | no | How to plan for this project. |
|
| plan | [Stage](#stage) | `steps: [init, plan]` | no | How to plan for this project. |
|
||||||
| apply | [Stage](#stage) | `steps: [apply]` | no | How to apply for this project. |
|
| apply | [Stage](#stage) | `steps: [apply]` | no | How to apply for this project. |
|
||||||
| import | [Stage](#stage) | `steps: [init, import]` | no | How to import for this project. |
|
| import | [Stage](#stage) | `steps: [init, import]` | no | How to import for this project. |
|
||||||
|
| state_rm | [Stage](#stage) | `steps: [init, state_rm]` | no | How to run state rm for this project. |
|
||||||
|
|
||||||
### Stage
|
### Stage
|
||||||
```yaml
|
```yaml
|
||||||
@@ -403,10 +410,11 @@ Steps can be a single string for a built-in command.
|
|||||||
- plan
|
- plan
|
||||||
- apply
|
- apply
|
||||||
- import
|
- import
|
||||||
|
- state_rm
|
||||||
```
|
```
|
||||||
| Key | Type | Default | Required | Description |
|
| Key | Type | Default | Required | Description |
|
||||||
|------------------------|--------|---------|----------|------------------------------------------------------------------------------------------------------------------|
|
|---------------------------------|--------|---------|----------|------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| init/plan/apply/import | string | none | no | Use a built-in command without additional configuration. Only `init`, `plan`, `apply` and `import` are supported |
|
| init/plan/apply/import/state_rm | string | none | no | Use a built-in command without additional configuration. Only `init`, `plan`, `apply`, `import` and `state_rm` are supported |
|
||||||
|
|
||||||
#### Built-In Command With Extra Args
|
#### Built-In Command With Extra Args
|
||||||
A map from string to `extra_args` for a built-in command with extra arguments.
|
A map from string to `extra_args` for a built-in command with extra arguments.
|
||||||
@@ -419,10 +427,12 @@ A map from string to `extra_args` for a built-in command with extra arguments.
|
|||||||
extra_args: [arg1, arg2]
|
extra_args: [arg1, arg2]
|
||||||
- import:
|
- import:
|
||||||
extra_args: [arg1, arg2]
|
extra_args: [arg1, arg2]
|
||||||
|
- state_rm:
|
||||||
|
extra_args: [arg1, arg2]
|
||||||
```
|
```
|
||||||
| Key | Type | Default | Required | Description |
|
| Key | Type | Default | Required | Description |
|
||||||
|------------------------|------------------------------------|---------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|---------------------------------|------------------------------------|---------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| init/plan/apply/import | map[`extra_args` -> array[string]] | none | no | Use a built-in command and append `extra_args`. Only `init`, `plan`, `apply` and `import` are supported as keys and only `extra_args` is supported as a value |
|
| init/plan/apply/import/state_rm | map[`extra_args` -> array[string]] | none | no | Use a built-in command and append `extra_args`. Only `init`, `plan`, `apply`, `import` and `state_rm` are supported as keys and only `extra_args` is supported as a value |
|
||||||
|
|
||||||
#### Custom `run` Command
|
#### Custom `run` Command
|
||||||
Or a custom command
|
Or a custom command
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ Values are chosen in this order:
|
|||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
* Accepts a comma separated list, ex. `command1,command2`.
|
* Accepts a comma separated list, ex. `command1,command2`.
|
||||||
* `version`, `plan`, `apply`, `unlock`, `approve_policies`, `import` and `all` are available.
|
* `version`, `plan`, `apply`, `unlock`, `approve_policies`, `import`, `state` and `all` are available.
|
||||||
* `all` is a special keyword that allows all commands. If pass `all` then all other commands will be ignored.
|
* `all` is a special keyword that allows all commands. If pass `all` then all other commands will be ignored.
|
||||||
|
|
||||||
### `--allow-draft-prs`
|
### `--allow-draft-prs`
|
||||||
|
|||||||
@@ -169,6 +169,51 @@ atlantis import -d dir 'aws_instance.example["foo"]' i-1234567890abcdef0 -- -var
|
|||||||
```
|
```
|
||||||
If a flag is needed to be always appended, see [Custom Workflow Use Cases](custom-workflows.html#adding-extra-arguments-to-terraform-commands).
|
If a flag is needed to be always appended, see [Custom Workflow Use Cases](custom-workflows.html#adding-extra-arguments-to-terraform-commands).
|
||||||
|
|
||||||
|
---
|
||||||
|
## atlantis state rm
|
||||||
|
```bash
|
||||||
|
atlantis state [options] rm ADDRESS... -- [terraform state rm flags]
|
||||||
|
```
|
||||||
|
### Explanation
|
||||||
|
Runs `terraform state rm` that matches the directory/project/workspace.
|
||||||
|
This command discards the terraform plan result. After run state rm and before an apply, another `atlantis plan` must be run again.
|
||||||
|
|
||||||
|
To allow the `state` command requires [--allow-commands](/docs/server-configuration.html#allow-commands) configuration.
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
```bash
|
||||||
|
# Runs state rm
|
||||||
|
atlantis state rm ADDRESS1 ADDRESS2
|
||||||
|
|
||||||
|
# Runs state rm in the root directory of the repo with workspace `default`
|
||||||
|
atlantis state -d . rm ADDRESS
|
||||||
|
|
||||||
|
# Runs state rm in the `project1` directory of the repo with workspace `default`
|
||||||
|
atlantis state -d project1 rm ADDRESS
|
||||||
|
|
||||||
|
# Runs state rm in the root directory of the repo with workspace `staging`
|
||||||
|
atlantis state -w staging rm ADDRESS
|
||||||
|
```
|
||||||
|
|
||||||
|
::: tip
|
||||||
|
* If run state rm to for_each resources, it requires a single quoted address.
|
||||||
|
* ex. `atlantis state rm 'aws_instance.example["foo"]'`
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Options
|
||||||
|
* `-d directory` Run state rm a resource for this directory, relative to root of repo. Use `.` for root.
|
||||||
|
* `-p project` Run state rm a resource for this project. Refers to the name of the project configured in the repo's [`atlantis.yaml`](repo-level-atlantis-yaml.html) repo configuration file. This cannot be used at the same time as `-d` or `-w`.
|
||||||
|
* `-w workspace` Run state rm a resource for a specific [Terraform workspace](https://developer.hashicorp.com/terraform/language/state/workspaces). Ignore this if Terraform workspaces are unused.
|
||||||
|
|
||||||
|
### Additional Terraform flags
|
||||||
|
|
||||||
|
If `terraform state rm` requires additional arguments, like `-lock=false'`
|
||||||
|
append them to the end of the comment after `--`, e.g.
|
||||||
|
```
|
||||||
|
atlantis state -d dir rm 'aws_instance.example["foo"]' -- -lock=false
|
||||||
|
```
|
||||||
|
If a flag is needed to be always appended, see [Custom Workflow Use Cases](custom-workflows.html#adding-extra-arguments-to-terraform-commands).
|
||||||
|
|
||||||
---
|
---
|
||||||
## atlantis unlock
|
## atlantis unlock
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -497,6 +497,71 @@ func TestGitHubWorkflow(t *testing.T) {
|
|||||||
{"exp-output-merge.txt"},
|
{"exp-output-merge.txt"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Description: "state rm single project",
|
||||||
|
RepoDir: "state-rm-single-project",
|
||||||
|
ModifiedFiles: []string{"main.tf"},
|
||||||
|
ExpAutoplan: true,
|
||||||
|
Comments: []string{
|
||||||
|
"atlantis import random_id.simple AA",
|
||||||
|
"atlantis import 'random_id.for_each[\"overridden\"]' BB -- -var var=overridden",
|
||||||
|
"atlantis import random_id.count[0] BB",
|
||||||
|
"atlantis plan -- -var var=overridden",
|
||||||
|
"atlantis state rm 'random_id.for_each[\"overridden\"]' -- -lock=false",
|
||||||
|
"atlantis state rm random_id.count[0] random_id.simple",
|
||||||
|
"atlantis plan -- -var var=overridden",
|
||||||
|
},
|
||||||
|
ExpReplies: [][]string{
|
||||||
|
{"exp-output-autoplan.txt"},
|
||||||
|
{"exp-output-import-simple.txt"},
|
||||||
|
{"exp-output-import-foreach.txt"},
|
||||||
|
{"exp-output-import-count.txt"},
|
||||||
|
{"exp-output-plan.txt"},
|
||||||
|
{"exp-output-state-rm-foreach.txt"},
|
||||||
|
{"exp-output-state-rm-multiple.txt"},
|
||||||
|
{"exp-output-plan-again.txt"},
|
||||||
|
{"exp-output-merged.txt"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Description: "state rm workspace",
|
||||||
|
RepoDir: "state-rm-workspace",
|
||||||
|
Comments: []string{
|
||||||
|
"atlantis import -p dir1-ops 'random_id.dummy1[0]' AA",
|
||||||
|
"atlantis plan -p dir1-ops",
|
||||||
|
"atlantis state rm -p dir1-ops 'random_id.dummy1[0]'",
|
||||||
|
"atlantis plan -p dir1-ops",
|
||||||
|
},
|
||||||
|
ExpReplies: [][]string{
|
||||||
|
{"exp-output-import-dummy1.txt"},
|
||||||
|
{"exp-output-plan.txt"},
|
||||||
|
{"exp-output-state-rm-dummy1.txt"},
|
||||||
|
{"exp-output-plan-again.txt"},
|
||||||
|
{"exp-output-merge.txt"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Description: "state rm multiple project",
|
||||||
|
RepoDir: "state-rm-multiple-project",
|
||||||
|
ModifiedFiles: []string{"dir1/main.tf", "dir2/main.tf"},
|
||||||
|
ExpAutoplan: true,
|
||||||
|
Comments: []string{
|
||||||
|
"atlantis import -d dir1 random_id.dummy AA",
|
||||||
|
"atlantis import -d dir2 random_id.dummy BB",
|
||||||
|
"atlantis plan",
|
||||||
|
"atlantis state rm random_id.dummy",
|
||||||
|
"atlantis plan",
|
||||||
|
},
|
||||||
|
ExpReplies: [][]string{
|
||||||
|
{"exp-output-autoplan.txt"},
|
||||||
|
{"exp-output-import-dummy1.txt"},
|
||||||
|
{"exp-output-import-dummy2.txt"},
|
||||||
|
{"exp-output-plan.txt"},
|
||||||
|
{"exp-output-state-rm-multiple-projects.txt"},
|
||||||
|
{"exp-output-plan-again.txt"},
|
||||||
|
{"exp-output-merged.txt"},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
t.Run(c.Description, func(t *testing.T) {
|
t.Run(c.Description, func(t *testing.T) {
|
||||||
@@ -1152,7 +1217,8 @@ func setupE2E(t *testing.T, repoDir string, opt setupOption) (events_controllers
|
|||||||
ApplyStepRunner: &runtime.ApplyStepRunner{
|
ApplyStepRunner: &runtime.ApplyStepRunner{
|
||||||
TerraformExecutor: terraformClient,
|
TerraformExecutor: terraformClient,
|
||||||
},
|
},
|
||||||
ImportStepRunner: runtime.NewImportStepRunner(terraformClient, defaultTFVersion),
|
ImportStepRunner: runtime.NewImportStepRunner(terraformClient, defaultTFVersion),
|
||||||
|
StateRmStepRunner: runtime.NewStateRmStepRunner(terraformClient, defaultTFVersion),
|
||||||
RunStepRunner: &runtime.RunStepRunner{
|
RunStepRunner: &runtime.RunStepRunner{
|
||||||
TerraformExecutor: terraformClient,
|
TerraformExecutor: terraformClient,
|
||||||
DefaultTFVersion: defaultTFVersion,
|
DefaultTFVersion: defaultTFVersion,
|
||||||
@@ -1262,6 +1328,12 @@ func setupE2E(t *testing.T, repoDir string, opt setupOption) (events_controllers
|
|||||||
projectCommandRunner,
|
projectCommandRunner,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
stateCommandRunner := events.NewStateCommandRunner(
|
||||||
|
pullUpdater,
|
||||||
|
projectCommandBuilder,
|
||||||
|
projectCommandRunner,
|
||||||
|
)
|
||||||
|
|
||||||
commentCommandRunnerByCmd := map[command.Name]events.CommentCommandRunner{
|
commentCommandRunnerByCmd := map[command.Name]events.CommentCommandRunner{
|
||||||
command.Plan: planCommandRunner,
|
command.Plan: planCommandRunner,
|
||||||
command.Apply: applyCommandRunner,
|
command.Apply: applyCommandRunner,
|
||||||
@@ -1269,6 +1341,7 @@ func setupE2E(t *testing.T, repoDir string, opt setupOption) (events_controllers
|
|||||||
command.Unlock: unlockCommandRunner,
|
command.Unlock: unlockCommandRunner,
|
||||||
command.Version: versionCommandRunner,
|
command.Version: versionCommandRunner,
|
||||||
command.Import: importCommandRunner,
|
command.Import: importCommandRunner,
|
||||||
|
command.State: stateCommandRunner,
|
||||||
}
|
}
|
||||||
|
|
||||||
commandRunner := &events.DefaultCommandRunner{
|
commandRunner := &events.DefaultCommandRunner{
|
||||||
|
|||||||
@@ -12,5 +12,7 @@ The resources that were imported are shown above. These resources are now in
|
|||||||
your Terraform state and will henceforth be managed by Terraform.
|
your Terraform state and will henceforth be managed by Terraform.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
:put_litter_in_its_place: A plan file was discarded. Re-plan would be required before applying.
|
||||||
|
|
||||||
* :repeat: To **plan** this project again, comment:
|
* :repeat: To **plan** this project again, comment:
|
||||||
* `atlantis plan -d dir1`
|
* `atlantis plan -d dir1`
|
||||||
@@ -12,5 +12,7 @@ The resources that were imported are shown above. These resources are now in
|
|||||||
your Terraform state and will henceforth be managed by Terraform.
|
your Terraform state and will henceforth be managed by Terraform.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
:put_litter_in_its_place: A plan file was discarded. Re-plan would be required before applying.
|
||||||
|
|
||||||
* :repeat: To **plan** this project again, comment:
|
* :repeat: To **plan** this project again, comment:
|
||||||
* `atlantis plan -d .`
|
* `atlantis plan -d .`
|
||||||
@@ -12,5 +12,7 @@ The resources that were imported are shown above. These resources are now in
|
|||||||
your Terraform state and will henceforth be managed by Terraform.
|
your Terraform state and will henceforth be managed by Terraform.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
:put_litter_in_its_place: A plan file was discarded. Re-plan would be required before applying.
|
||||||
|
|
||||||
* :repeat: To **plan** this project again, comment:
|
* :repeat: To **plan** this project again, comment:
|
||||||
* `atlantis plan -d .`
|
* `atlantis plan -d .`
|
||||||
@@ -12,5 +12,7 @@ The resources that were imported are shown above. These resources are now in
|
|||||||
your Terraform state and will henceforth be managed by Terraform.
|
your Terraform state and will henceforth be managed by Terraform.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
:put_litter_in_its_place: A plan file was discarded. Re-plan would be required before applying.
|
||||||
|
|
||||||
* :repeat: To **plan** this project again, comment:
|
* :repeat: To **plan** this project again, comment:
|
||||||
* `atlantis plan -d .`
|
* `atlantis plan -d .`
|
||||||
@@ -12,5 +12,7 @@ The resources that were imported are shown above. These resources are now in
|
|||||||
your Terraform state and will henceforth be managed by Terraform.
|
your Terraform state and will henceforth be managed by Terraform.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
:put_litter_in_its_place: A plan file was discarded. Re-plan would be required before applying.
|
||||||
|
|
||||||
* :repeat: To **plan** this project again, comment:
|
* :repeat: To **plan** this project again, comment:
|
||||||
* `atlantis plan -d .`
|
* `atlantis plan -d .`
|
||||||
@@ -12,5 +12,7 @@ The resources that were imported are shown above. These resources are now in
|
|||||||
your Terraform state and will henceforth be managed by Terraform.
|
your Terraform state and will henceforth be managed by Terraform.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
:put_litter_in_its_place: A plan file was discarded. Re-plan would be required before applying.
|
||||||
|
|
||||||
* :repeat: To **plan** this project again, comment:
|
* :repeat: To **plan** this project again, comment:
|
||||||
* `atlantis plan -p dir1-ops`
|
* `atlantis plan -p dir1-ops`
|
||||||
|
|||||||
@@ -12,5 +12,7 @@ The resources that were imported are shown above. These resources are now in
|
|||||||
your Terraform state and will henceforth be managed by Terraform.
|
your Terraform state and will henceforth be managed by Terraform.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
:put_litter_in_its_place: A plan file was discarded. Re-plan would be required before applying.
|
||||||
|
|
||||||
* :repeat: To **plan** this project again, comment:
|
* :repeat: To **plan** this project again, comment:
|
||||||
* `atlantis plan -p dir1-ops`
|
* `atlantis plan -p dir1-ops`
|
||||||
|
|||||||
4
server/controllers/events/testdata/test-repos/state-rm-multiple-project/atlantis.yaml
vendored
Normal file
4
server/controllers/events/testdata/test-repos/state-rm-multiple-project/atlantis.yaml
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
version: 3
|
||||||
|
projects:
|
||||||
|
- dir: dir1
|
||||||
|
- dir: dir2
|
||||||
4
server/controllers/events/testdata/test-repos/state-rm-multiple-project/dir1/main.tf
vendored
Normal file
4
server/controllers/events/testdata/test-repos/state-rm-multiple-project/dir1/main.tf
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
resource "random_id" "dummy" {
|
||||||
|
keepers = {}
|
||||||
|
byte_length = 1
|
||||||
|
}
|
||||||
4
server/controllers/events/testdata/test-repos/state-rm-multiple-project/dir2/main.tf
vendored
Normal file
4
server/controllers/events/testdata/test-repos/state-rm-multiple-project/dir2/main.tf
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
resource "random_id" "dummy" {
|
||||||
|
keepers = {}
|
||||||
|
byte_length = 1
|
||||||
|
}
|
||||||
75
server/controllers/events/testdata/test-repos/state-rm-multiple-project/exp-output-autoplan.txt
vendored
Normal file
75
server/controllers/events/testdata/test-repos/state-rm-multiple-project/exp-output-autoplan.txt
vendored
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
Ran Plan for 2 projects:
|
||||||
|
|
||||||
|
1. dir: `dir1` workspace: `default`
|
||||||
|
1. dir: `dir2` workspace: `default`
|
||||||
|
|
||||||
|
### 1. dir: `dir1` workspace: `default`
|
||||||
|
<details><summary>Show Output</summary>
|
||||||
|
|
||||||
|
```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:
|
||||||
|
|
||||||
|
# random_id.dummy will be created
|
||||||
|
+ resource "random_id" "dummy" {
|
||||||
|
+ b64_std = (known after apply)
|
||||||
|
+ b64_url = (known after apply)
|
||||||
|
+ byte_length = 1
|
||||||
|
+ dec = (known after apply)
|
||||||
|
+ hex = (known after apply)
|
||||||
|
+ id = (known after apply)
|
||||||
|
+ keepers = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Plan: 1 to add, 0 to change, 0 to destroy.
|
||||||
|
```
|
||||||
|
|
||||||
|
* :arrow_forward: To **apply** this plan, comment:
|
||||||
|
* `atlantis apply -d dir1`
|
||||||
|
* :put_litter_in_its_place: To **delete** this plan click [here](lock-url)
|
||||||
|
* :repeat: To **plan** this project again, comment:
|
||||||
|
* `atlantis plan -d dir1`
|
||||||
|
</details>
|
||||||
|
Plan: 1 to add, 0 to change, 0 to destroy.
|
||||||
|
|
||||||
|
---
|
||||||
|
### 2. dir: `dir2` workspace: `default`
|
||||||
|
<details><summary>Show Output</summary>
|
||||||
|
|
||||||
|
```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:
|
||||||
|
|
||||||
|
# random_id.dummy will be created
|
||||||
|
+ resource "random_id" "dummy" {
|
||||||
|
+ b64_std = (known after apply)
|
||||||
|
+ b64_url = (known after apply)
|
||||||
|
+ byte_length = 1
|
||||||
|
+ dec = (known after apply)
|
||||||
|
+ hex = (known after apply)
|
||||||
|
+ id = (known after apply)
|
||||||
|
+ keepers = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Plan: 1 to add, 0 to change, 0 to destroy.
|
||||||
|
```
|
||||||
|
|
||||||
|
* :arrow_forward: To **apply** this plan, comment:
|
||||||
|
* `atlantis apply -d dir2`
|
||||||
|
* :put_litter_in_its_place: To **delete** this plan click [here](lock-url)
|
||||||
|
* :repeat: To **plan** this project again, comment:
|
||||||
|
* `atlantis plan -d dir2`
|
||||||
|
</details>
|
||||||
|
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`
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
Ran Import for dir: `dir1` workspace: `default`
|
||||||
|
|
||||||
|
```diff
|
||||||
|
random_id.dummy: Importing from ID "AA"...
|
||||||
|
random_id.dummy: Import prepared!
|
||||||
|
Prepared random_id for import
|
||||||
|
random_id.dummy: Refreshing state... [id=AA]
|
||||||
|
|
||||||
|
Import successful!
|
||||||
|
|
||||||
|
The resources that were imported are shown above. These resources are now in
|
||||||
|
your Terraform state and will henceforth be managed by Terraform.
|
||||||
|
```
|
||||||
|
|
||||||
|
:put_litter_in_its_place: A plan file was discarded. Re-plan would be required before applying.
|
||||||
|
|
||||||
|
* :repeat: To **plan** this project again, comment:
|
||||||
|
* `atlantis plan -d dir1`
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
Ran Import for dir: `dir2` workspace: `default`
|
||||||
|
|
||||||
|
```diff
|
||||||
|
random_id.dummy: Importing from ID "BB"...
|
||||||
|
random_id.dummy: Import prepared!
|
||||||
|
Prepared random_id for import
|
||||||
|
random_id.dummy: Refreshing state... [id=BB]
|
||||||
|
|
||||||
|
Import successful!
|
||||||
|
|
||||||
|
The resources that were imported are shown above. These resources are now in
|
||||||
|
your Terraform state and will henceforth be managed by Terraform.
|
||||||
|
```
|
||||||
|
|
||||||
|
:put_litter_in_its_place: A plan file was discarded. Re-plan would be required before applying.
|
||||||
|
|
||||||
|
* :repeat: To **plan** this project again, comment:
|
||||||
|
* `atlantis plan -d dir2`
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
Locks and plans deleted for the projects and workspaces modified in this pull request:
|
||||||
|
|
||||||
|
- dir: `dir1` workspace: `default`
|
||||||
|
- dir: `dir2` workspace: `default`
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
Ran Plan for 2 projects:
|
||||||
|
|
||||||
|
1. dir: `dir1` workspace: `default`
|
||||||
|
1. dir: `dir2` workspace: `default`
|
||||||
|
|
||||||
|
### 1. dir: `dir1` workspace: `default`
|
||||||
|
<details><summary>Show Output</summary>
|
||||||
|
|
||||||
|
```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:
|
||||||
|
|
||||||
|
# random_id.dummy will be created
|
||||||
|
+ resource "random_id" "dummy" {
|
||||||
|
+ b64_std = (known after apply)
|
||||||
|
+ b64_url = (known after apply)
|
||||||
|
+ byte_length = 1
|
||||||
|
+ dec = (known after apply)
|
||||||
|
+ hex = (known after apply)
|
||||||
|
+ id = (known after apply)
|
||||||
|
+ keepers = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Plan: 1 to add, 0 to change, 0 to destroy.
|
||||||
|
```
|
||||||
|
|
||||||
|
* :arrow_forward: To **apply** this plan, comment:
|
||||||
|
* `atlantis apply -d dir1`
|
||||||
|
* :put_litter_in_its_place: To **delete** this plan click [here](lock-url)
|
||||||
|
* :repeat: To **plan** this project again, comment:
|
||||||
|
* `atlantis plan -d dir1`
|
||||||
|
</details>
|
||||||
|
Plan: 1 to add, 0 to change, 0 to destroy.
|
||||||
|
|
||||||
|
---
|
||||||
|
### 2. dir: `dir2` workspace: `default`
|
||||||
|
<details><summary>Show Output</summary>
|
||||||
|
|
||||||
|
```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:
|
||||||
|
|
||||||
|
# random_id.dummy will be created
|
||||||
|
+ resource "random_id" "dummy" {
|
||||||
|
+ b64_std = (known after apply)
|
||||||
|
+ b64_url = (known after apply)
|
||||||
|
+ byte_length = 1
|
||||||
|
+ dec = (known after apply)
|
||||||
|
+ hex = (known after apply)
|
||||||
|
+ id = (known after apply)
|
||||||
|
+ keepers = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Plan: 1 to add, 0 to change, 0 to destroy.
|
||||||
|
```
|
||||||
|
|
||||||
|
* :arrow_forward: To **apply** this plan, comment:
|
||||||
|
* `atlantis apply -d dir2`
|
||||||
|
* :put_litter_in_its_place: To **delete** this plan click [here](lock-url)
|
||||||
|
* :repeat: To **plan** this project again, comment:
|
||||||
|
* `atlantis plan -d dir2`
|
||||||
|
</details>
|
||||||
|
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`
|
||||||
43
server/controllers/events/testdata/test-repos/state-rm-multiple-project/exp-output-plan.txt
vendored
Normal file
43
server/controllers/events/testdata/test-repos/state-rm-multiple-project/exp-output-plan.txt
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
Ran Plan for 2 projects:
|
||||||
|
|
||||||
|
1. dir: `dir1` workspace: `default`
|
||||||
|
1. dir: `dir2` workspace: `default`
|
||||||
|
|
||||||
|
### 1. dir: `dir1` workspace: `default`
|
||||||
|
```diff
|
||||||
|
random_id.dummy: Refreshing state... [id=AA]
|
||||||
|
|
||||||
|
No changes. Your infrastructure matches the configuration.
|
||||||
|
|
||||||
|
Terraform has compared your real infrastructure against your configuration
|
||||||
|
and found no differences, so no changes are needed.
|
||||||
|
```
|
||||||
|
|
||||||
|
* :arrow_forward: To **apply** this plan, comment:
|
||||||
|
* `atlantis apply -d dir1`
|
||||||
|
* :put_litter_in_its_place: To **delete** this plan click [here](lock-url)
|
||||||
|
* :repeat: To **plan** this project again, comment:
|
||||||
|
* `atlantis plan -d dir1`
|
||||||
|
|
||||||
|
---
|
||||||
|
### 2. dir: `dir2` workspace: `default`
|
||||||
|
```diff
|
||||||
|
random_id.dummy: Refreshing state... [id=BB]
|
||||||
|
|
||||||
|
No changes. Your infrastructure matches the configuration.
|
||||||
|
|
||||||
|
Terraform has compared your real infrastructure against your configuration
|
||||||
|
and found no differences, so no changes are needed.
|
||||||
|
```
|
||||||
|
|
||||||
|
* :arrow_forward: To **apply** this plan, comment:
|
||||||
|
* `atlantis apply -d dir2`
|
||||||
|
* :put_litter_in_its_place: To **delete** this plan click [here](lock-url)
|
||||||
|
* :repeat: To **plan** this project again, comment:
|
||||||
|
* `atlantis plan -d dir2`
|
||||||
|
|
||||||
|
---
|
||||||
|
* :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`
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
Ran State for 2 projects:
|
||||||
|
|
||||||
|
1. dir: `dir1` workspace: `default`
|
||||||
|
1. dir: `dir2` workspace: `default`
|
||||||
|
|
||||||
|
### 1. dir: `dir1` workspace: `default`
|
||||||
|
```diff
|
||||||
|
Removed random_id.dummy
|
||||||
|
Successfully removed 1 resource instance(s).
|
||||||
|
```
|
||||||
|
|
||||||
|
:put_litter_in_its_place: A plan file was discarded. Re-plan would be required before applying.
|
||||||
|
|
||||||
|
* :repeat: To **plan** this project again, comment:
|
||||||
|
* `atlantis plan -d dir1`
|
||||||
|
|
||||||
|
---
|
||||||
|
### 2. dir: `dir2` workspace: `default`
|
||||||
|
```diff
|
||||||
|
Removed random_id.dummy
|
||||||
|
Successfully removed 1 resource instance(s).
|
||||||
|
```
|
||||||
|
|
||||||
|
:put_litter_in_its_place: A plan file was discarded. Re-plan would be required before applying.
|
||||||
|
|
||||||
|
* :repeat: To **plan** this project again, comment:
|
||||||
|
* `atlantis plan -d dir2`
|
||||||
|
|
||||||
|
---
|
||||||
60
server/controllers/events/testdata/test-repos/state-rm-single-project/exp-output-autoplan.txt
vendored
Normal file
60
server/controllers/events/testdata/test-repos/state-rm-single-project/exp-output-autoplan.txt
vendored
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
Ran Plan for dir: `.` workspace: `default`
|
||||||
|
|
||||||
|
<details><summary>Show Output</summary>
|
||||||
|
|
||||||
|
```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:
|
||||||
|
|
||||||
|
# random_id.count[0] will be created
|
||||||
|
+ resource "random_id" "count" {
|
||||||
|
+ b64_std = (known after apply)
|
||||||
|
+ b64_url = (known after apply)
|
||||||
|
+ byte_length = 1
|
||||||
|
+ dec = (known after apply)
|
||||||
|
+ hex = (known after apply)
|
||||||
|
+ id = (known after apply)
|
||||||
|
+ keepers = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
# random_id.for_each["default"] will be created
|
||||||
|
+ resource "random_id" "for_each" {
|
||||||
|
+ b64_std = (known after apply)
|
||||||
|
+ b64_url = (known after apply)
|
||||||
|
+ byte_length = 1
|
||||||
|
+ dec = (known after apply)
|
||||||
|
+ hex = (known after apply)
|
||||||
|
+ id = (known after apply)
|
||||||
|
+ keepers = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
# random_id.simple will be created
|
||||||
|
+ resource "random_id" "simple" {
|
||||||
|
+ b64_std = (known after apply)
|
||||||
|
+ b64_url = (known after apply)
|
||||||
|
+ byte_length = 1
|
||||||
|
+ dec = (known after apply)
|
||||||
|
+ hex = (known after apply)
|
||||||
|
+ id = (known after apply)
|
||||||
|
+ keepers = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Plan: 3 to add, 0 to change, 0 to destroy.
|
||||||
|
```
|
||||||
|
|
||||||
|
* :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 .`
|
||||||
|
</details>
|
||||||
|
Plan: 3 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`
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
Ran Import for dir: `.` workspace: `default`
|
||||||
|
|
||||||
|
```diff
|
||||||
|
random_id.count[0]: Importing from ID "BB"...
|
||||||
|
random_id.count[0]: Import prepared!
|
||||||
|
Prepared random_id for import
|
||||||
|
random_id.count[0]: Refreshing state... [id=BB]
|
||||||
|
|
||||||
|
Import successful!
|
||||||
|
|
||||||
|
The resources that were imported are shown above. These resources are now in
|
||||||
|
your Terraform state and will henceforth be managed by Terraform.
|
||||||
|
```
|
||||||
|
|
||||||
|
:put_litter_in_its_place: A plan file was discarded. Re-plan would be required before applying.
|
||||||
|
|
||||||
|
* :repeat: To **plan** this project again, comment:
|
||||||
|
* `atlantis plan -d .`
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
Ran Import for dir: `.` workspace: `default`
|
||||||
|
|
||||||
|
```diff
|
||||||
|
random_id.for_each["overridden"]: Importing from ID "BB"...
|
||||||
|
random_id.for_each["overridden"]: Import prepared!
|
||||||
|
Prepared random_id for import
|
||||||
|
random_id.for_each["overridden"]: Refreshing state... [id=BB]
|
||||||
|
|
||||||
|
Import successful!
|
||||||
|
|
||||||
|
The resources that were imported are shown above. These resources are now in
|
||||||
|
your Terraform state and will henceforth be managed by Terraform.
|
||||||
|
```
|
||||||
|
|
||||||
|
:put_litter_in_its_place: A plan file was discarded. Re-plan would be required before applying.
|
||||||
|
|
||||||
|
* :repeat: To **plan** this project again, comment:
|
||||||
|
* `atlantis plan -d .`
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
Ran Import for dir: `.` workspace: `default`
|
||||||
|
|
||||||
|
```diff
|
||||||
|
random_id.simple: Importing from ID "AA"...
|
||||||
|
random_id.simple: Import prepared!
|
||||||
|
Prepared random_id for import
|
||||||
|
random_id.simple: Refreshing state... [id=AA]
|
||||||
|
|
||||||
|
Import successful!
|
||||||
|
|
||||||
|
The resources that were imported are shown above. These resources are now in
|
||||||
|
your Terraform state and will henceforth be managed by Terraform.
|
||||||
|
```
|
||||||
|
|
||||||
|
:put_litter_in_its_place: A plan file was discarded. Re-plan would be required before applying.
|
||||||
|
|
||||||
|
* :repeat: To **plan** this project again, comment:
|
||||||
|
* `atlantis plan -d .`
|
||||||
3
server/controllers/events/testdata/test-repos/state-rm-single-project/exp-output-merged.txt
vendored
Normal file
3
server/controllers/events/testdata/test-repos/state-rm-single-project/exp-output-merged.txt
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Locks and plans deleted for the projects and workspaces modified in this pull request:
|
||||||
|
|
||||||
|
- dir: `.` workspace: `default`
|
||||||
60
server/controllers/events/testdata/test-repos/state-rm-single-project/exp-output-plan-again.txt
vendored
Normal file
60
server/controllers/events/testdata/test-repos/state-rm-single-project/exp-output-plan-again.txt
vendored
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
Ran Plan for dir: `.` workspace: `default`
|
||||||
|
|
||||||
|
<details><summary>Show Output</summary>
|
||||||
|
|
||||||
|
```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:
|
||||||
|
|
||||||
|
# random_id.count[0] will be created
|
||||||
|
+ resource "random_id" "count" {
|
||||||
|
+ b64_std = (known after apply)
|
||||||
|
+ b64_url = (known after apply)
|
||||||
|
+ byte_length = 1
|
||||||
|
+ dec = (known after apply)
|
||||||
|
+ hex = (known after apply)
|
||||||
|
+ id = (known after apply)
|
||||||
|
+ keepers = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
# random_id.for_each["overridden"] will be created
|
||||||
|
+ resource "random_id" "for_each" {
|
||||||
|
+ b64_std = (known after apply)
|
||||||
|
+ b64_url = (known after apply)
|
||||||
|
+ byte_length = 1
|
||||||
|
+ dec = (known after apply)
|
||||||
|
+ hex = (known after apply)
|
||||||
|
+ id = (known after apply)
|
||||||
|
+ keepers = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
# random_id.simple will be created
|
||||||
|
+ resource "random_id" "simple" {
|
||||||
|
+ b64_std = (known after apply)
|
||||||
|
+ b64_url = (known after apply)
|
||||||
|
+ byte_length = 1
|
||||||
|
+ dec = (known after apply)
|
||||||
|
+ hex = (known after apply)
|
||||||
|
+ id = (known after apply)
|
||||||
|
+ keepers = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Plan: 3 to add, 0 to change, 0 to destroy.
|
||||||
|
```
|
||||||
|
|
||||||
|
* :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 . -- -var var=overridden`
|
||||||
|
</details>
|
||||||
|
Plan: 3 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`
|
||||||
20
server/controllers/events/testdata/test-repos/state-rm-single-project/exp-output-plan.txt
vendored
Normal file
20
server/controllers/events/testdata/test-repos/state-rm-single-project/exp-output-plan.txt
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
Ran Plan for dir: `.` workspace: `default`
|
||||||
|
|
||||||
|
```diff
|
||||||
|
No changes. Your infrastructure matches the configuration.
|
||||||
|
|
||||||
|
Terraform has compared your real infrastructure against your configuration
|
||||||
|
and found no differences, so no changes are needed.
|
||||||
|
```
|
||||||
|
|
||||||
|
* :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 . -- -var var=overridden`
|
||||||
|
|
||||||
|
---
|
||||||
|
* :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`
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
Ran State `rm` for dir: `.` workspace: `default`
|
||||||
|
|
||||||
|
```diff
|
||||||
|
Removed random_id.for_each["overridden"]
|
||||||
|
Successfully removed 1 resource instance(s).
|
||||||
|
```
|
||||||
|
|
||||||
|
:put_litter_in_its_place: A plan file was discarded. Re-plan would be required before applying.
|
||||||
|
|
||||||
|
* :repeat: To **plan** this project again, comment:
|
||||||
|
* `atlantis plan -d .`
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
Ran State `rm` for dir: `.` workspace: `default`
|
||||||
|
|
||||||
|
```diff
|
||||||
|
Removed random_id.count[0]
|
||||||
|
Removed random_id.simple
|
||||||
|
Successfully removed 2 resource instance(s).
|
||||||
|
```
|
||||||
|
|
||||||
|
:put_litter_in_its_place: A plan file was discarded. Re-plan would be required before applying.
|
||||||
|
|
||||||
|
* :repeat: To **plan** this project again, comment:
|
||||||
|
* `atlantis plan -d .`
|
||||||
20
server/controllers/events/testdata/test-repos/state-rm-single-project/main.tf
vendored
Normal file
20
server/controllers/events/testdata/test-repos/state-rm-single-project/main.tf
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
resource "random_id" "simple" {
|
||||||
|
keepers = {}
|
||||||
|
byte_length = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "random_id" "for_each" {
|
||||||
|
for_each = toset([var.var])
|
||||||
|
keepers = {}
|
||||||
|
byte_length = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "random_id" "count" {
|
||||||
|
count = 1
|
||||||
|
keepers = {}
|
||||||
|
byte_length = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "var" {
|
||||||
|
default = "default"
|
||||||
|
}
|
||||||
5
server/controllers/events/testdata/test-repos/state-rm-workspace/atlantis.yaml
vendored
Normal file
5
server/controllers/events/testdata/test-repos/state-rm-workspace/atlantis.yaml
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
version: 3
|
||||||
|
projects:
|
||||||
|
- name: dir1-ops
|
||||||
|
dir: dir1
|
||||||
|
workspace: ops
|
||||||
10
server/controllers/events/testdata/test-repos/state-rm-workspace/dir1/main.tf
vendored
Normal file
10
server/controllers/events/testdata/test-repos/state-rm-workspace/dir1/main.tf
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
resource "random_id" "dummy1" {
|
||||||
|
count = terraform.workspace == "ops" ? 1 : 0
|
||||||
|
|
||||||
|
keepers = {}
|
||||||
|
byte_length = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
output "workspace" {
|
||||||
|
value = terraform.workspace
|
||||||
|
}
|
||||||
18
server/controllers/events/testdata/test-repos/state-rm-workspace/exp-output-import-dummy1.txt
vendored
Normal file
18
server/controllers/events/testdata/test-repos/state-rm-workspace/exp-output-import-dummy1.txt
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
Ran Import for project: `dir1-ops` dir: `dir1` workspace: `ops`
|
||||||
|
|
||||||
|
```diff
|
||||||
|
random_id.dummy1[0]: Importing from ID "AA"...
|
||||||
|
random_id.dummy1[0]: Import prepared!
|
||||||
|
Prepared random_id for import
|
||||||
|
random_id.dummy1[0]: Refreshing state... [id=AA]
|
||||||
|
|
||||||
|
Import successful!
|
||||||
|
|
||||||
|
The resources that were imported are shown above. These resources are now in
|
||||||
|
your Terraform state and will henceforth be managed by Terraform.
|
||||||
|
```
|
||||||
|
|
||||||
|
:put_litter_in_its_place: A plan file was discarded. Re-plan would be required before applying.
|
||||||
|
|
||||||
|
* :repeat: To **plan** this project again, comment:
|
||||||
|
* `atlantis plan -p dir1-ops`
|
||||||
3
server/controllers/events/testdata/test-repos/state-rm-workspace/exp-output-merge.txt
vendored
Normal file
3
server/controllers/events/testdata/test-repos/state-rm-workspace/exp-output-merge.txt
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Locks and plans deleted for the projects and workspaces modified in this pull request:
|
||||||
|
|
||||||
|
- dir: `dir1` workspace: `ops`
|
||||||
38
server/controllers/events/testdata/test-repos/state-rm-workspace/exp-output-plan-again.txt
vendored
Normal file
38
server/controllers/events/testdata/test-repos/state-rm-workspace/exp-output-plan-again.txt
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
Ran Plan for project: `dir1-ops` dir: `dir1` workspace: `ops`
|
||||||
|
|
||||||
|
<details><summary>Show Output</summary>
|
||||||
|
|
||||||
|
```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:
|
||||||
|
|
||||||
|
# random_id.dummy1[0] will be created
|
||||||
|
+ resource "random_id" "dummy1" {
|
||||||
|
+ b64_std = (known after apply)
|
||||||
|
+ b64_url = (known after apply)
|
||||||
|
+ byte_length = 1
|
||||||
|
+ dec = (known after apply)
|
||||||
|
+ hex = (known after apply)
|
||||||
|
+ id = (known after apply)
|
||||||
|
+ keepers = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Plan: 1 to add, 0 to change, 0 to destroy.
|
||||||
|
```
|
||||||
|
|
||||||
|
* :arrow_forward: To **apply** this plan, comment:
|
||||||
|
* `atlantis apply -p dir1-ops`
|
||||||
|
* :put_litter_in_its_place: To **delete** this plan click [here](lock-url)
|
||||||
|
* :repeat: To **plan** this project again, comment:
|
||||||
|
* `atlantis plan -p dir1-ops`
|
||||||
|
</details>
|
||||||
|
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`
|
||||||
22
server/controllers/events/testdata/test-repos/state-rm-workspace/exp-output-plan.txt
vendored
Normal file
22
server/controllers/events/testdata/test-repos/state-rm-workspace/exp-output-plan.txt
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
Ran Plan for project: `dir1-ops` dir: `dir1` workspace: `ops`
|
||||||
|
|
||||||
|
```diff
|
||||||
|
random_id.dummy1[0]: Refreshing state... [id=AA]
|
||||||
|
|
||||||
|
No changes. Your infrastructure matches the configuration.
|
||||||
|
|
||||||
|
Terraform has compared your real infrastructure against your configuration
|
||||||
|
and found no differences, so no changes are needed.
|
||||||
|
```
|
||||||
|
|
||||||
|
* :arrow_forward: To **apply** this plan, comment:
|
||||||
|
* `atlantis apply -p dir1-ops`
|
||||||
|
* :put_litter_in_its_place: To **delete** this plan click [here](lock-url)
|
||||||
|
* :repeat: To **plan** this project again, comment:
|
||||||
|
* `atlantis plan -p dir1-ops`
|
||||||
|
|
||||||
|
---
|
||||||
|
* :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`
|
||||||
11
server/controllers/events/testdata/test-repos/state-rm-workspace/exp-output-state-rm-dummy1.txt
vendored
Normal file
11
server/controllers/events/testdata/test-repos/state-rm-workspace/exp-output-state-rm-dummy1.txt
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
Ran State `rm` for project: `dir1-ops` dir: `dir1` workspace: `ops`
|
||||||
|
|
||||||
|
```diff
|
||||||
|
Removed random_id.dummy1[0]
|
||||||
|
Successfully removed 1 resource instance(s).
|
||||||
|
```
|
||||||
|
|
||||||
|
:put_litter_in_its_place: A plan file was discarded. Re-plan would be required before applying.
|
||||||
|
|
||||||
|
* :repeat: To **plan** this project again, comment:
|
||||||
|
* `atlantis plan -p dir1-ops`
|
||||||
@@ -175,7 +175,8 @@ workflows:
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Import: valid.DefaultImportStage,
|
Import: valid.DefaultImportStage,
|
||||||
|
StateRm: valid.DefaultStateRmStage,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -755,6 +756,9 @@ workflows:
|
|||||||
import:
|
import:
|
||||||
steps:
|
steps:
|
||||||
- import
|
- import
|
||||||
|
state_rm:
|
||||||
|
steps:
|
||||||
|
- state_rm
|
||||||
`,
|
`,
|
||||||
exp: valid.RepoCfg{
|
exp: valid.RepoCfg{
|
||||||
Version: 3,
|
Version: 3,
|
||||||
@@ -808,6 +812,13 @@ workflows:
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
StateRm: valid.Stage{
|
||||||
|
Steps: []valid.Step{
|
||||||
|
{
|
||||||
|
StepName: "state_rm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -843,6 +854,10 @@ workflows:
|
|||||||
steps:
|
steps:
|
||||||
- import:
|
- import:
|
||||||
extra_args: ["a", "b"]
|
extra_args: ["a", "b"]
|
||||||
|
state_rm:
|
||||||
|
steps:
|
||||||
|
- state_rm:
|
||||||
|
extra_args: ["a", "b"]
|
||||||
`,
|
`,
|
||||||
exp: valid.RepoCfg{
|
exp: valid.RepoCfg{
|
||||||
Version: 3,
|
Version: 3,
|
||||||
@@ -899,6 +914,14 @@ workflows:
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
StateRm: valid.Stage{
|
||||||
|
Steps: []valid.Step{
|
||||||
|
{
|
||||||
|
StepName: "state_rm",
|
||||||
|
ExtraArgs: []string{"a", "b"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -923,6 +946,9 @@ workflows:
|
|||||||
import:
|
import:
|
||||||
steps:
|
steps:
|
||||||
- run: echo apply "arg 3"
|
- run: echo apply "arg 3"
|
||||||
|
state_rm:
|
||||||
|
steps:
|
||||||
|
- run: echo apply "arg 4"
|
||||||
`,
|
`,
|
||||||
exp: valid.RepoCfg{
|
exp: valid.RepoCfg{
|
||||||
Version: 3,
|
Version: 3,
|
||||||
@@ -971,6 +997,14 @@ workflows:
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
StateRm: valid.Stage{
|
||||||
|
Steps: []valid.Step{
|
||||||
|
{
|
||||||
|
StepName: "run",
|
||||||
|
RunCommand: "echo apply \"arg 4\"",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1003,6 +1037,11 @@ workflows:
|
|||||||
- env:
|
- env:
|
||||||
name: env_name
|
name: env_name
|
||||||
value: env_value
|
value: env_value
|
||||||
|
state_rm:
|
||||||
|
steps:
|
||||||
|
- env:
|
||||||
|
name: env_name
|
||||||
|
value: env_value
|
||||||
`,
|
`,
|
||||||
exp: valid.RepoCfg{
|
exp: valid.RepoCfg{
|
||||||
Version: 3,
|
Version: 3,
|
||||||
@@ -1055,6 +1094,15 @@ workflows:
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
StateRm: valid.Stage{
|
||||||
|
Steps: []valid.Step{
|
||||||
|
{
|
||||||
|
StepName: "env",
|
||||||
|
EnvVarName: "env_name",
|
||||||
|
EnvVarValue: "env_value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1194,6 +1242,17 @@ func TestParseGlobalCfg(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
StateRm: valid.Stage{
|
||||||
|
Steps: []valid.Step{
|
||||||
|
{
|
||||||
|
StepName: "run",
|
||||||
|
RunCommand: "custom command",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StepName: "state_rm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
conftestVersion, _ := version.NewVersion("v1.0.0")
|
conftestVersion, _ := version.NewVersion("v1.0.0")
|
||||||
@@ -1291,6 +1350,7 @@ workflows:
|
|||||||
plan:
|
plan:
|
||||||
policy_check:
|
policy_check:
|
||||||
import:
|
import:
|
||||||
|
state_rm:
|
||||||
`,
|
`,
|
||||||
exp: valid.GlobalCfg{
|
exp: valid.GlobalCfg{
|
||||||
Repos: defaultCfg.Repos,
|
Repos: defaultCfg.Repos,
|
||||||
@@ -1312,6 +1372,8 @@ workflows:
|
|||||||
steps:
|
steps:
|
||||||
import:
|
import:
|
||||||
steps:
|
steps:
|
||||||
|
state_rm:
|
||||||
|
steps:
|
||||||
`,
|
`,
|
||||||
exp: valid.GlobalCfg{
|
exp: valid.GlobalCfg{
|
||||||
Repos: defaultCfg.Repos,
|
Repos: defaultCfg.Repos,
|
||||||
@@ -1362,6 +1424,10 @@ workflows:
|
|||||||
steps:
|
steps:
|
||||||
- run: custom command
|
- run: custom command
|
||||||
- import
|
- import
|
||||||
|
state_rm:
|
||||||
|
steps:
|
||||||
|
- run: custom command
|
||||||
|
- state_rm
|
||||||
policies:
|
policies:
|
||||||
conftest_version: v1.0.0
|
conftest_version: v1.0.0
|
||||||
policy_sets:
|
policy_sets:
|
||||||
@@ -1454,6 +1520,8 @@ workflows:
|
|||||||
steps: []
|
steps: []
|
||||||
import:
|
import:
|
||||||
steps: []
|
steps: []
|
||||||
|
state_rm:
|
||||||
|
steps: []
|
||||||
`,
|
`,
|
||||||
exp: valid.GlobalCfg{
|
exp: valid.GlobalCfg{
|
||||||
Repos: []valid.Repo{
|
Repos: []valid.Repo{
|
||||||
@@ -1481,6 +1549,9 @@ workflows:
|
|||||||
Import: valid.Stage{
|
Import: valid.Stage{
|
||||||
Steps: nil,
|
Steps: nil,
|
||||||
},
|
},
|
||||||
|
StateRm: valid.Stage{
|
||||||
|
Steps: nil,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
AllowedWorkflows: []string{},
|
AllowedWorkflows: []string{},
|
||||||
AllowedOverrides: []string{},
|
AllowedOverrides: []string{},
|
||||||
@@ -1599,6 +1670,14 @@ func TestParserValidator_ParseGlobalCfgJSON(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
StateRm: valid.Stage{
|
||||||
|
Steps: []valid.Step{
|
||||||
|
{
|
||||||
|
StepName: "run",
|
||||||
|
RunCommand: "custom state_rm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
conftestVersion, _ := version.NewVersion("v1.0.0")
|
conftestVersion, _ := version.NewVersion("v1.0.0")
|
||||||
@@ -1661,6 +1740,11 @@ func TestParserValidator_ParseGlobalCfgJSON(t *testing.T) {
|
|||||||
"steps": [
|
"steps": [
|
||||||
{"run": "custom import"}
|
{"run": "custom import"}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"state_rm": {
|
||||||
|
"steps": [
|
||||||
|
{"run": "custom state_rm"}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1833,5 +1917,6 @@ func defaultWorkflow(name string) valid.Workflow {
|
|||||||
Plan: valid.DefaultPlanStage,
|
Plan: valid.DefaultPlanStage,
|
||||||
PolicyCheck: valid.DefaultPolicyCheckStage,
|
PolicyCheck: valid.DefaultPolicyCheckStage,
|
||||||
Import: valid.DefaultImportStage,
|
Import: valid.DefaultImportStage,
|
||||||
|
StateRm: valid.DefaultStateRmStage,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -308,6 +308,7 @@ func TestConfig_ToValid(t *testing.T) {
|
|||||||
Apply: nil,
|
Apply: nil,
|
||||||
PolicyCheck: nil,
|
PolicyCheck: nil,
|
||||||
Import: nil,
|
Import: nil,
|
||||||
|
StateRm: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -322,6 +323,7 @@ func TestConfig_ToValid(t *testing.T) {
|
|||||||
PolicyCheck: valid.DefaultPolicyCheckStage,
|
PolicyCheck: valid.DefaultPolicyCheckStage,
|
||||||
Apply: valid.DefaultApplyStage,
|
Apply: valid.DefaultApplyStage,
|
||||||
Import: valid.DefaultImportStage,
|
Import: valid.DefaultImportStage,
|
||||||
|
StateRm: valid.DefaultStateRmStage,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -362,6 +364,13 @@ func TestConfig_ToValid(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
StateRm: &raw.Stage{
|
||||||
|
Steps: []raw.Step{
|
||||||
|
{
|
||||||
|
Key: String("state_rm"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Projects: []raw.Project{
|
Projects: []raw.Project{
|
||||||
@@ -405,6 +414,13 @@ func TestConfig_ToValid(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
StateRm: valid.Stage{
|
||||||
|
Steps: []valid.Step{
|
||||||
|
{
|
||||||
|
StepName: "state_rm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Projects: []valid.Project{
|
Projects: []valid.Project{
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ const (
|
|||||||
EnvStepName = "env"
|
EnvStepName = "env"
|
||||||
MultiEnvStepName = "multienv"
|
MultiEnvStepName = "multienv"
|
||||||
ImportStepName = "import"
|
ImportStepName = "import"
|
||||||
|
StateRmStepName = "state_rm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Step represents a single action/command to perform. In YAML, it can be set as
|
// Step represents a single action/command to perform. In YAML, it can be set as
|
||||||
@@ -90,7 +91,8 @@ func (s Step) validStepName(stepName string) bool {
|
|||||||
stepName == MultiEnvStepName ||
|
stepName == MultiEnvStepName ||
|
||||||
stepName == ShowStepName ||
|
stepName == ShowStepName ||
|
||||||
stepName == PolicyCheckStepName ||
|
stepName == PolicyCheckStepName ||
|
||||||
stepName == ImportStepName
|
stepName == ImportStepName ||
|
||||||
|
stepName == StateRmStepName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Step) Validate() error {
|
func (s Step) Validate() error {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ type Workflow struct {
|
|||||||
Plan *Stage `yaml:"plan,omitempty" json:"plan,omitempty"`
|
Plan *Stage `yaml:"plan,omitempty" json:"plan,omitempty"`
|
||||||
PolicyCheck *Stage `yaml:"policy_check,omitempty" json:"policy_check,omitempty"`
|
PolicyCheck *Stage `yaml:"policy_check,omitempty" json:"policy_check,omitempty"`
|
||||||
Import *Stage `yaml:"import,omitempty" json:"import,omitempty"`
|
Import *Stage `yaml:"import,omitempty" json:"import,omitempty"`
|
||||||
|
StateRm *Stage `yaml:"state_rm,omitempty" json:"state_rm,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w Workflow) Validate() error {
|
func (w Workflow) Validate() error {
|
||||||
@@ -18,6 +19,7 @@ func (w Workflow) Validate() error {
|
|||||||
validation.Field(&w.Plan),
|
validation.Field(&w.Plan),
|
||||||
validation.Field(&w.PolicyCheck),
|
validation.Field(&w.PolicyCheck),
|
||||||
validation.Field(&w.Import),
|
validation.Field(&w.Import),
|
||||||
|
validation.Field(&w.StateRm),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,6 +40,7 @@ func (w Workflow) ToValid(name string) valid.Workflow {
|
|||||||
v.Plan = w.toValidStage(w.Plan, valid.DefaultPlanStage)
|
v.Plan = w.toValidStage(w.Plan, valid.DefaultPlanStage)
|
||||||
v.PolicyCheck = w.toValidStage(w.PolicyCheck, valid.DefaultPolicyCheckStage)
|
v.PolicyCheck = w.toValidStage(w.PolicyCheck, valid.DefaultPolicyCheckStage)
|
||||||
v.Import = w.toValidStage(w.Import, valid.DefaultImportStage)
|
v.Import = w.toValidStage(w.Import, valid.DefaultImportStage)
|
||||||
|
v.StateRm = w.toValidStage(w.StateRm, valid.DefaultStateRmStage)
|
||||||
|
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ func TestWorkflow_ToValid(t *testing.T) {
|
|||||||
Plan: valid.DefaultPlanStage,
|
Plan: valid.DefaultPlanStage,
|
||||||
PolicyCheck: valid.DefaultPolicyCheckStage,
|
PolicyCheck: valid.DefaultPolicyCheckStage,
|
||||||
Import: valid.DefaultImportStage,
|
Import: valid.DefaultImportStage,
|
||||||
|
StateRm: valid.DefaultStateRmStage,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -182,6 +183,13 @@ func TestWorkflow_ToValid(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
StateRm: &raw.Stage{
|
||||||
|
Steps: []raw.Step{
|
||||||
|
{
|
||||||
|
Key: String("state_rm"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
exp: valid.Workflow{
|
exp: valid.Workflow{
|
||||||
Apply: valid.Stage{
|
Apply: valid.Stage{
|
||||||
@@ -212,6 +220,13 @@ func TestWorkflow_ToValid(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
StateRm: valid.Stage{
|
||||||
|
Steps: []valid.Step{
|
||||||
|
{
|
||||||
|
StepName: "state_rm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,6 +149,18 @@ var DefaultImportStage = Stage{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefaultStateRmStage is the Atlantis default state_rm stage.
|
||||||
|
var DefaultStateRmStage = Stage{
|
||||||
|
Steps: []Step{
|
||||||
|
{
|
||||||
|
StepName: "init",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StepName: "state_rm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// Deprecated: use NewGlobalCfgFromArgs
|
// Deprecated: use NewGlobalCfgFromArgs
|
||||||
func NewGlobalCfgWithHooks(allowRepoCfg bool, mergeableReq bool, approvedReq bool, unDivergedReq bool, preWorkflowHooks []*WorkflowHook, postWorkflowHooks []*WorkflowHook) GlobalCfg {
|
func NewGlobalCfgWithHooks(allowRepoCfg bool, mergeableReq bool, approvedReq bool, unDivergedReq bool, preWorkflowHooks []*WorkflowHook, postWorkflowHooks []*WorkflowHook) GlobalCfg {
|
||||||
return NewGlobalCfgFromArgs(GlobalCfgArgs{
|
return NewGlobalCfgFromArgs(GlobalCfgArgs{
|
||||||
@@ -194,6 +206,7 @@ func NewGlobalCfgFromArgs(args GlobalCfgArgs) GlobalCfg {
|
|||||||
Plan: DefaultPlanStage,
|
Plan: DefaultPlanStage,
|
||||||
PolicyCheck: DefaultPolicyCheckStage,
|
PolicyCheck: DefaultPolicyCheckStage,
|
||||||
Import: DefaultImportStage,
|
Import: DefaultImportStage,
|
||||||
|
StateRm: DefaultStateRmStage,
|
||||||
}
|
}
|
||||||
// Must construct slices here instead of using a `var` declaration because
|
// Must construct slices here instead of using a `var` declaration because
|
||||||
// we treat nil slices differently.
|
// we treat nil slices differently.
|
||||||
|
|||||||
@@ -55,6 +55,16 @@ func TestNewGlobalCfg(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
StateRm: valid.Stage{
|
||||||
|
Steps: []valid.Step{
|
||||||
|
{
|
||||||
|
StepName: "init",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StepName: "state_rm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
baseCfg := valid.GlobalCfg{
|
baseCfg := valid.GlobalCfg{
|
||||||
Repos: []valid.Repo{
|
Repos: []valid.Repo{
|
||||||
@@ -631,6 +641,7 @@ policies:
|
|||||||
Plan: valid.DefaultPlanStage,
|
Plan: valid.DefaultPlanStage,
|
||||||
PolicyCheck: valid.DefaultPolicyCheckStage,
|
PolicyCheck: valid.DefaultPolicyCheckStage,
|
||||||
Import: valid.DefaultImportStage,
|
Import: valid.DefaultImportStage,
|
||||||
|
StateRm: valid.DefaultStateRmStage,
|
||||||
},
|
},
|
||||||
PolicySets: valid.PolicySets{
|
PolicySets: valid.PolicySets{
|
||||||
Version: nil,
|
Version: nil,
|
||||||
@@ -675,6 +686,7 @@ policies:
|
|||||||
Plan: valid.DefaultPlanStage,
|
Plan: valid.DefaultPlanStage,
|
||||||
PolicyCheck: valid.DefaultPolicyCheckStage,
|
PolicyCheck: valid.DefaultPolicyCheckStage,
|
||||||
Import: valid.DefaultImportStage,
|
Import: valid.DefaultImportStage,
|
||||||
|
StateRm: valid.DefaultStateRmStage,
|
||||||
},
|
},
|
||||||
PolicySets: valid.PolicySets{
|
PolicySets: valid.PolicySets{
|
||||||
Version: version,
|
Version: version,
|
||||||
@@ -736,6 +748,7 @@ func TestGlobalCfg_MergeProjectCfg(t *testing.T) {
|
|||||||
PolicyCheck: valid.DefaultPolicyCheckStage,
|
PolicyCheck: valid.DefaultPolicyCheckStage,
|
||||||
Plan: valid.DefaultPlanStage,
|
Plan: valid.DefaultPlanStage,
|
||||||
Import: valid.DefaultImportStage,
|
Import: valid.DefaultImportStage,
|
||||||
|
StateRm: valid.DefaultStateRmStage,
|
||||||
}
|
}
|
||||||
cases := map[string]struct {
|
cases := map[string]struct {
|
||||||
gCfg string
|
gCfg string
|
||||||
@@ -774,7 +787,8 @@ workflows:
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Import: valid.DefaultImportStage,
|
Import: valid.DefaultImportStage,
|
||||||
|
StateRm: valid.DefaultStateRmStage,
|
||||||
},
|
},
|
||||||
RepoRelDir: ".",
|
RepoRelDir: ".",
|
||||||
Workspace: "default",
|
Workspace: "default",
|
||||||
|
|||||||
@@ -170,4 +170,5 @@ type Workflow struct {
|
|||||||
Plan Stage
|
Plan Stage
|
||||||
PolicyCheck Stage
|
PolicyCheck Stage
|
||||||
Import Stage
|
Import Stage
|
||||||
|
StateRm Stage
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import (
|
|||||||
|
|
||||||
"github.com/hashicorp/go-version"
|
"github.com/hashicorp/go-version"
|
||||||
. "github.com/petergtz/pegomock"
|
. "github.com/petergtz/pegomock"
|
||||||
"github.com/runatlantis/atlantis/server/core/runtime/mocks/matchers"
|
runtimematchers "github.com/runatlantis/atlantis/server/core/runtime/mocks/matchers"
|
||||||
"github.com/runatlantis/atlantis/server/core/terraform/mocks"
|
"github.com/runatlantis/atlantis/server/core/terraform/mocks"
|
||||||
matchers2 "github.com/runatlantis/atlantis/server/core/terraform/mocks/matchers"
|
tfmatchers "github.com/runatlantis/atlantis/server/core/terraform/mocks/matchers"
|
||||||
"github.com/runatlantis/atlantis/server/events/command"
|
"github.com/runatlantis/atlantis/server/events/command"
|
||||||
"github.com/runatlantis/atlantis/server/logging"
|
"github.com/runatlantis/atlantis/server/logging"
|
||||||
. "github.com/runatlantis/atlantis/testing"
|
. "github.com/runatlantis/atlantis/testing"
|
||||||
@@ -35,7 +35,7 @@ func TestImportStepRunner_Run_Success(t *testing.T) {
|
|||||||
tfVersion, _ := version.NewVersion("0.15.0")
|
tfVersion, _ := version.NewVersion("0.15.0")
|
||||||
s := NewImportStepRunner(terraform, tfVersion)
|
s := NewImportStepRunner(terraform, tfVersion)
|
||||||
|
|
||||||
When(terraform.RunCommandWithVersion(matchers.AnyCommandProjectContext(), AnyString(), AnyStringSlice(), matchers2.AnyMapOfStringToString(), matchers2.AnyPtrToGoVersionVersion(), AnyString())).
|
When(terraform.RunCommandWithVersion(runtimematchers.AnyCommandProjectContext(), AnyString(), AnyStringSlice(), tfmatchers.AnyMapOfStringToString(), tfmatchers.AnyPtrToGoVersionVersion(), AnyString())).
|
||||||
ThenReturn("output", nil)
|
ThenReturn("output", nil)
|
||||||
output, err := s.Run(context, []string{}, tmpDir, map[string]string(nil))
|
output, err := s.Run(context, []string{}, tmpDir, map[string]string(nil))
|
||||||
Ok(t, err)
|
Ok(t, err)
|
||||||
@@ -65,7 +65,7 @@ func TestImportStepRunner_Run_Workspace(t *testing.T) {
|
|||||||
tfVersion, _ := version.NewVersion("0.15.0")
|
tfVersion, _ := version.NewVersion("0.15.0")
|
||||||
s := NewImportStepRunner(terraform, tfVersion)
|
s := NewImportStepRunner(terraform, tfVersion)
|
||||||
|
|
||||||
When(terraform.RunCommandWithVersion(matchers.AnyCommandProjectContext(), AnyString(), AnyStringSlice(), matchers2.AnyMapOfStringToString(), matchers2.AnyPtrToGoVersionVersion(), AnyString())).
|
When(terraform.RunCommandWithVersion(runtimematchers.AnyCommandProjectContext(), AnyString(), AnyStringSlice(), runtimematchers.AnyMapOfStringToString(), runtimematchers.AnyPtrToGoVersionVersion(), AnyString())).
|
||||||
ThenReturn("output", nil)
|
ThenReturn("output", nil)
|
||||||
output, err := s.Run(context, []string{}, tmpDir, map[string]string(nil))
|
output, err := s.Run(context, []string{}, tmpDir, map[string]string(nil))
|
||||||
Ok(t, err)
|
Ok(t, err)
|
||||||
|
|||||||
46
server/core/runtime/state_rm_step_runner.go
Normal file
46
server/core/runtime/state_rm_step_runner.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
version "github.com/hashicorp/go-version"
|
||||||
|
"github.com/runatlantis/atlantis/server/events/command"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stateRmStepRunner struct {
|
||||||
|
terraformExecutor TerraformExec
|
||||||
|
defaultTFVersion *version.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStateRmStepRunner(terraformExecutor TerraformExec, defaultTfVersion *version.Version) Runner {
|
||||||
|
runner := &stateRmStepRunner{
|
||||||
|
terraformExecutor: terraformExecutor,
|
||||||
|
defaultTFVersion: defaultTfVersion,
|
||||||
|
}
|
||||||
|
return NewWorkspaceStepRunnerDelegate(terraformExecutor, defaultTfVersion, runner)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *stateRmStepRunner) Run(ctx command.ProjectContext, extraArgs []string, path string, envs map[string]string) (string, error) {
|
||||||
|
tfVersion := p.defaultTFVersion
|
||||||
|
if ctx.TerraformVersion != nil {
|
||||||
|
tfVersion = ctx.TerraformVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
stateRmCmd := []string{"state", "rm"}
|
||||||
|
stateRmCmd = append(stateRmCmd, extraArgs...)
|
||||||
|
stateRmCmd = append(stateRmCmd, ctx.EscapedCommentArgs...)
|
||||||
|
out, err := p.terraformExecutor.RunCommandWithVersion(ctx, filepath.Clean(path), stateRmCmd, envs, tfVersion, ctx.Workspace)
|
||||||
|
|
||||||
|
// If the state rm was successful and a plan file exists, delete the plan.
|
||||||
|
planPath := filepath.Join(path, GetPlanFilename(ctx.Workspace, ctx.ProjectName))
|
||||||
|
if err == nil {
|
||||||
|
if _, planPathErr := os.Stat(planPath); !os.IsNotExist(planPathErr) {
|
||||||
|
ctx.Log.Info("state rm successful, deleting planfile")
|
||||||
|
if removeErr := os.Remove(planPath); removeErr != nil {
|
||||||
|
ctx.Log.Warn("failed to delete planfile after successful state rm: %s", removeErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
84
server/core/runtime/state_rm_step_runner_test.go
Normal file
84
server/core/runtime/state_rm_step_runner_test.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-version"
|
||||||
|
. "github.com/petergtz/pegomock"
|
||||||
|
runtimematchers "github.com/runatlantis/atlantis/server/core/runtime/mocks/matchers"
|
||||||
|
"github.com/runatlantis/atlantis/server/core/terraform/mocks"
|
||||||
|
tfmatchers "github.com/runatlantis/atlantis/server/core/terraform/mocks/matchers"
|
||||||
|
"github.com/runatlantis/atlantis/server/events/command"
|
||||||
|
"github.com/runatlantis/atlantis/server/logging"
|
||||||
|
. "github.com/runatlantis/atlantis/testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStateRmStepRunner_Run_Success(t *testing.T) {
|
||||||
|
logger := logging.NewNoopLogger(t)
|
||||||
|
workspace := "default"
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
planPath := filepath.Join(tmpDir, fmt.Sprintf("%s.tfplan", workspace))
|
||||||
|
err := os.WriteFile(planPath, nil, 0600)
|
||||||
|
Ok(t, err)
|
||||||
|
|
||||||
|
context := command.ProjectContext{
|
||||||
|
Log: logger,
|
||||||
|
EscapedCommentArgs: []string{"-lock=false", "addr1", "addr2", "addr3"},
|
||||||
|
Workspace: workspace,
|
||||||
|
}
|
||||||
|
|
||||||
|
RegisterMockTestingT(t)
|
||||||
|
terraform := mocks.NewMockClient()
|
||||||
|
tfVersion, _ := version.NewVersion("0.15.0")
|
||||||
|
s := NewStateRmStepRunner(terraform, tfVersion)
|
||||||
|
|
||||||
|
When(terraform.RunCommandWithVersion(runtimematchers.AnyCommandProjectContext(), AnyString(), AnyStringSlice(), tfmatchers.AnyMapOfStringToString(), tfmatchers.AnyPtrToGoVersionVersion(), AnyString())).
|
||||||
|
ThenReturn("output", nil)
|
||||||
|
output, err := s.Run(context, []string{}, tmpDir, map[string]string(nil))
|
||||||
|
Ok(t, err)
|
||||||
|
Equals(t, "output", output)
|
||||||
|
commands := []string{"state", "rm", "-lock=false", "addr1", "addr2", "addr3"}
|
||||||
|
terraform.VerifyWasCalledOnce().RunCommandWithVersion(context, tmpDir, commands, map[string]string(nil), tfVersion, "default")
|
||||||
|
_, err = os.Stat(planPath)
|
||||||
|
Assert(t, os.IsNotExist(err), "planfile should be deleted")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStateRmStepRunner_Run_Workspace(t *testing.T) {
|
||||||
|
logger := logging.NewNoopLogger(t)
|
||||||
|
workspace := "something"
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
planPath := filepath.Join(tmpDir, fmt.Sprintf("%s.tfplan", workspace))
|
||||||
|
err := os.WriteFile(planPath, nil, 0600)
|
||||||
|
Ok(t, err)
|
||||||
|
|
||||||
|
context := command.ProjectContext{
|
||||||
|
Log: logger,
|
||||||
|
EscapedCommentArgs: []string{"-lock=false", "addr1", "addr2", "addr3"},
|
||||||
|
Workspace: workspace,
|
||||||
|
}
|
||||||
|
|
||||||
|
RegisterMockTestingT(t)
|
||||||
|
terraform := mocks.NewMockClient()
|
||||||
|
tfVersion, _ := version.NewVersion("0.15.0")
|
||||||
|
s := NewStateRmStepRunner(terraform, tfVersion)
|
||||||
|
|
||||||
|
When(terraform.RunCommandWithVersion(runtimematchers.AnyCommandProjectContext(), AnyString(), AnyStringSlice(), runtimematchers.AnyMapOfStringToString(), runtimematchers.AnyPtrToGoVersionVersion(), AnyString())).
|
||||||
|
ThenReturn("output", nil)
|
||||||
|
output, err := s.Run(context, []string{}, tmpDir, map[string]string(nil))
|
||||||
|
Ok(t, err)
|
||||||
|
Equals(t, "output", output)
|
||||||
|
|
||||||
|
// switch workspace
|
||||||
|
terraform.VerifyWasCalledOnce().RunCommandWithVersion(context, tmpDir, []string{"workspace", "show"}, map[string]string(nil), tfVersion, workspace)
|
||||||
|
terraform.VerifyWasCalledOnce().RunCommandWithVersion(context, tmpDir, []string{"workspace", "select", workspace}, map[string]string(nil), tfVersion, workspace)
|
||||||
|
|
||||||
|
// exec state rm
|
||||||
|
commands := []string{"state", "rm", "-lock=false", "addr1", "addr2", "addr3"}
|
||||||
|
terraform.VerifyWasCalledOnce().RunCommandWithVersion(context, tmpDir, commands, map[string]string(nil), tfVersion, workspace)
|
||||||
|
|
||||||
|
_, err = os.Stat(planPath)
|
||||||
|
Assert(t, os.IsNotExist(err), "planfile should be deleted")
|
||||||
|
}
|
||||||
@@ -28,9 +28,16 @@ const (
|
|||||||
Version
|
Version
|
||||||
// Import is a command to run terraform import
|
// Import is a command to run terraform import
|
||||||
Import
|
Import
|
||||||
|
// State is a command to run terraform state rm
|
||||||
|
State
|
||||||
// Adding more? Don't forget to update String() below
|
// Adding more? Don't forget to update String() below
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ArgCount struct {
|
||||||
|
Min int
|
||||||
|
Max int
|
||||||
|
}
|
||||||
|
|
||||||
// AllCommentCommands are list of commands that can be run from a comment.
|
// AllCommentCommands are list of commands that can be run from a comment.
|
||||||
var AllCommentCommands = []Name{
|
var AllCommentCommands = []Name{
|
||||||
Version,
|
Version,
|
||||||
@@ -39,6 +46,7 @@ var AllCommentCommands = []Name{
|
|||||||
Unlock,
|
Unlock,
|
||||||
ApprovePolicies,
|
ApprovePolicies,
|
||||||
Import,
|
Import,
|
||||||
|
State,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TitleString returns the string representation in title form.
|
// TitleString returns the string representation in title form.
|
||||||
@@ -64,6 +72,8 @@ func (c Name) String() string {
|
|||||||
return "version"
|
return "version"
|
||||||
case Import:
|
case Import:
|
||||||
return "import"
|
return "import"
|
||||||
|
case State:
|
||||||
|
return "state"
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -73,11 +83,53 @@ func (c Name) DefaultUsage() string {
|
|||||||
switch c {
|
switch c {
|
||||||
case Import:
|
case Import:
|
||||||
return "import ADDRESS ID"
|
return "import ADDRESS ID"
|
||||||
|
case State:
|
||||||
|
return "state [rm ADDRESS...]"
|
||||||
default:
|
default:
|
||||||
return c.String()
|
return c.String()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SubCommands returns the list of sub commands for the command
|
||||||
|
func (c Name) SubCommands() []string {
|
||||||
|
switch c {
|
||||||
|
case State:
|
||||||
|
return []string{"rm"}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommandArgCount returns the number of required arguments for the command
|
||||||
|
func (c Name) CommandArgCount(subCommand string) (*ArgCount, error) {
|
||||||
|
switch c {
|
||||||
|
case Import:
|
||||||
|
return &ArgCount{2, 2}, nil // "atlantis import ADDRESS ID"
|
||||||
|
case State:
|
||||||
|
if subCommand == "rm" {
|
||||||
|
return &ArgCount{1, -1}, nil // "atlantis state rm ADDRESS..."
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("command arg count unknown sub command: %s", subCommand)
|
||||||
|
default:
|
||||||
|
return &ArgCount{0, 0}, nil // other command doesn't require any args
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsMatchCount returns true if the number of arguments matches the requirement
|
||||||
|
func (a ArgCount) IsMatchCount(count int) bool {
|
||||||
|
if a.Min != -1 {
|
||||||
|
if count < a.Min {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if a.Max != -1 {
|
||||||
|
if count > a.Max {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// ParseCommandName parses raw name into a command name.
|
// ParseCommandName parses raw name into a command name.
|
||||||
func ParseCommandName(name string) (Name, error) {
|
func ParseCommandName(name string) (Name, error) {
|
||||||
switch name {
|
switch name {
|
||||||
@@ -95,6 +147,8 @@ func ParseCommandName(name string) (Name, error) {
|
|||||||
return Version, nil
|
return Version, nil
|
||||||
case "import":
|
case "import":
|
||||||
return Import, nil
|
return Import, nil
|
||||||
|
case "state":
|
||||||
|
return State, nil
|
||||||
}
|
}
|
||||||
return -1, fmt.Errorf("unknown command name: %s", name)
|
return -1, fmt.Errorf("unknown command name: %s", name)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package command_test
|
package command_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/runatlantis/atlantis/server/events/command"
|
"github.com/runatlantis/atlantis/server/events/command"
|
||||||
@@ -36,6 +39,7 @@ func TestName_String(t *testing.T) {
|
|||||||
{command.ApprovePolicies, "approve_policies"},
|
{command.ApprovePolicies, "approve_policies"},
|
||||||
{command.Version, "version"},
|
{command.Version, "version"},
|
||||||
{command.Import, "import"},
|
{command.Import, "import"},
|
||||||
|
{command.State, "state"},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.want, func(t *testing.T) {
|
t.Run(tt.want, func(t *testing.T) {
|
||||||
@@ -58,6 +62,7 @@ func TestName_DefaultUsage(t *testing.T) {
|
|||||||
{command.ApprovePolicies, "approve_policies"},
|
{command.ApprovePolicies, "approve_policies"},
|
||||||
{command.Version, "version"},
|
{command.Version, "version"},
|
||||||
{command.Import, "import ADDRESS ID"},
|
{command.Import, "import ADDRESS ID"},
|
||||||
|
{command.State, "state [rm ADDRESS...]"},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.c.String(), func(t *testing.T) {
|
t.Run(tt.c.String(), func(t *testing.T) {
|
||||||
@@ -68,6 +73,98 @@ func TestName_DefaultUsage(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestName_SubCommands(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
c command.Name
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{c: command.Apply},
|
||||||
|
{c: command.Plan},
|
||||||
|
{c: command.Unlock},
|
||||||
|
{c: command.PolicyCheck},
|
||||||
|
{c: command.ApprovePolicies},
|
||||||
|
{c: command.Version},
|
||||||
|
{c: command.Import},
|
||||||
|
{c: command.State, want: []string{"rm"}},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.c.String(), func(t *testing.T) {
|
||||||
|
if got := tt.c.SubCommands(); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("SubCommands() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestName_CommandArgCount(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
c command.Name
|
||||||
|
subCommand string
|
||||||
|
want *command.ArgCount
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{c: command.Apply, want: &command.ArgCount{}},
|
||||||
|
{c: command.Plan, want: &command.ArgCount{}},
|
||||||
|
{c: command.Unlock, want: &command.ArgCount{}},
|
||||||
|
{c: command.PolicyCheck, want: &command.ArgCount{}},
|
||||||
|
{c: command.ApprovePolicies, want: &command.ArgCount{}},
|
||||||
|
{c: command.Version, want: &command.ArgCount{}},
|
||||||
|
{c: command.Import, want: &command.ArgCount{Min: 2, Max: 2}},
|
||||||
|
{c: command.State, subCommand: "rm", want: &command.ArgCount{Min: 1, Max: -1}},
|
||||||
|
{c: command.State, subCommand: "unknown", wantErr: true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(fmt.Sprintf("%s %s", tt.c, tt.subCommand), func(t *testing.T) {
|
||||||
|
got, err := tt.c.CommandArgCount(tt.subCommand)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("CommandArgCount() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("CommandArgCount() got = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArgCount_IsMatchCount(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
Min int
|
||||||
|
Max int
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
count int
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{name: "[0,0] success", fields: fields{Min: 0, Max: 0}, count: 0, want: true},
|
||||||
|
{name: "[0,0] failure", fields: fields{Min: 0, Max: 0}, count: 1, want: false},
|
||||||
|
{name: "[1,1] success", fields: fields{Min: 1, Max: 1}, count: 1, want: true},
|
||||||
|
{name: "[1,1] failure1", fields: fields{Min: 1, Max: 1}, count: 0, want: false},
|
||||||
|
{name: "[1,1] failure2", fields: fields{Min: 1, Max: 1}, count: 2, want: false},
|
||||||
|
{name: "[-inf,1] success1", fields: fields{Min: -1, Max: 1}, count: 0, want: true},
|
||||||
|
{name: "[-inf,1] success2", fields: fields{Min: -1, Max: 1}, count: 1, want: true},
|
||||||
|
{name: "[-inf,1] failure", fields: fields{Min: -1, Max: 1}, count: 2, want: false},
|
||||||
|
{name: "[1,inf] success1", fields: fields{Min: 1, Max: -1}, count: 1, want: true},
|
||||||
|
{name: "[1,inf] success2", fields: fields{Min: 1, Max: -1}, count: math.MaxInt, want: true},
|
||||||
|
{name: "[1,inf] failure", fields: fields{Min: 1, Max: -1}, count: 0, want: false},
|
||||||
|
{name: "[-inf,inf] success", fields: fields{Min: -1, Max: -1}, count: 0, want: true},
|
||||||
|
{name: "[-inf,inf] success", fields: fields{Min: -1, Max: -1}, count: math.MaxInt, want: true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
a := command.ArgCount{
|
||||||
|
Min: tt.fields.Min,
|
||||||
|
Max: tt.fields.Max,
|
||||||
|
}
|
||||||
|
if got := a.IsMatchCount(tt.count); got != tt.want {
|
||||||
|
t.Errorf("IsMatchCount() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseCommandName(t *testing.T) {
|
func TestParseCommandName(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
exp command.Name
|
exp command.Name
|
||||||
@@ -80,6 +177,7 @@ func TestParseCommandName(t *testing.T) {
|
|||||||
{command.ApprovePolicies, "approve_policies"},
|
{command.ApprovePolicies, "approve_policies"},
|
||||||
{command.Version, "version"},
|
{command.Version, "version"},
|
||||||
{command.Import, "import"},
|
{command.Import, "import"},
|
||||||
|
{command.State, "state"},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
// ProjectResult is the result of executing a plan/policy_check/apply for a specific project.
|
// ProjectResult is the result of executing a plan/policy_check/apply for a specific project.
|
||||||
type ProjectResult struct {
|
type ProjectResult struct {
|
||||||
Command Name
|
Command Name
|
||||||
|
SubCommand string
|
||||||
RepoRelDir string
|
RepoRelDir string
|
||||||
Workspace string
|
Workspace string
|
||||||
Error error
|
Error error
|
||||||
@@ -16,6 +17,7 @@ type ProjectResult struct {
|
|||||||
ApplySuccess string
|
ApplySuccess string
|
||||||
VersionSuccess string
|
VersionSuccess string
|
||||||
ImportSuccess *models.ImportSuccess
|
ImportSuccess *models.ImportSuccess
|
||||||
|
StateRmSuccess *models.StateRmSuccess
|
||||||
ProjectName string
|
ProjectName string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import (
|
|||||||
"github.com/runatlantis/atlantis/server/events/command"
|
"github.com/runatlantis/atlantis/server/events/command"
|
||||||
"github.com/runatlantis/atlantis/server/events/models"
|
"github.com/runatlantis/atlantis/server/events/models"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -206,7 +207,6 @@ func (e *CommentParser) Parse(rawComment string, vcsHost models.VCSHostType) Com
|
|||||||
var verbose, autoMergeDisabled bool
|
var verbose, autoMergeDisabled bool
|
||||||
var flagSet *pflag.FlagSet
|
var flagSet *pflag.FlagSet
|
||||||
var name command.Name
|
var name command.Name
|
||||||
var requiredCommandArgCount int
|
|
||||||
|
|
||||||
// Set up the flag parsing depending on the command.
|
// Set up the flag parsing depending on the command.
|
||||||
switch cmd {
|
switch cmd {
|
||||||
@@ -251,47 +251,22 @@ func (e *CommentParser) Parse(rawComment string, vcsHost models.VCSHostType) Com
|
|||||||
flagSet.StringVarP(&dir, dirFlagLong, dirFlagShort, "", "Which directory to run import in relative to root of repo, ex. 'child/dir'.")
|
flagSet.StringVarP(&dir, dirFlagLong, dirFlagShort, "", "Which directory to run import in relative to root of repo, ex. 'child/dir'.")
|
||||||
flagSet.StringVarP(&project, projectFlagLong, projectFlagShort, "", "Which project to run import for. Refers to the name of the project configured in a repo config file. Cannot be used at same time as workspace or dir flags.")
|
flagSet.StringVarP(&project, projectFlagLong, projectFlagShort, "", "Which project to run import for. Refers to the name of the project configured in a repo config file. Cannot be used at same time as workspace or dir flags.")
|
||||||
flagSet.BoolVarP(&verbose, verboseFlagLong, verboseFlagShort, false, "Append Atlantis log to comment.")
|
flagSet.BoolVarP(&verbose, verboseFlagLong, verboseFlagShort, false, "Append Atlantis log to comment.")
|
||||||
requiredCommandArgCount = 2 // import requires `atlantis import ADDRESS ID` args
|
case command.State.String():
|
||||||
|
name = command.State
|
||||||
|
flagSet = pflag.NewFlagSet(command.State.String(), pflag.ContinueOnError)
|
||||||
|
flagSet.SetOutput(io.Discard)
|
||||||
|
flagSet.StringVarP(&workspace, workspaceFlagLong, workspaceFlagShort, "", "Switch to this Terraform workspace before processing tfstate.")
|
||||||
|
flagSet.StringVarP(&dir, dirFlagLong, dirFlagShort, "", "Which directory to run state command in relative to root of repo, ex. 'child/dir'.")
|
||||||
|
flagSet.StringVarP(&project, projectFlagLong, projectFlagShort, "", "Which project to run state command for. Refers to the name of the project configured in a repo config file. Cannot be used at same time as workspace or dir flags.")
|
||||||
|
flagSet.BoolVarP(&verbose, verboseFlagLong, verboseFlagShort, false, "Append Atlantis log to comment.")
|
||||||
default:
|
default:
|
||||||
return CommentParseResult{CommentResponse: fmt.Sprintf("Error: unknown command %q – this is a bug", cmd)}
|
return CommentParseResult{CommentResponse: fmt.Sprintf("Error: unknown command %q – this is a bug", cmd)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now parse the flags.
|
subName, extraArgs, errResult := e.parseArgs(name, args, flagSet)
|
||||||
// It's safe to use [2:] because we know there's at least 2 elements in args.
|
if errResult != "" {
|
||||||
err = flagSet.Parse(args[2:])
|
return CommentParseResult{CommentResponse: errResult}
|
||||||
if err == pflag.ErrHelp {
|
|
||||||
return CommentParseResult{CommentResponse: fmt.Sprintf("```\nUsage of %s:\n%s\n```", name.DefaultUsage(), flagSet.FlagUsagesWrapped(usagesCols))}
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
if cmd == command.Unlock.String() {
|
|
||||||
return CommentParseResult{CommentResponse: fmt.Sprintf(UnlockUsage, e.ExecutableName)}
|
|
||||||
}
|
|
||||||
return CommentParseResult{CommentResponse: e.errMarkdown(err.Error(), cmd, flagSet)}
|
|
||||||
}
|
|
||||||
|
|
||||||
var commandArgs []string // commandArgs are the arguments that are passed before `--` without any parameter flags.
|
|
||||||
if flagSet.ArgsLenAtDash() == -1 {
|
|
||||||
commandArgs = flagSet.Args()
|
|
||||||
} else {
|
|
||||||
commandArgs = flagSet.Args()[0:flagSet.ArgsLenAtDash()]
|
|
||||||
}
|
|
||||||
if len(commandArgs) != requiredCommandArgCount {
|
|
||||||
if requiredCommandArgCount > 0 {
|
|
||||||
return CommentParseResult{CommentResponse: e.errMarkdown(fmt.Sprintf("invalid argument count – %s", strings.Join(commandArgs, " ")), name.DefaultUsage(), flagSet)}
|
|
||||||
}
|
|
||||||
return CommentParseResult{CommentResponse: e.errMarkdown(fmt.Sprintf("unknown argument(s) – %s", strings.Join(commandArgs, " ")), name.DefaultUsage(), flagSet)}
|
|
||||||
}
|
|
||||||
|
|
||||||
var extraArgs []string // command extra_args
|
|
||||||
if flagSet.ArgsLenAtDash() != -1 {
|
|
||||||
extraArgs = append(extraArgs, flagSet.Args()[flagSet.ArgsLenAtDash():]...)
|
|
||||||
}
|
|
||||||
// pass commandArgs into extraArgs after extra args.
|
|
||||||
// - after comment_parser, we will use extra_args only.
|
|
||||||
// - terraform command args accept after options like followings
|
|
||||||
// - from: `atlantis import ADDRESS ID -- -var foo=bar
|
|
||||||
// - to: `terraform import -var foo=bar ADDRESS ID`
|
|
||||||
extraArgs = append(extraArgs, commandArgs...)
|
|
||||||
|
|
||||||
dir, err = e.validateDir(dir)
|
dir, err = e.validateDir(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -316,10 +291,73 @@ func (e *CommentParser) Parse(rawComment string, vcsHost models.VCSHostType) Com
|
|||||||
}
|
}
|
||||||
|
|
||||||
return CommentParseResult{
|
return CommentParseResult{
|
||||||
Command: NewCommentCommand(dir, extraArgs, name, verbose, autoMergeDisabled, workspace, project),
|
Command: NewCommentCommand(dir, extraArgs, name, subName, verbose, autoMergeDisabled, workspace, project),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *CommentParser) parseArgs(name command.Name, args []string, flagSet *pflag.FlagSet) (string, []string, string) {
|
||||||
|
// Now parse the flags.
|
||||||
|
// It's safe to use [2:] because we know there's at least 2 elements in args.
|
||||||
|
err := flagSet.Parse(args[2:])
|
||||||
|
if err == pflag.ErrHelp {
|
||||||
|
return "", nil, fmt.Sprintf("```\nUsage of %s:\n%s\n```", name.DefaultUsage(), flagSet.FlagUsagesWrapped(usagesCols))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if name == command.Unlock {
|
||||||
|
return "", nil, fmt.Sprintf(UnlockUsage, e.ExecutableName)
|
||||||
|
}
|
||||||
|
return "", nil, e.errMarkdown(err.Error(), name.String(), flagSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
var commandArgs []string // commandArgs are the arguments that are passed before `--` without any parameter flags.
|
||||||
|
if flagSet.ArgsLenAtDash() == -1 {
|
||||||
|
commandArgs = flagSet.Args()
|
||||||
|
} else {
|
||||||
|
commandArgs = flagSet.Args()[0:flagSet.ArgsLenAtDash()]
|
||||||
|
}
|
||||||
|
|
||||||
|
// If command require subcommand, get it from command args
|
||||||
|
var subCommand string
|
||||||
|
availableSubCommands := name.SubCommands()
|
||||||
|
if len(availableSubCommands) > 0 { // command requires a subcommand
|
||||||
|
if len(commandArgs) < 1 {
|
||||||
|
return "", nil, e.errMarkdown("subcommand required", name.String(), flagSet)
|
||||||
|
}
|
||||||
|
subCommand, commandArgs = commandArgs[0], commandArgs[1:]
|
||||||
|
isAvailableSubCommand := slices.Contains(availableSubCommands, subCommand)
|
||||||
|
if !isAvailableSubCommand {
|
||||||
|
errMsg := fmt.Sprintf("invalid subcommand %s (not %s)", subCommand, strings.Join(availableSubCommands, ", "))
|
||||||
|
return "", nil, e.errMarkdown(errMsg, name.String(), flagSet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check command args count requirements
|
||||||
|
commandArgCount, err := name.CommandArgCount(subCommand)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, e.errMarkdown(err.Error(), name.String(), flagSet)
|
||||||
|
}
|
||||||
|
if !commandArgCount.IsMatchCount(len(commandArgs)) {
|
||||||
|
return "", nil, e.errMarkdown(fmt.Sprintf("unknown argument(s) – %s", strings.Join(commandArgs, " ")), name.DefaultUsage(), flagSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
var extraArgs []string // command extra_args
|
||||||
|
if flagSet.ArgsLenAtDash() != -1 {
|
||||||
|
extraArgs = append(extraArgs, flagSet.Args()[flagSet.ArgsLenAtDash():]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pass commandArgs into extraArgs after extra args.
|
||||||
|
// - after comment_parser, we will use extra_args only.
|
||||||
|
// - terraform command args accept after options like followings
|
||||||
|
// - e.g.
|
||||||
|
// - from: `atlantis import ADDRESS ID -- -var foo=bar
|
||||||
|
// - to: `terraform import -var foo=bar ADDRESS ID`
|
||||||
|
// - e.g.
|
||||||
|
// - from: `atlantis state rm ADDRESS1 ADDRESS2 -- -var foo=bar
|
||||||
|
// - to: `terraform state rm -var foo=bar ADDRESS1 ADDRESS2` (subcommand=rm)
|
||||||
|
extraArgs = append(extraArgs, commandArgs...)
|
||||||
|
return subCommand, extraArgs, ""
|
||||||
|
}
|
||||||
|
|
||||||
// BuildPlanComment builds a plan comment for the specified args.
|
// BuildPlanComment builds a plan comment for the specified args.
|
||||||
func (e *CommentParser) BuildPlanComment(repoRelDir string, workspace string, project string, commentArgs []string) string {
|
func (e *CommentParser) BuildPlanComment(repoRelDir string, workspace string, project string, commentArgs []string) string {
|
||||||
flags := e.buildFlags(repoRelDir, workspace, project, false)
|
flags := e.buildFlags(repoRelDir, workspace, project, false)
|
||||||
@@ -424,6 +462,7 @@ func (e *CommentParser) HelpComment() string {
|
|||||||
AllowUnlock bool
|
AllowUnlock bool
|
||||||
AllowApprovePolicies bool
|
AllowApprovePolicies bool
|
||||||
AllowImport bool
|
AllowImport bool
|
||||||
|
AllowState bool
|
||||||
}{
|
}{
|
||||||
ExecutableName: e.ExecutableName,
|
ExecutableName: e.ExecutableName,
|
||||||
AllowVersion: e.isAllowedCommand(command.Version.String()),
|
AllowVersion: e.isAllowedCommand(command.Version.String()),
|
||||||
@@ -432,6 +471,7 @@ func (e *CommentParser) HelpComment() string {
|
|||||||
AllowUnlock: e.isAllowedCommand(command.Unlock.String()),
|
AllowUnlock: e.isAllowedCommand(command.Unlock.String()),
|
||||||
AllowApprovePolicies: e.isAllowedCommand(command.ApprovePolicies.String()),
|
AllowApprovePolicies: e.isAllowedCommand(command.ApprovePolicies.String()),
|
||||||
AllowImport: e.isAllowedCommand(command.Import.String()),
|
AllowImport: e.isAllowedCommand(command.Import.String()),
|
||||||
|
AllowState: e.isAllowedCommand(command.State.String()),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return fmt.Sprintf("Failed to render template, this is a bug: %v", err)
|
return fmt.Sprintf("Failed to render template, this is a bug: %v", err)
|
||||||
}
|
}
|
||||||
@@ -483,8 +523,14 @@ Commands:
|
|||||||
version Print the output of 'terraform version'
|
version Print the output of 'terraform version'
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if .AllowImport }}
|
{{- if .AllowImport }}
|
||||||
import Runs 'terraform import' for the changes in this pull request.
|
import ADDRESS ID
|
||||||
To plan a specific project, use the -d, -w and -p flags.
|
Runs 'terraform import' for the passed address resource.
|
||||||
|
To import a specific project, use the -d, -w and -p flags.
|
||||||
|
{{- end }}
|
||||||
|
{{- if .AllowState }}
|
||||||
|
state rm ADDRESS...
|
||||||
|
Runs 'terraform state rm' for the passed address resource.
|
||||||
|
To remove a specific project resource, use the -d, -w and -p flags.
|
||||||
{{- end }}
|
{{- end }}
|
||||||
help View help.
|
help View help.
|
||||||
|
|
||||||
|
|||||||
@@ -29,13 +29,7 @@ var commentParser = events.CommentParser{
|
|||||||
GithubUser: "github-user",
|
GithubUser: "github-user",
|
||||||
GitlabUser: "gitlab-user",
|
GitlabUser: "gitlab-user",
|
||||||
ExecutableName: "atlantis",
|
ExecutableName: "atlantis",
|
||||||
AllowCommands: []command.Name{
|
AllowCommands: command.AllCommentCommands,
|
||||||
command.Plan,
|
|
||||||
command.Apply,
|
|
||||||
command.Unlock,
|
|
||||||
command.ApprovePolicies,
|
|
||||||
command.Import,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewCommentParser(t *testing.T) {
|
func TestNewCommentParser(t *testing.T) {
|
||||||
@@ -151,82 +145,69 @@ func TestParse_HelpResponse(t *testing.T) {
|
|||||||
func TestParse_UnusedArguments(t *testing.T) {
|
func TestParse_UnusedArguments(t *testing.T) {
|
||||||
t.Log("if there are unused flags we return an error")
|
t.Log("if there are unused flags we return an error")
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
Command command.Name
|
Command command.Name
|
||||||
Args string
|
Args string
|
||||||
Unused string
|
Unused string
|
||||||
ExpMessage string
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
command.Plan,
|
command.Plan,
|
||||||
"-d . arg",
|
"-d . arg",
|
||||||
"arg",
|
"arg",
|
||||||
"unknown argument(s)",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command.Plan,
|
command.Plan,
|
||||||
"arg -d .",
|
"arg -d .",
|
||||||
"arg",
|
"arg",
|
||||||
"unknown argument(s)",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command.Plan,
|
command.Plan,
|
||||||
"arg",
|
"arg",
|
||||||
"arg",
|
"arg",
|
||||||
"unknown argument(s)",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command.Plan,
|
command.Plan,
|
||||||
"arg arg2",
|
"arg arg2",
|
||||||
"arg arg2",
|
"arg arg2",
|
||||||
"unknown argument(s)",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command.Plan,
|
command.Plan,
|
||||||
"-d . arg -w kjj arg2",
|
"-d . arg -w kjj arg2",
|
||||||
"arg arg2",
|
"arg arg2",
|
||||||
"unknown argument(s)",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command.Apply,
|
command.Apply,
|
||||||
"-d . arg",
|
"-d . arg",
|
||||||
"arg",
|
"arg",
|
||||||
"unknown argument(s)",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command.Apply,
|
command.Apply,
|
||||||
"arg arg2",
|
"arg arg2",
|
||||||
"arg arg2",
|
"arg arg2",
|
||||||
"unknown argument(s)",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command.Apply,
|
command.Apply,
|
||||||
"arg arg2 -- useful",
|
"arg arg2 -- useful",
|
||||||
"arg arg2",
|
"arg arg2",
|
||||||
"unknown argument(s)",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command.Apply,
|
command.Apply,
|
||||||
"arg arg2 --",
|
"arg arg2 --",
|
||||||
"arg arg2",
|
"arg arg2",
|
||||||
"unknown argument(s)",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command.ApprovePolicies,
|
command.ApprovePolicies,
|
||||||
"arg arg2 --",
|
"arg arg2 --",
|
||||||
"arg arg2",
|
"arg arg2",
|
||||||
"unknown argument(s)",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command.Import,
|
command.Import,
|
||||||
"arg --",
|
"arg --",
|
||||||
"arg",
|
"arg",
|
||||||
"invalid argument count",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
command.Import,
|
command.Import,
|
||||||
"arg1 arg2 arg3 --",
|
"arg1 arg2 arg3 --",
|
||||||
"arg1 arg2 arg3",
|
"arg1 arg2 arg3",
|
||||||
"invalid argument count",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
@@ -244,7 +225,7 @@ func TestParse_UnusedArguments(t *testing.T) {
|
|||||||
case command.Import:
|
case command.Import:
|
||||||
usage = ImportUsage
|
usage = ImportUsage
|
||||||
}
|
}
|
||||||
Equals(t, fmt.Sprintf("```\nError: %s – %s.\n%s```", c.ExpMessage, c.Unused, usage), r.CommentResponse)
|
Equals(t, fmt.Sprintf("```\nError: unknown argument(s) – %s.\n%s```", c.Unused, usage), r.CommentResponse)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -319,6 +300,8 @@ func TestParse_SubcommandUsage(t *testing.T) {
|
|||||||
{"atlantis approve_policies --help", "approve_policies"},
|
{"atlantis approve_policies --help", "approve_policies"},
|
||||||
{"atlantis import -h", "import ADDRESS ID"},
|
{"atlantis import -h", "import ADDRESS ID"},
|
||||||
{"atlantis import --help", "import ADDRESS ID"},
|
{"atlantis import --help", "import ADDRESS ID"},
|
||||||
|
{"atlantis state -h", "state [rm ADDRESS...]"},
|
||||||
|
{"atlantis state --help", "state [rm ADDRESS...]"},
|
||||||
}
|
}
|
||||||
for _, c := range tests {
|
for _, c := range tests {
|
||||||
r := commentParser.Parse(c.input, models.Github)
|
r := commentParser.Parse(c.input, models.Github)
|
||||||
@@ -357,6 +340,10 @@ func TestParse_InvalidFlags(t *testing.T) {
|
|||||||
"atlantis import --abc",
|
"atlantis import --abc",
|
||||||
"Error: unknown flag: --abc",
|
"Error: unknown flag: --abc",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"atlantis state rm --abc",
|
||||||
|
"Error: unknown flag: --abc",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
r := commentParser.Parse(c.comment, models.Github)
|
r := commentParser.Parse(c.comment, models.Github)
|
||||||
@@ -373,16 +360,20 @@ func TestParse_RelativeDirPath(t *testing.T) {
|
|||||||
"atlantis plan -d ..",
|
"atlantis plan -d ..",
|
||||||
"atlantis apply -d ..",
|
"atlantis apply -d ..",
|
||||||
"atlantis import -d .. address id",
|
"atlantis import -d .. address id",
|
||||||
|
"atlantis state -d .. rm address",
|
||||||
// These won't return an error because we prepend with . when parsing.
|
// These won't return an error because we prepend with . when parsing.
|
||||||
//"atlantis plan -d /..",
|
//"atlantis plan -d /..",
|
||||||
//"atlantis apply -d /..",
|
//"atlantis apply -d /..",
|
||||||
//"atlantis import -d /.. address id",
|
//"atlantis import -d /.. address id",
|
||||||
|
//"atlantis state rm -d /.. address",
|
||||||
"atlantis plan -d ./..",
|
"atlantis plan -d ./..",
|
||||||
"atlantis apply -d ./..",
|
"atlantis apply -d ./..",
|
||||||
"atlantis import -d ./.. address id",
|
"atlantis import -d ./.. address id",
|
||||||
|
"atlantis state -d ./.. rm address",
|
||||||
"atlantis plan -d a/b/../../..",
|
"atlantis plan -d a/b/../../..",
|
||||||
"atlantis apply -d a/../..",
|
"atlantis apply -d a/../..",
|
||||||
"atlantis import -d a/../.. address id",
|
"atlantis import -d a/../.. address id",
|
||||||
|
"atlantis state -d a/../.. rm address id",
|
||||||
}
|
}
|
||||||
for _, c := range comments {
|
for _, c := range comments {
|
||||||
r := commentParser.Parse(c, models.Github)
|
r := commentParser.Parse(c, models.Github)
|
||||||
@@ -427,15 +418,19 @@ func TestParse_InvalidWorkspace(t *testing.T) {
|
|||||||
"atlantis plan -w ..",
|
"atlantis plan -w ..",
|
||||||
"atlantis apply -w ..",
|
"atlantis apply -w ..",
|
||||||
"atlantis import -w .. address id",
|
"atlantis import -w .. address id",
|
||||||
|
"atlantis import -w .. rm address",
|
||||||
"atlantis plan -w /",
|
"atlantis plan -w /",
|
||||||
"atlantis apply -w /",
|
"atlantis apply -w /",
|
||||||
"atlantis import -w / address id",
|
"atlantis import -w / address id",
|
||||||
|
"atlantis state -w / rm address",
|
||||||
"atlantis plan -w ..abc",
|
"atlantis plan -w ..abc",
|
||||||
"atlantis apply -w abc..",
|
"atlantis apply -w abc..",
|
||||||
"atlantis import -w abc.. address id",
|
"atlantis import -w abc.. address id",
|
||||||
|
"atlantis state -w abc.. rm address",
|
||||||
"atlantis plan -w abc..abc",
|
"atlantis plan -w abc..abc",
|
||||||
"atlantis apply -w ../../../etc/passwd",
|
"atlantis apply -w ../../../etc/passwd",
|
||||||
"atlantis import -w ../../../etc/passwd address id",
|
"atlantis import -w ../../../etc/passwd address id",
|
||||||
|
"atlantis state -w ../../../etc/passwd rm address",
|
||||||
}
|
}
|
||||||
for _, c := range comments {
|
for _, c := range comments {
|
||||||
r := commentParser.Parse(c, models.Github)
|
r := commentParser.Parse(c, models.Github)
|
||||||
@@ -681,7 +676,7 @@ func TestParse_Parsing(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range cases {
|
for _, test := range cases {
|
||||||
for _, cmdName := range []string{"plan", "apply", "import 'some[\"addr\"]' id"} {
|
for _, cmdName := range []string{"plan", "apply", "import 'some[\"addr\"]' id", "state rm 'some[\"addr\"]'"} {
|
||||||
comment := fmt.Sprintf("atlantis %s %s", cmdName, test.flags)
|
comment := fmt.Sprintf("atlantis %s %s", cmdName, test.flags)
|
||||||
t.Run(comment, func(t *testing.T) {
|
t.Run(comment, func(t *testing.T) {
|
||||||
r := commentParser.Parse(comment, models.Github)
|
r := commentParser.Parse(comment, models.Github)
|
||||||
@@ -710,6 +705,15 @@ func TestParse_Parsing(t *testing.T) {
|
|||||||
Assert(t, r.Command.Name == command.Import, "did not parse comment %q as import command", comment)
|
Assert(t, r.Command.Name == command.Import, "did not parse comment %q as import command", comment)
|
||||||
Assert(t, expExtraArgs == actExtraArgs, "exp extra args to equal %v but got %v for comment %q", expExtraArgs, actExtraArgs, comment)
|
Assert(t, expExtraArgs == actExtraArgs, "exp extra args to equal %v but got %v for comment %q", expExtraArgs, actExtraArgs, comment)
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(cmdName, "state rm") {
|
||||||
|
expExtraArgs := "some[\"addr\"]" // state rm use default args with `some["addr"]`
|
||||||
|
if test.expExtraArgs != "" {
|
||||||
|
expExtraArgs = fmt.Sprintf("%s %s", test.expExtraArgs, expExtraArgs)
|
||||||
|
}
|
||||||
|
Assert(t, r.Command.Name == command.State, "did not parse comment %q as state command", comment)
|
||||||
|
Assert(t, r.Command.SubName == "rm", "did not parse comment %q as state rm subcommand", comment)
|
||||||
|
Assert(t, expExtraArgs == actExtraArgs, "exp extra args to equal %v but got %v for comment %q", expExtraArgs, actExtraArgs, comment)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -873,8 +877,12 @@ Commands:
|
|||||||
approve_policies
|
approve_policies
|
||||||
Approves all current policy checking failures for the PR.
|
Approves all current policy checking failures for the PR.
|
||||||
version Print the output of 'terraform version'
|
version Print the output of 'terraform version'
|
||||||
import Runs 'terraform import' for the changes in this pull request.
|
import ADDRESS ID
|
||||||
To plan a specific project, use the -d, -w and -p flags.
|
Runs 'terraform import' for the passed address resource.
|
||||||
|
To import a specific project, use the -d, -w and -p flags.
|
||||||
|
state rm ADDRESS...
|
||||||
|
Runs 'terraform state rm' for the passed address resource.
|
||||||
|
To remove a specific project resource, use the -d, -w and -p flags.
|
||||||
help View help.
|
help View help.
|
||||||
|
|
||||||
Flags:
|
Flags:
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ const usagesCols = 90
|
|||||||
type PullCommand interface {
|
type PullCommand interface {
|
||||||
// CommandName is the name of the command we're running.
|
// CommandName is the name of the command we're running.
|
||||||
CommandName() command.Name
|
CommandName() command.Name
|
||||||
|
// SubCommandName is the subcommand name of the command we're running.
|
||||||
|
SubCommandName() string
|
||||||
// IsVerbose is true if the output of this command should be verbose.
|
// IsVerbose is true if the output of this command should be verbose.
|
||||||
IsVerbose() bool
|
IsVerbose() bool
|
||||||
// IsAutoplan is true if this is an autoplan command vs. a comment command.
|
// IsAutoplan is true if this is an autoplan command vs. a comment command.
|
||||||
@@ -53,6 +55,11 @@ func (c PolicyCheckCommand) CommandName() command.Name {
|
|||||||
return command.PolicyCheck
|
return command.PolicyCheck
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SubCommandName is a subcommand for policy_check.
|
||||||
|
func (c PolicyCheckCommand) SubCommandName() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// IsVerbose is false for policy_check commands.
|
// IsVerbose is false for policy_check commands.
|
||||||
func (c PolicyCheckCommand) IsVerbose() bool {
|
func (c PolicyCheckCommand) IsVerbose() bool {
|
||||||
return false
|
return false
|
||||||
@@ -72,6 +79,11 @@ func (c AutoplanCommand) CommandName() command.Name {
|
|||||||
return command.Plan
|
return command.Plan
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SubCommandName is a subcommand for auto plan.
|
||||||
|
func (c AutoplanCommand) SubCommandName() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// IsVerbose is false for autoplan commands.
|
// IsVerbose is false for autoplan commands.
|
||||||
func (c AutoplanCommand) IsVerbose() bool {
|
func (c AutoplanCommand) IsVerbose() bool {
|
||||||
return false
|
return false
|
||||||
@@ -92,6 +104,8 @@ type CommentCommand struct {
|
|||||||
Flags []string
|
Flags []string
|
||||||
// Name is the name of the command the comment specified.
|
// Name is the name of the command the comment specified.
|
||||||
Name command.Name
|
Name command.Name
|
||||||
|
// SubName is the name of the sub command the comment specified.
|
||||||
|
SubName string
|
||||||
// AutoMergeDisabled is true if the command should not automerge after apply.
|
// AutoMergeDisabled is true if the command should not automerge after apply.
|
||||||
AutoMergeDisabled bool
|
AutoMergeDisabled bool
|
||||||
// Verbose is true if the command should output verbosely.
|
// Verbose is true if the command should output verbosely.
|
||||||
@@ -117,6 +131,11 @@ func (c CommentCommand) CommandName() command.Name {
|
|||||||
return c.Name
|
return c.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SubCommandName returns the name of this subcommand.
|
||||||
|
func (c CommentCommand) SubCommandName() string {
|
||||||
|
return c.SubName
|
||||||
|
}
|
||||||
|
|
||||||
// IsVerbose is true if the command should give verbose output.
|
// IsVerbose is true if the command should give verbose output.
|
||||||
func (c CommentCommand) IsVerbose() bool {
|
func (c CommentCommand) IsVerbose() bool {
|
||||||
return c.Verbose
|
return c.Verbose
|
||||||
@@ -133,7 +152,7 @@ func (c CommentCommand) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewCommentCommand constructs a CommentCommand, setting all missing fields to defaults.
|
// NewCommentCommand constructs a CommentCommand, setting all missing fields to defaults.
|
||||||
func NewCommentCommand(repoRelDir string, flags []string, name command.Name, verbose, autoMergeDisabled bool, workspace string, project string) *CommentCommand {
|
func NewCommentCommand(repoRelDir string, flags []string, name command.Name, subName string, verbose, autoMergeDisabled bool, workspace string, project string) *CommentCommand {
|
||||||
// If repoRelDir was empty we want to keep it that way to indicate that it
|
// If repoRelDir was empty we want to keep it that way to indicate that it
|
||||||
// wasn't specified in the comment.
|
// wasn't specified in the comment.
|
||||||
if repoRelDir != "" {
|
if repoRelDir != "" {
|
||||||
@@ -146,6 +165,7 @@ func NewCommentCommand(repoRelDir string, flags []string, name command.Name, ver
|
|||||||
RepoRelDir: repoRelDir,
|
RepoRelDir: repoRelDir,
|
||||||
Flags: flags,
|
Flags: flags,
|
||||||
Name: name,
|
Name: name,
|
||||||
|
SubName: subName,
|
||||||
Verbose: verbose,
|
Verbose: verbose,
|
||||||
Workspace: workspace,
|
Workspace: workspace,
|
||||||
AutoMergeDisabled: autoMergeDisabled,
|
AutoMergeDisabled: autoMergeDisabled,
|
||||||
|
|||||||
@@ -729,14 +729,14 @@ func TestNewCommand_CleansDir(t *testing.T) {
|
|||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
t.Run(c.RepoRelDir, func(t *testing.T) {
|
t.Run(c.RepoRelDir, func(t *testing.T) {
|
||||||
cmd := events.NewCommentCommand(c.RepoRelDir, nil, command.Plan, false, false, "workspace", "")
|
cmd := events.NewCommentCommand(c.RepoRelDir, nil, command.Plan, "", false, false, "workspace", "")
|
||||||
Equals(t, c.ExpDir, cmd.RepoRelDir)
|
Equals(t, c.ExpDir, cmd.RepoRelDir)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewCommand_EmptyDirWorkspaceProject(t *testing.T) {
|
func TestNewCommand_EmptyDirWorkspaceProject(t *testing.T) {
|
||||||
cmd := events.NewCommentCommand("", nil, command.Plan, false, false, "", "")
|
cmd := events.NewCommentCommand("", nil, command.Plan, "", false, false, "", "")
|
||||||
Equals(t, events.CommentCommand{
|
Equals(t, events.CommentCommand{
|
||||||
RepoRelDir: "",
|
RepoRelDir: "",
|
||||||
Flags: nil,
|
Flags: nil,
|
||||||
@@ -748,7 +748,7 @@ func TestNewCommand_EmptyDirWorkspaceProject(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNewCommand_AllFieldsSet(t *testing.T) {
|
func TestNewCommand_AllFieldsSet(t *testing.T) {
|
||||||
cmd := events.NewCommentCommand("dir", []string{"a", "b"}, command.Plan, true, false, "workspace", "project")
|
cmd := events.NewCommentCommand("dir", []string{"a", "b"}, command.Plan, "", true, false, "workspace", "project")
|
||||||
Equals(t, events.CommentCommand{
|
Equals(t, events.CommentCommand{
|
||||||
Workspace: "workspace",
|
Workspace: "workspace",
|
||||||
RepoRelDir: "dir",
|
RepoRelDir: "dir",
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func (b *InstrumentedProjectCommandBuilder) BuildPlanCommands(ctx *command.Conte
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *InstrumentedProjectCommandBuilder) BuildVersionCommands(ctx *command.Context, comment *CommentCommand) ([]command.ProjectContext, error) {
|
func (b *InstrumentedProjectCommandBuilder) BuildImportCommands(ctx *command.Context, comment *CommentCommand) ([]command.ProjectContext, error) {
|
||||||
return b.buildAndEmitStats(
|
return b.buildAndEmitStats(
|
||||||
"import",
|
"import",
|
||||||
func() ([]command.ProjectContext, error) {
|
func() ([]command.ProjectContext, error) {
|
||||||
@@ -49,6 +49,15 @@ func (b *InstrumentedProjectCommandBuilder) BuildVersionCommands(ctx *command.Co
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *InstrumentedProjectCommandBuilder) BuildStateRmCommands(ctx *command.Context, comment *CommentCommand) ([]command.ProjectContext, error) {
|
||||||
|
return b.buildAndEmitStats(
|
||||||
|
"state rm",
|
||||||
|
func() ([]command.ProjectContext, error) {
|
||||||
|
return b.ProjectCommandBuilder.BuildStateRmCommands(ctx, comment)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func (b *InstrumentedProjectCommandBuilder) buildAndEmitStats(
|
func (b *InstrumentedProjectCommandBuilder) buildAndEmitStats(
|
||||||
command string,
|
command string,
|
||||||
execute func() ([]command.ProjectContext, error),
|
execute func() ([]command.ProjectContext, error),
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ type IntrumentedCommandRunner interface {
|
|||||||
Apply(ctx command.ProjectContext) command.ProjectResult
|
Apply(ctx command.ProjectContext) command.ProjectResult
|
||||||
ApprovePolicies(ctx command.ProjectContext) command.ProjectResult
|
ApprovePolicies(ctx command.ProjectContext) command.ProjectResult
|
||||||
Import(ctx command.ProjectContext) command.ProjectResult
|
Import(ctx command.ProjectContext) command.ProjectResult
|
||||||
|
StateRm(ctx command.ProjectContext) command.ProjectResult
|
||||||
}
|
}
|
||||||
|
|
||||||
type InstrumentedProjectCommandRunner struct {
|
type InstrumentedProjectCommandRunner struct {
|
||||||
@@ -53,6 +54,10 @@ func (p *InstrumentedProjectCommandRunner) Import(ctx command.ProjectContext) co
|
|||||||
return RunAndEmitStats("import", ctx, p.projectCommandRunner.Import, p.scope)
|
return RunAndEmitStats("import", ctx, p.projectCommandRunner.Import, p.scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *InstrumentedProjectCommandRunner) StateRm(ctx command.ProjectContext) command.ProjectResult {
|
||||||
|
return RunAndEmitStats("state rm", ctx, p.projectCommandRunner.StateRm, p.scope)
|
||||||
|
}
|
||||||
|
|
||||||
func RunAndEmitStats(commandName string, ctx command.ProjectContext, execute func(ctx command.ProjectContext) command.ProjectResult, scope tally.Scope) command.ProjectResult {
|
func RunAndEmitStats(commandName string, ctx command.ProjectContext, execute func(ctx command.ProjectContext) command.ProjectResult, scope tally.Scope) command.ProjectResult {
|
||||||
// ensures we are differentiating between project level command and overall command
|
// ensures we are differentiating between project level command and overall command
|
||||||
scope = ctx.SetProjectScopeTags(scope)
|
scope = ctx.SetProjectScopeTags(scope)
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ var (
|
|||||||
approvePoliciesCommandTitle = command.ApprovePolicies.TitleString()
|
approvePoliciesCommandTitle = command.ApprovePolicies.TitleString()
|
||||||
versionCommandTitle = command.Version.TitleString()
|
versionCommandTitle = command.Version.TitleString()
|
||||||
importCommandTitle = command.Import.TitleString()
|
importCommandTitle = command.Import.TitleString()
|
||||||
|
stateCommandTitle = command.State.TitleString()
|
||||||
// maxUnwrappedLines is the maximum number of lines the Terraform output
|
// maxUnwrappedLines is the maximum number of lines the Terraform output
|
||||||
// can be before we wrap it in an expandable template.
|
// can be before we wrap it in an expandable template.
|
||||||
maxUnwrappedLines = 12
|
maxUnwrappedLines = 12
|
||||||
@@ -60,6 +61,7 @@ type MarkdownRenderer struct {
|
|||||||
// commonData is data that all responses have.
|
// commonData is data that all responses have.
|
||||||
type commonData struct {
|
type commonData struct {
|
||||||
Command string
|
Command string
|
||||||
|
SubCommand string
|
||||||
Verbose bool
|
Verbose bool
|
||||||
Log string
|
Log string
|
||||||
PlansDeleted bool
|
PlansDeleted bool
|
||||||
@@ -140,10 +142,11 @@ func NewMarkdownRenderer(
|
|||||||
|
|
||||||
// Render formats the data into a markdown string.
|
// Render formats the data into a markdown string.
|
||||||
// nolint: interfacer
|
// nolint: interfacer
|
||||||
func (m *MarkdownRenderer) Render(res command.Result, cmdName command.Name, log string, verbose bool, vcsHost models.VCSHostType) string {
|
func (m *MarkdownRenderer) Render(res command.Result, cmdName command.Name, subCmd, log string, verbose bool, vcsHost models.VCSHostType) string {
|
||||||
commandStr := cases.Title(language.English).String(strings.Replace(cmdName.String(), "_", " ", -1))
|
commandStr := cases.Title(language.English).String(strings.Replace(cmdName.String(), "_", " ", -1))
|
||||||
common := commonData{
|
common := commonData{
|
||||||
Command: commandStr,
|
Command: commandStr,
|
||||||
|
SubCommand: subCmd,
|
||||||
Verbose: verbose,
|
Verbose: verbose,
|
||||||
Log: log,
|
Log: log,
|
||||||
PlansDeleted: res.PlansDeleted,
|
PlansDeleted: res.PlansDeleted,
|
||||||
@@ -225,6 +228,13 @@ func (m *MarkdownRenderer) renderProjectResults(results []command.ProjectResult,
|
|||||||
} else {
|
} else {
|
||||||
resultData.Rendered = m.renderTemplateTrimSpace(templates.Lookup("importSuccessUnwrapped"), result.ImportSuccess)
|
resultData.Rendered = m.renderTemplateTrimSpace(templates.Lookup("importSuccessUnwrapped"), result.ImportSuccess)
|
||||||
}
|
}
|
||||||
|
} else if result.StateRmSuccess != nil {
|
||||||
|
result.StateRmSuccess.Output = strings.TrimSpace(result.StateRmSuccess.Output)
|
||||||
|
if m.shouldUseWrappedTmpl(vcsHost, result.StateRmSuccess.Output) {
|
||||||
|
resultData.Rendered = m.renderTemplateTrimSpace(templates.Lookup("stateRmSuccessWrapped"), result.StateRmSuccess)
|
||||||
|
} else {
|
||||||
|
resultData.Rendered = m.renderTemplateTrimSpace(templates.Lookup("stateRmSuccessUnwrapped"), result.StateRmSuccess)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
resultData.Rendered = "Found no template. This is a bug!"
|
resultData.Rendered = "Found no template. This is a bug!"
|
||||||
}
|
}
|
||||||
@@ -249,6 +259,13 @@ func (m *MarkdownRenderer) renderProjectResults(results []command.ProjectResult,
|
|||||||
tmpl = templates.Lookup("singleProjectApply")
|
tmpl = templates.Lookup("singleProjectApply")
|
||||||
case len(resultsTmplData) == 1 && common.Command == importCommandTitle:
|
case len(resultsTmplData) == 1 && common.Command == importCommandTitle:
|
||||||
tmpl = templates.Lookup("singleProjectImport")
|
tmpl = templates.Lookup("singleProjectImport")
|
||||||
|
case len(resultsTmplData) == 1 && common.Command == stateCommandTitle:
|
||||||
|
switch common.SubCommand {
|
||||||
|
case "rm":
|
||||||
|
tmpl = templates.Lookup("singleProjectStateRm")
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("no template matched–this is a bug: command=%s, subcommand=%s", common.Command, common.SubCommand)
|
||||||
|
}
|
||||||
case common.Command == planCommandTitle,
|
case common.Command == planCommandTitle,
|
||||||
common.Command == policyCheckCommandTitle:
|
common.Command == policyCheckCommandTitle:
|
||||||
tmpl = templates.Lookup("multiProjectPlan")
|
tmpl = templates.Lookup("multiProjectPlan")
|
||||||
@@ -260,8 +277,15 @@ func (m *MarkdownRenderer) renderProjectResults(results []command.ProjectResult,
|
|||||||
tmpl = templates.Lookup("multiProjectVersion")
|
tmpl = templates.Lookup("multiProjectVersion")
|
||||||
case common.Command == importCommandTitle:
|
case common.Command == importCommandTitle:
|
||||||
tmpl = templates.Lookup("multiProjectImport")
|
tmpl = templates.Lookup("multiProjectImport")
|
||||||
|
case common.Command == stateCommandTitle:
|
||||||
|
switch common.SubCommand {
|
||||||
|
case "rm":
|
||||||
|
tmpl = templates.Lookup("multiProjectStateRm")
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("no template matched–this is a bug: command=%s, subcommand=%s", common.Command, common.SubCommand)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return "no template matched–this is a bug"
|
return fmt.Sprintf("no template matched–this is a bug: command=%s", common.Command)
|
||||||
}
|
}
|
||||||
return m.renderTemplateTrimSpace(tmpl, resultData{resultsTmplData, common})
|
return m.renderTemplateTrimSpace(tmpl, resultData{resultsTmplData, common})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ func TestRenderErr(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, verbose := range []bool{true, false} {
|
for _, verbose := range []bool{true, false} {
|
||||||
t.Run(fmt.Sprintf("%s_%t", c.Description, verbose), func(t *testing.T) {
|
t.Run(fmt.Sprintf("%s_%t", c.Description, verbose), func(t *testing.T) {
|
||||||
s := r.Render(res, c.Command, "log", verbose, models.Github)
|
s := r.Render(res, c.Command, "", "log", verbose, models.Github)
|
||||||
if !verbose {
|
if !verbose {
|
||||||
Equals(t, strings.TrimSpace(c.Expected), strings.TrimSpace(s))
|
Equals(t, strings.TrimSpace(c.Expected), strings.TrimSpace(s))
|
||||||
} else {
|
} else {
|
||||||
@@ -109,7 +109,7 @@ func TestRenderFailure(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, verbose := range []bool{true, false} {
|
for _, verbose := range []bool{true, false} {
|
||||||
t.Run(fmt.Sprintf("%s_%t", c.Description, verbose), func(t *testing.T) {
|
t.Run(fmt.Sprintf("%s_%t", c.Description, verbose), func(t *testing.T) {
|
||||||
s := r.Render(res, c.Command, "log", verbose, models.Github)
|
s := r.Render(res, c.Command, "", "log", verbose, models.Github)
|
||||||
if !verbose {
|
if !verbose {
|
||||||
Equals(t, strings.TrimSpace(c.Expected), strings.TrimSpace(s))
|
Equals(t, strings.TrimSpace(c.Expected), strings.TrimSpace(s))
|
||||||
} else {
|
} else {
|
||||||
@@ -126,7 +126,7 @@ func TestRenderErrAndFailure(t *testing.T) {
|
|||||||
Error: errors.New("error"),
|
Error: errors.New("error"),
|
||||||
Failure: "failure",
|
Failure: "failure",
|
||||||
}
|
}
|
||||||
s := r.Render(res, command.Plan, "", false, models.Github)
|
s := r.Render(res, command.Plan, "", "", false, models.Github)
|
||||||
Equals(t, "**Plan Error**\n```\nerror\n```", s)
|
Equals(t, "**Plan Error**\n```\nerror\n```", s)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,6 +134,7 @@ func TestRenderProjectResults(t *testing.T) {
|
|||||||
cases := []struct {
|
cases := []struct {
|
||||||
Description string
|
Description string
|
||||||
Command command.Name
|
Command command.Name
|
||||||
|
SubCommand string
|
||||||
ProjectResults []command.ProjectResult
|
ProjectResults []command.ProjectResult
|
||||||
VCSHost models.VCSHostType
|
VCSHost models.VCSHostType
|
||||||
Expected string
|
Expected string
|
||||||
@@ -141,6 +142,7 @@ func TestRenderProjectResults(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"no projects",
|
"no projects",
|
||||||
command.Plan,
|
command.Plan,
|
||||||
|
"",
|
||||||
[]command.ProjectResult{},
|
[]command.ProjectResult{},
|
||||||
models.Github,
|
models.Github,
|
||||||
"Ran Plan for 0 projects:\n\n\n",
|
"Ran Plan for 0 projects:\n\n\n",
|
||||||
@@ -148,6 +150,7 @@ func TestRenderProjectResults(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"single successful plan",
|
"single successful plan",
|
||||||
command.Plan,
|
command.Plan,
|
||||||
|
"",
|
||||||
[]command.ProjectResult{
|
[]command.ProjectResult{
|
||||||
{
|
{
|
||||||
PlanSuccess: &models.PlanSuccess{
|
PlanSuccess: &models.PlanSuccess{
|
||||||
@@ -183,6 +186,7 @@ $$$
|
|||||||
{
|
{
|
||||||
"single successful plan with main ahead",
|
"single successful plan with main ahead",
|
||||||
command.Plan,
|
command.Plan,
|
||||||
|
"",
|
||||||
[]command.ProjectResult{
|
[]command.ProjectResult{
|
||||||
{
|
{
|
||||||
PlanSuccess: &models.PlanSuccess{
|
PlanSuccess: &models.PlanSuccess{
|
||||||
@@ -221,6 +225,7 @@ $$$
|
|||||||
{
|
{
|
||||||
"single successful plan with project name",
|
"single successful plan with project name",
|
||||||
command.Plan,
|
command.Plan,
|
||||||
|
"",
|
||||||
[]command.ProjectResult{
|
[]command.ProjectResult{
|
||||||
{
|
{
|
||||||
PlanSuccess: &models.PlanSuccess{
|
PlanSuccess: &models.PlanSuccess{
|
||||||
@@ -257,6 +262,7 @@ $$$
|
|||||||
{
|
{
|
||||||
"single successful policy check with project name",
|
"single successful policy check with project name",
|
||||||
command.PolicyCheck,
|
command.PolicyCheck,
|
||||||
|
"",
|
||||||
[]command.ProjectResult{
|
[]command.ProjectResult{
|
||||||
{
|
{
|
||||||
PolicyCheckSuccess: &models.PolicyCheckSuccess{
|
PolicyCheckSuccess: &models.PolicyCheckSuccess{
|
||||||
@@ -306,6 +312,7 @@ $$$
|
|||||||
{
|
{
|
||||||
"single successful import",
|
"single successful import",
|
||||||
command.Import,
|
command.Import,
|
||||||
|
"",
|
||||||
[]command.ProjectResult{
|
[]command.ProjectResult{
|
||||||
{
|
{
|
||||||
ImportSuccess: &models.ImportSuccess{
|
ImportSuccess: &models.ImportSuccess{
|
||||||
@@ -324,12 +331,43 @@ $$$diff
|
|||||||
import-output
|
import-output
|
||||||
$$$
|
$$$
|
||||||
|
|
||||||
|
:put_litter_in_its_place: A plan file was discarded. Re-plan would be required before applying.
|
||||||
|
|
||||||
* :repeat: To **plan** this project again, comment:
|
* :repeat: To **plan** this project again, comment:
|
||||||
* $atlantis plan -d path -w workspace$`,
|
* $atlantis plan -d path -w workspace$`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"single successful state rm",
|
||||||
|
command.State,
|
||||||
|
"rm",
|
||||||
|
[]command.ProjectResult{
|
||||||
|
{
|
||||||
|
StateRmSuccess: &models.StateRmSuccess{
|
||||||
|
Output: "state-rm-output",
|
||||||
|
RePlanCmd: "atlantis plan -d path -w workspace",
|
||||||
|
},
|
||||||
|
Workspace: "workspace",
|
||||||
|
RepoRelDir: "path",
|
||||||
|
ProjectName: "projectname",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
models.Github,
|
||||||
|
`Ran State $rm$ for project: $projectname$ dir: $path$ workspace: $workspace$
|
||||||
|
|
||||||
|
$$$diff
|
||||||
|
state-rm-output
|
||||||
|
$$$
|
||||||
|
|
||||||
|
:put_litter_in_its_place: A plan file was discarded. Re-plan would be required before applying.
|
||||||
|
|
||||||
|
* :repeat: To **plan** this project again, comment:
|
||||||
|
* $atlantis plan -d path -w workspace$
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"single successful apply",
|
"single successful apply",
|
||||||
command.Apply,
|
command.Apply,
|
||||||
|
"",
|
||||||
[]command.ProjectResult{
|
[]command.ProjectResult{
|
||||||
{
|
{
|
||||||
ApplySuccess: "success",
|
ApplySuccess: "success",
|
||||||
@@ -347,6 +385,7 @@ $$$`,
|
|||||||
{
|
{
|
||||||
"single successful apply with project name",
|
"single successful apply with project name",
|
||||||
command.Apply,
|
command.Apply,
|
||||||
|
"",
|
||||||
[]command.ProjectResult{
|
[]command.ProjectResult{
|
||||||
{
|
{
|
||||||
ApplySuccess: "success",
|
ApplySuccess: "success",
|
||||||
@@ -365,6 +404,7 @@ $$$`,
|
|||||||
{
|
{
|
||||||
"multiple successful plans",
|
"multiple successful plans",
|
||||||
command.Plan,
|
command.Plan,
|
||||||
|
"",
|
||||||
[]command.ProjectResult{
|
[]command.ProjectResult{
|
||||||
{
|
{
|
||||||
Workspace: "workspace",
|
Workspace: "workspace",
|
||||||
@@ -427,6 +467,7 @@ $$$
|
|||||||
{
|
{
|
||||||
"multiple successful policy checks",
|
"multiple successful policy checks",
|
||||||
command.PolicyCheck,
|
command.PolicyCheck,
|
||||||
|
"",
|
||||||
[]command.ProjectResult{
|
[]command.ProjectResult{
|
||||||
{
|
{
|
||||||
Workspace: "workspace",
|
Workspace: "workspace",
|
||||||
@@ -489,6 +530,7 @@ $$$
|
|||||||
{
|
{
|
||||||
"multiple successful applies",
|
"multiple successful applies",
|
||||||
command.Apply,
|
command.Apply,
|
||||||
|
"",
|
||||||
[]command.ProjectResult{
|
[]command.ProjectResult{
|
||||||
{
|
{
|
||||||
RepoRelDir: "path",
|
RepoRelDir: "path",
|
||||||
@@ -525,6 +567,7 @@ $$$
|
|||||||
{
|
{
|
||||||
"single errored plan",
|
"single errored plan",
|
||||||
command.Plan,
|
command.Plan,
|
||||||
|
"",
|
||||||
[]command.ProjectResult{
|
[]command.ProjectResult{
|
||||||
{
|
{
|
||||||
Error: errors.New("error"),
|
Error: errors.New("error"),
|
||||||
@@ -543,6 +586,7 @@ $$$`,
|
|||||||
{
|
{
|
||||||
"single failed plan",
|
"single failed plan",
|
||||||
command.Plan,
|
command.Plan,
|
||||||
|
"",
|
||||||
[]command.ProjectResult{
|
[]command.ProjectResult{
|
||||||
{
|
{
|
||||||
RepoRelDir: "path",
|
RepoRelDir: "path",
|
||||||
@@ -558,6 +602,7 @@ $$$`,
|
|||||||
{
|
{
|
||||||
"successful, failed, and errored plan",
|
"successful, failed, and errored plan",
|
||||||
command.Plan,
|
command.Plan,
|
||||||
|
"",
|
||||||
[]command.ProjectResult{
|
[]command.ProjectResult{
|
||||||
{
|
{
|
||||||
Workspace: "workspace",
|
Workspace: "workspace",
|
||||||
@@ -620,6 +665,7 @@ $$$
|
|||||||
{
|
{
|
||||||
"successful, failed, and errored policy check",
|
"successful, failed, and errored policy check",
|
||||||
command.PolicyCheck,
|
command.PolicyCheck,
|
||||||
|
"",
|
||||||
[]command.ProjectResult{
|
[]command.ProjectResult{
|
||||||
{
|
{
|
||||||
Workspace: "workspace",
|
Workspace: "workspace",
|
||||||
@@ -685,6 +731,7 @@ $$$
|
|||||||
{
|
{
|
||||||
"successful, failed, and errored apply",
|
"successful, failed, and errored apply",
|
||||||
command.Apply,
|
command.Apply,
|
||||||
|
"",
|
||||||
[]command.ProjectResult{
|
[]command.ProjectResult{
|
||||||
{
|
{
|
||||||
Workspace: "workspace",
|
Workspace: "workspace",
|
||||||
@@ -731,6 +778,7 @@ $$$
|
|||||||
{
|
{
|
||||||
"successful, failed, and errored apply",
|
"successful, failed, and errored apply",
|
||||||
command.Apply,
|
command.Apply,
|
||||||
|
"",
|
||||||
[]command.ProjectResult{
|
[]command.ProjectResult{
|
||||||
{
|
{
|
||||||
Workspace: "workspace",
|
Workspace: "workspace",
|
||||||
@@ -784,7 +832,7 @@ $$$
|
|||||||
}
|
}
|
||||||
for _, verbose := range []bool{true, false} {
|
for _, verbose := range []bool{true, false} {
|
||||||
t.Run(c.Description, func(t *testing.T) {
|
t.Run(c.Description, func(t *testing.T) {
|
||||||
s := r.Render(res, c.Command, "log", verbose, c.VCSHost)
|
s := r.Render(res, c.Command, c.SubCommand, "log", verbose, c.VCSHost)
|
||||||
expWithBackticks := strings.Replace(c.Expected, "$", "`", -1)
|
expWithBackticks := strings.Replace(c.Expected, "$", "`", -1)
|
||||||
if !verbose {
|
if !verbose {
|
||||||
Equals(t, strings.TrimSpace(expWithBackticks), strings.TrimSpace(s))
|
Equals(t, strings.TrimSpace(expWithBackticks), strings.TrimSpace(s))
|
||||||
@@ -939,7 +987,7 @@ $$$
|
|||||||
}
|
}
|
||||||
for _, verbose := range []bool{true, false} {
|
for _, verbose := range []bool{true, false} {
|
||||||
t.Run(c.Description, func(t *testing.T) {
|
t.Run(c.Description, func(t *testing.T) {
|
||||||
s := r.Render(res, c.Command, "log", verbose, c.VCSHost)
|
s := r.Render(res, c.Command, "", "log", verbose, c.VCSHost)
|
||||||
expWithBackticks := strings.Replace(c.Expected, "$", "`", -1)
|
expWithBackticks := strings.Replace(c.Expected, "$", "`", -1)
|
||||||
if !verbose {
|
if !verbose {
|
||||||
Equals(t, strings.TrimSpace(expWithBackticks), strings.TrimSpace(s))
|
Equals(t, strings.TrimSpace(expWithBackticks), strings.TrimSpace(s))
|
||||||
@@ -1087,7 +1135,7 @@ $$$
|
|||||||
}
|
}
|
||||||
for _, verbose := range []bool{true, false} {
|
for _, verbose := range []bool{true, false} {
|
||||||
t.Run(c.Description, func(t *testing.T) {
|
t.Run(c.Description, func(t *testing.T) {
|
||||||
s := r.Render(res, c.Command, "log", verbose, c.VCSHost)
|
s := r.Render(res, c.Command, "", "log", verbose, c.VCSHost)
|
||||||
expWithBackticks := strings.Replace(c.Expected, "$", "`", -1)
|
expWithBackticks := strings.Replace(c.Expected, "$", "`", -1)
|
||||||
if !verbose {
|
if !verbose {
|
||||||
Equals(t, strings.TrimSpace(expWithBackticks), strings.TrimSpace(s))
|
Equals(t, strings.TrimSpace(expWithBackticks), strings.TrimSpace(s))
|
||||||
@@ -1132,7 +1180,7 @@ func TestRenderCustomPolicyCheckTemplate_DisableApplyAll(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, command.PolicyCheck, "log", false, models.Github)
|
}, command.PolicyCheck, "", "log", false, models.Github)
|
||||||
exp := "Ran Policy Check for dir: `path` workspace: `workspace`\n\nsomecustometext"
|
exp := "Ran Policy Check for dir: `path` workspace: `workspace`\n\nsomecustometext"
|
||||||
Equals(t, exp, rendered)
|
Equals(t, exp, rendered)
|
||||||
}
|
}
|
||||||
@@ -1158,7 +1206,7 @@ func TestRenderProjectResults_DisableFolding(t *testing.T) {
|
|||||||
Error: errors.New(strings.Repeat("line\n", 13)),
|
Error: errors.New(strings.Repeat("line\n", 13)),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, command.Plan, "log", false, models.Github)
|
}, command.Plan, "", "log", false, models.Github)
|
||||||
Equals(t, false, strings.Contains(rendered, "\n<details>"))
|
Equals(t, false, strings.Contains(rendered, "\n<details>"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1249,7 +1297,7 @@ func TestRenderProjectResults_WrappedErr(t *testing.T) {
|
|||||||
Error: errors.New(c.Output),
|
Error: errors.New(c.Output),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, command.Plan, "log", false, c.VCSHost)
|
}, command.Plan, "", "log", false, c.VCSHost)
|
||||||
var exp string
|
var exp string
|
||||||
if c.ShouldWrap {
|
if c.ShouldWrap {
|
||||||
exp = `Ran Plan for dir: $.$ workspace: $default$
|
exp = `Ran Plan for dir: $.$ workspace: $default$
|
||||||
@@ -1377,7 +1425,7 @@ func TestRenderProjectResults_WrapSingleProject(t *testing.T) {
|
|||||||
}
|
}
|
||||||
rendered := mr.Render(command.Result{
|
rendered := mr.Render(command.Result{
|
||||||
ProjectResults: []command.ProjectResult{pr},
|
ProjectResults: []command.ProjectResult{pr},
|
||||||
}, cmd, "log", false, c.VCSHost)
|
}, cmd, "", "log", false, c.VCSHost)
|
||||||
|
|
||||||
// Check result.
|
// Check result.
|
||||||
var exp string
|
var exp string
|
||||||
@@ -1476,7 +1524,7 @@ func TestRenderProjectResults_MultiProjectApplyWrapped(t *testing.T) {
|
|||||||
ApplySuccess: tfOut,
|
ApplySuccess: tfOut,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, command.Apply, "log", false, models.Github)
|
}, command.Apply, "", "log", false, models.Github)
|
||||||
exp := `Ran Apply for 2 projects:
|
exp := `Ran Apply for 2 projects:
|
||||||
|
|
||||||
1. dir: $.$ workspace: $staging$
|
1. dir: $.$ workspace: $staging$
|
||||||
@@ -1541,7 +1589,7 @@ func TestRenderProjectResults_MultiProjectPlanWrapped(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, command.Plan, "log", false, models.Github)
|
}, command.Plan, "", "log", false, models.Github)
|
||||||
exp := `Ran Plan for 2 projects:
|
exp := `Ran Plan for 2 projects:
|
||||||
|
|
||||||
1. dir: $.$ workspace: $staging$
|
1. dir: $.$ workspace: $staging$
|
||||||
@@ -1692,7 +1740,7 @@ This plan was not saved because one or more projects failed and automerge requir
|
|||||||
"", // MarkdownTemplateOverridesDir
|
"", // MarkdownTemplateOverridesDir
|
||||||
"atlantis", // executableName
|
"atlantis", // executableName
|
||||||
)
|
)
|
||||||
rendered := mr.Render(c.cr, command.Plan, "log", false, models.Github)
|
rendered := mr.Render(c.cr, command.Plan, "", "log", false, models.Github)
|
||||||
expWithBackticks := strings.Replace(c.exp, "$", "`", -1)
|
expWithBackticks := strings.Replace(c.exp, "$", "`", -1)
|
||||||
Equals(t, expWithBackticks, rendered)
|
Equals(t, expWithBackticks, rendered)
|
||||||
})
|
})
|
||||||
@@ -2157,7 +2205,7 @@ $$$
|
|||||||
}
|
}
|
||||||
for _, verbose := range []bool{true, false} {
|
for _, verbose := range []bool{true, false} {
|
||||||
t.Run(c.Description, func(t *testing.T) {
|
t.Run(c.Description, func(t *testing.T) {
|
||||||
s := r.Render(res, c.Command, "log", verbose, c.VCSHost)
|
s := r.Render(res, c.Command, "", "log", verbose, c.VCSHost)
|
||||||
expWithBackticks := strings.Replace(c.Expected, "$", "`", -1)
|
expWithBackticks := strings.Replace(c.Expected, "$", "`", -1)
|
||||||
if !verbose {
|
if !verbose {
|
||||||
Equals(t, strings.TrimSpace(expWithBackticks), strings.TrimSpace(s))
|
Equals(t, strings.TrimSpace(expWithBackticks), strings.TrimSpace(s))
|
||||||
@@ -2594,7 +2642,7 @@ func TestRenderProjectResultsWithEnableDiffMarkdownFormat(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, verbose := range []bool{true, false} {
|
for _, verbose := range []bool{true, false} {
|
||||||
t.Run(c.Description, func(t *testing.T) {
|
t.Run(c.Description, func(t *testing.T) {
|
||||||
s := r.Render(res, c.Command, "log", verbose, c.VCSHost)
|
s := r.Render(res, c.Command, "", "log", verbose, c.VCSHost)
|
||||||
expWithBackticks := strings.Replace(c.Expected, "$", "`", -1)
|
expWithBackticks := strings.Replace(c.Expected, "$", "`", -1)
|
||||||
if !verbose {
|
if !verbose {
|
||||||
Equals(t, strings.TrimSpace(expWithBackticks), strings.TrimSpace(s))
|
Equals(t, strings.TrimSpace(expWithBackticks), strings.TrimSpace(s))
|
||||||
@@ -2632,7 +2680,7 @@ func BenchmarkRenderProjectResultsWithEnableDiffMarkdownFormat(b *testing.B) {
|
|||||||
b.Run(fmt.Sprintf("verbose %t", verbose), func(b *testing.B) {
|
b.Run(fmt.Sprintf("verbose %t", verbose), func(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
render = r.Render(res, c.Command, "log", verbose, c.VCSHost)
|
render = r.Render(res, c.Command, "", "log", verbose, c.VCSHost)
|
||||||
}
|
}
|
||||||
Render = render
|
Render = render
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -121,6 +121,25 @@ func (mock *MockProjectCommandBuilder) BuildPlanCommands(_param0 *command.Contex
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mock *MockProjectCommandBuilder) BuildStateRmCommands(_param0 *command.Context, _param1 *events.CommentCommand) ([]command.ProjectContext, error) {
|
||||||
|
if mock == nil {
|
||||||
|
panic("mock must not be nil. Use myMock := NewMockProjectCommandBuilder().")
|
||||||
|
}
|
||||||
|
params := []pegomock.Param{_param0, _param1}
|
||||||
|
result := pegomock.GetGenericMockFrom(mock).Invoke("BuildStateRmCommands", params, []reflect.Type{reflect.TypeOf((*[]command.ProjectContext)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||||
|
var ret0 []command.ProjectContext
|
||||||
|
var ret1 error
|
||||||
|
if len(result) != 0 {
|
||||||
|
if result[0] != nil {
|
||||||
|
ret0 = result[0].([]command.ProjectContext)
|
||||||
|
}
|
||||||
|
if result[1] != nil {
|
||||||
|
ret1 = result[1].(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
func (mock *MockProjectCommandBuilder) BuildVersionCommands(_param0 *command.Context, _param1 *events.CommentCommand) ([]command.ProjectContext, error) {
|
func (mock *MockProjectCommandBuilder) BuildVersionCommands(_param0 *command.Context, _param1 *events.CommentCommand) ([]command.ProjectContext, error) {
|
||||||
if mock == nil {
|
if mock == nil {
|
||||||
panic("mock must not be nil. Use myMock := NewMockProjectCommandBuilder().")
|
panic("mock must not be nil. Use myMock := NewMockProjectCommandBuilder().")
|
||||||
@@ -328,6 +347,37 @@ func (c *MockProjectCommandBuilder_BuildPlanCommands_OngoingVerification) GetAll
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (verifier *VerifierMockProjectCommandBuilder) BuildStateRmCommands(_param0 *command.Context, _param1 *events.CommentCommand) *MockProjectCommandBuilder_BuildStateRmCommands_OngoingVerification {
|
||||||
|
params := []pegomock.Param{_param0, _param1}
|
||||||
|
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "BuildStateRmCommands", params, verifier.timeout)
|
||||||
|
return &MockProjectCommandBuilder_BuildStateRmCommands_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockProjectCommandBuilder_BuildStateRmCommands_OngoingVerification struct {
|
||||||
|
mock *MockProjectCommandBuilder
|
||||||
|
methodInvocations []pegomock.MethodInvocation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MockProjectCommandBuilder_BuildStateRmCommands_OngoingVerification) GetCapturedArguments() (*command.Context, *events.CommentCommand) {
|
||||||
|
_param0, _param1 := c.GetAllCapturedArguments()
|
||||||
|
return _param0[len(_param0)-1], _param1[len(_param1)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MockProjectCommandBuilder_BuildStateRmCommands_OngoingVerification) GetAllCapturedArguments() (_param0 []*command.Context, _param1 []*events.CommentCommand) {
|
||||||
|
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||||
|
if len(params) > 0 {
|
||||||
|
_param0 = make([]*command.Context, len(c.methodInvocations))
|
||||||
|
for u, param := range params[0] {
|
||||||
|
_param0[u] = param.(*command.Context)
|
||||||
|
}
|
||||||
|
_param1 = make([]*events.CommentCommand, len(c.methodInvocations))
|
||||||
|
for u, param := range params[1] {
|
||||||
|
_param1[u] = param.(*events.CommentCommand)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (verifier *VerifierMockProjectCommandBuilder) BuildVersionCommands(_param0 *command.Context, _param1 *events.CommentCommand) *MockProjectCommandBuilder_BuildVersionCommands_OngoingVerification {
|
func (verifier *VerifierMockProjectCommandBuilder) BuildVersionCommands(_param0 *command.Context, _param1 *events.CommentCommand) *MockProjectCommandBuilder_BuildVersionCommands_OngoingVerification {
|
||||||
params := []pegomock.Param{_param0, _param1}
|
params := []pegomock.Param{_param0, _param1}
|
||||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "BuildVersionCommands", params, verifier.timeout)
|
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "BuildVersionCommands", params, verifier.timeout)
|
||||||
|
|||||||
@@ -100,6 +100,21 @@ func (mock *MockProjectCommandRunner) PolicyCheck(_param0 command.ProjectContext
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mock *MockProjectCommandRunner) StateRm(_param0 command.ProjectContext) command.ProjectResult {
|
||||||
|
if mock == nil {
|
||||||
|
panic("mock must not be nil. Use myMock := NewMockProjectCommandRunner().")
|
||||||
|
}
|
||||||
|
params := []pegomock.Param{_param0}
|
||||||
|
result := pegomock.GetGenericMockFrom(mock).Invoke("StateRm", params, []reflect.Type{reflect.TypeOf((*command.ProjectResult)(nil)).Elem()})
|
||||||
|
var ret0 command.ProjectResult
|
||||||
|
if len(result) != 0 {
|
||||||
|
if result[0] != nil {
|
||||||
|
ret0 = result[0].(command.ProjectResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
func (mock *MockProjectCommandRunner) Version(_param0 command.ProjectContext) command.ProjectResult {
|
func (mock *MockProjectCommandRunner) Version(_param0 command.ProjectContext) command.ProjectResult {
|
||||||
if mock == nil {
|
if mock == nil {
|
||||||
panic("mock must not be nil. Use myMock := NewMockProjectCommandRunner().")
|
panic("mock must not be nil. Use myMock := NewMockProjectCommandRunner().")
|
||||||
@@ -287,6 +302,33 @@ func (c *MockProjectCommandRunner_PolicyCheck_OngoingVerification) GetAllCapture
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (verifier *VerifierMockProjectCommandRunner) StateRm(_param0 command.ProjectContext) *MockProjectCommandRunner_StateRm_OngoingVerification {
|
||||||
|
params := []pegomock.Param{_param0}
|
||||||
|
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "StateRm", params, verifier.timeout)
|
||||||
|
return &MockProjectCommandRunner_StateRm_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockProjectCommandRunner_StateRm_OngoingVerification struct {
|
||||||
|
mock *MockProjectCommandRunner
|
||||||
|
methodInvocations []pegomock.MethodInvocation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MockProjectCommandRunner_StateRm_OngoingVerification) GetCapturedArguments() command.ProjectContext {
|
||||||
|
_param0 := c.GetAllCapturedArguments()
|
||||||
|
return _param0[len(_param0)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MockProjectCommandRunner_StateRm_OngoingVerification) GetAllCapturedArguments() (_param0 []command.ProjectContext) {
|
||||||
|
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||||
|
if len(params) > 0 {
|
||||||
|
_param0 = make([]command.ProjectContext, len(c.methodInvocations))
|
||||||
|
for u, param := range params[0] {
|
||||||
|
_param0[u] = param.(command.ProjectContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (verifier *VerifierMockProjectCommandRunner) Version(_param0 command.ProjectContext) *MockProjectCommandRunner_Version_OngoingVerification {
|
func (verifier *VerifierMockProjectCommandRunner) Version(_param0 command.ProjectContext) *MockProjectCommandRunner_Version_OngoingVerification {
|
||||||
params := []pegomock.Param{_param0}
|
params := []pegomock.Param{_param0}
|
||||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Version", params, verifier.timeout)
|
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Version", params, verifier.timeout)
|
||||||
|
|||||||
@@ -430,6 +430,14 @@ type ImportSuccess struct {
|
|||||||
RePlanCmd string
|
RePlanCmd string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StateRmSuccess is the result of a successful state rm run.
|
||||||
|
type StateRmSuccess struct {
|
||||||
|
// Output is the output from terraform state rm
|
||||||
|
Output string
|
||||||
|
// RePlanCmd is the command that users should run to re-plan this project.
|
||||||
|
RePlanCmd string
|
||||||
|
}
|
||||||
|
|
||||||
// Summary extracts one line summary of policy check.
|
// Summary extracts one line summary of policy check.
|
||||||
func (p *PolicyCheckSuccess) Summary() string {
|
func (p *PolicyCheckSuccess) Summary() string {
|
||||||
note := ""
|
note := ""
|
||||||
|
|||||||
@@ -165,6 +165,13 @@ type ProjectImportCommandBuilder interface {
|
|||||||
BuildImportCommands(ctx *command.Context, comment *CommentCommand) ([]command.ProjectContext, error)
|
BuildImportCommands(ctx *command.Context, comment *CommentCommand) ([]command.ProjectContext, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ProjectStateCommandBuilder interface {
|
||||||
|
// BuildStateRmCommands builds project state rm commands for this ctx and comment. If
|
||||||
|
// comment doesn't specify one project then there may be multiple commands
|
||||||
|
// to be run.
|
||||||
|
BuildStateRmCommands(ctx *command.Context, comment *CommentCommand) ([]command.ProjectContext, error)
|
||||||
|
}
|
||||||
|
|
||||||
//go:generate pegomock generate -m --package mocks -o mocks/mock_project_command_builder.go ProjectCommandBuilder
|
//go:generate pegomock generate -m --package mocks -o mocks/mock_project_command_builder.go ProjectCommandBuilder
|
||||||
|
|
||||||
// ProjectCommandBuilder builds commands that run on individual projects.
|
// ProjectCommandBuilder builds commands that run on individual projects.
|
||||||
@@ -174,6 +181,7 @@ type ProjectCommandBuilder interface {
|
|||||||
ProjectApprovePoliciesCommandBuilder
|
ProjectApprovePoliciesCommandBuilder
|
||||||
ProjectVersionCommandBuilder
|
ProjectVersionCommandBuilder
|
||||||
ProjectImportCommandBuilder
|
ProjectImportCommandBuilder
|
||||||
|
ProjectStateCommandBuilder
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultProjectCommandBuilder implements ProjectCommandBuilder.
|
// DefaultProjectCommandBuilder implements ProjectCommandBuilder.
|
||||||
@@ -199,7 +207,7 @@ type DefaultProjectCommandBuilder struct {
|
|||||||
|
|
||||||
// See ProjectCommandBuilder.BuildAutoplanCommands.
|
// See ProjectCommandBuilder.BuildAutoplanCommands.
|
||||||
func (p *DefaultProjectCommandBuilder) BuildAutoplanCommands(ctx *command.Context) ([]command.ProjectContext, error) {
|
func (p *DefaultProjectCommandBuilder) BuildAutoplanCommands(ctx *command.Context) ([]command.ProjectContext, error) {
|
||||||
projCtxs, err := p.buildAllCommandsByCfg(ctx, command.Plan, nil, false)
|
projCtxs, err := p.buildAllCommandsByCfg(ctx, command.Plan, "", nil, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -217,7 +225,7 @@ func (p *DefaultProjectCommandBuilder) BuildAutoplanCommands(ctx *command.Contex
|
|||||||
// See ProjectCommandBuilder.BuildPlanCommands.
|
// See ProjectCommandBuilder.BuildPlanCommands.
|
||||||
func (p *DefaultProjectCommandBuilder) BuildPlanCommands(ctx *command.Context, cmd *CommentCommand) ([]command.ProjectContext, error) {
|
func (p *DefaultProjectCommandBuilder) BuildPlanCommands(ctx *command.Context, cmd *CommentCommand) ([]command.ProjectContext, error) {
|
||||||
if !cmd.IsForSpecificProject() {
|
if !cmd.IsForSpecificProject() {
|
||||||
return p.buildAllCommandsByCfg(ctx, cmd.CommandName(), cmd.Flags, cmd.Verbose)
|
return p.buildAllCommandsByCfg(ctx, cmd.CommandName(), cmd.SubName, cmd.Flags, cmd.Verbose)
|
||||||
}
|
}
|
||||||
pcc, err := p.buildProjectPlanCommand(ctx, cmd)
|
pcc, err := p.buildProjectPlanCommand(ctx, cmd)
|
||||||
return pcc, err
|
return pcc, err
|
||||||
@@ -228,7 +236,7 @@ func (p *DefaultProjectCommandBuilder) BuildApplyCommands(ctx *command.Context,
|
|||||||
if !cmd.IsForSpecificProject() {
|
if !cmd.IsForSpecificProject() {
|
||||||
return p.buildAllProjectCommandsByPlan(ctx, cmd)
|
return p.buildAllProjectCommandsByPlan(ctx, cmd)
|
||||||
}
|
}
|
||||||
pac, err := p.buildProjectApplyCommand(ctx, cmd)
|
pac, err := p.buildProjectCommand(ctx, cmd)
|
||||||
return pac, err
|
return pac, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,21 +248,29 @@ func (p *DefaultProjectCommandBuilder) BuildVersionCommands(ctx *command.Context
|
|||||||
if !cmd.IsForSpecificProject() {
|
if !cmd.IsForSpecificProject() {
|
||||||
return p.buildAllProjectCommandsByPlan(ctx, cmd)
|
return p.buildAllProjectCommandsByPlan(ctx, cmd)
|
||||||
}
|
}
|
||||||
pac, err := p.buildProjectVersionCommand(ctx, cmd)
|
pac, err := p.buildProjectCommand(ctx, cmd)
|
||||||
return pac, err
|
return pac, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *DefaultProjectCommandBuilder) BuildImportCommands(ctx *command.Context, cmd *CommentCommand) ([]command.ProjectContext, error) {
|
func (p *DefaultProjectCommandBuilder) BuildImportCommands(ctx *command.Context, cmd *CommentCommand) ([]command.ProjectContext, error) {
|
||||||
if !cmd.IsForSpecificProject() {
|
if !cmd.IsForSpecificProject() {
|
||||||
// import discard a plan file, so use buildAllCommandsByCfg instead buildAllProjectCommandsByPlan.
|
// import discard a plan file, so use buildAllCommandsByCfg instead buildAllProjectCommandsByPlan.
|
||||||
return p.buildAllCommandsByCfg(ctx, cmd.CommandName(), cmd.Flags, cmd.Verbose)
|
return p.buildAllCommandsByCfg(ctx, cmd.CommandName(), cmd.SubName, cmd.Flags, cmd.Verbose)
|
||||||
}
|
}
|
||||||
return p.buildProjectImportCommand(ctx, cmd)
|
return p.buildProjectCommand(ctx, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DefaultProjectCommandBuilder) BuildStateRmCommands(ctx *command.Context, cmd *CommentCommand) ([]command.ProjectContext, error) {
|
||||||
|
if !cmd.IsForSpecificProject() {
|
||||||
|
// state rm discard a plan file, so use buildAllCommandsByCfg instead buildAllProjectCommandsByPlan.
|
||||||
|
return p.buildAllCommandsByCfg(ctx, cmd.CommandName(), cmd.SubName, cmd.Flags, cmd.Verbose)
|
||||||
|
}
|
||||||
|
return p.buildProjectCommand(ctx, cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildAllCommandsByCfg builds init contexts for all projects we determine were
|
// buildAllCommandsByCfg builds init contexts for all projects we determine were
|
||||||
// modified in this ctx.
|
// modified in this ctx.
|
||||||
func (p *DefaultProjectCommandBuilder) buildAllCommandsByCfg(ctx *command.Context, cmdName command.Name, commentFlags []string, verbose bool) ([]command.ProjectContext, error) {
|
func (p *DefaultProjectCommandBuilder) buildAllCommandsByCfg(ctx *command.Context, cmdName command.Name, subCmdName string, commentFlags []string, verbose bool) ([]command.ProjectContext, error) {
|
||||||
// We'll need the list of modified files.
|
// We'll need the list of modified files.
|
||||||
modifiedFiles, err := p.VCSClient.GetModifiedFiles(ctx.Pull.BaseRepo, ctx.Pull)
|
modifiedFiles, err := p.VCSClient.GetModifiedFiles(ctx.Pull.BaseRepo, ctx.Pull)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -345,6 +361,7 @@ func (p *DefaultProjectCommandBuilder) buildAllCommandsByCfg(ctx *command.Contex
|
|||||||
p.ProjectCommandContextBuilder.BuildProjectContext(
|
p.ProjectCommandContextBuilder.BuildProjectContext(
|
||||||
ctx,
|
ctx,
|
||||||
cmdName,
|
cmdName,
|
||||||
|
subCmdName,
|
||||||
mergedCfg,
|
mergedCfg,
|
||||||
commentFlags,
|
commentFlags,
|
||||||
repoDir,
|
repoDir,
|
||||||
@@ -391,6 +408,7 @@ func (p *DefaultProjectCommandBuilder) buildAllCommandsByCfg(ctx *command.Contex
|
|||||||
p.ProjectCommandContextBuilder.BuildProjectContext(
|
p.ProjectCommandContextBuilder.BuildProjectContext(
|
||||||
ctx,
|
ctx,
|
||||||
cmdName,
|
cmdName,
|
||||||
|
subCmdName,
|
||||||
pCfg,
|
pCfg,
|
||||||
commentFlags,
|
commentFlags,
|
||||||
repoDir,
|
repoDir,
|
||||||
@@ -498,6 +516,7 @@ func (p *DefaultProjectCommandBuilder) buildProjectPlanCommand(ctx *command.Cont
|
|||||||
return p.buildProjectCommandCtx(
|
return p.buildProjectCommandCtx(
|
||||||
ctx,
|
ctx,
|
||||||
command.Plan,
|
command.Plan,
|
||||||
|
"",
|
||||||
cmd.ProjectName,
|
cmd.ProjectName,
|
||||||
cmd.Flags,
|
cmd.Flags,
|
||||||
defaultRepoDir,
|
defaultRepoDir,
|
||||||
@@ -590,7 +609,7 @@ func (p *DefaultProjectCommandBuilder) buildAllProjectCommandsByPlan(ctx *comman
|
|||||||
|
|
||||||
var cmds []command.ProjectContext
|
var cmds []command.ProjectContext
|
||||||
for _, plan := range plans {
|
for _, plan := range plans {
|
||||||
commentCmds, err := p.buildProjectCommandCtx(ctx, commentCmd.CommandName(), plan.ProjectName, commentCmd.Flags, defaultRepoDir, plan.RepoRelDir, plan.Workspace, commentCmd.Verbose)
|
commentCmds, err := p.buildProjectCommandCtx(ctx, commentCmd.CommandName(), commentCmd.SubName, plan.ProjectName, commentCmd.Flags, defaultRepoDir, plan.RepoRelDir, plan.Workspace, commentCmd.Verbose)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "building command for dir %q", plan.RepoRelDir)
|
return nil, errors.Wrapf(err, "building command for dir %q", plan.RepoRelDir)
|
||||||
}
|
}
|
||||||
@@ -604,9 +623,9 @@ func (p *DefaultProjectCommandBuilder) buildAllProjectCommandsByPlan(ctx *comman
|
|||||||
return cmds, nil
|
return cmds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildProjectApplyCommand builds an apply command for the single project
|
// buildProjectCommand builds an command for the single project
|
||||||
// identified by cmd.
|
// identified by cmd except plan.
|
||||||
func (p *DefaultProjectCommandBuilder) buildProjectApplyCommand(ctx *command.Context, cmd *CommentCommand) ([]command.ProjectContext, error) {
|
func (p *DefaultProjectCommandBuilder) buildProjectCommand(ctx *command.Context, cmd *CommentCommand) ([]command.ProjectContext, error) {
|
||||||
workspace := DefaultWorkspace
|
workspace := DefaultWorkspace
|
||||||
if cmd.Workspace != "" {
|
if cmd.Workspace != "" {
|
||||||
workspace = cmd.Workspace
|
workspace = cmd.Workspace
|
||||||
@@ -635,89 +654,8 @@ func (p *DefaultProjectCommandBuilder) buildProjectApplyCommand(ctx *command.Con
|
|||||||
|
|
||||||
return p.buildProjectCommandCtx(
|
return p.buildProjectCommandCtx(
|
||||||
ctx,
|
ctx,
|
||||||
command.Apply,
|
cmd.Name,
|
||||||
cmd.ProjectName,
|
cmd.SubName,
|
||||||
cmd.Flags,
|
|
||||||
repoDir,
|
|
||||||
repoRelDir,
|
|
||||||
workspace,
|
|
||||||
cmd.Verbose,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildProjectVersionCommand builds a version command for the single project
|
|
||||||
// identified by cmd.
|
|
||||||
func (p *DefaultProjectCommandBuilder) buildProjectVersionCommand(ctx *command.Context, cmd *CommentCommand) ([]command.ProjectContext, error) {
|
|
||||||
workspace := DefaultWorkspace
|
|
||||||
if cmd.Workspace != "" {
|
|
||||||
workspace = cmd.Workspace
|
|
||||||
}
|
|
||||||
|
|
||||||
var projCtx []command.ProjectContext
|
|
||||||
unlockFn, err := p.WorkingDirLocker.TryLock(ctx.Pull.BaseRepo.FullName, ctx.Pull.Num, workspace, DefaultRepoRelDir)
|
|
||||||
if err != nil {
|
|
||||||
return projCtx, err
|
|
||||||
}
|
|
||||||
defer unlockFn()
|
|
||||||
|
|
||||||
// use the default repository workspace because it is the only one guaranteed to have an atlantis.yaml,
|
|
||||||
// other workspaces will not have the file if they are using pre_workflow_hooks to generate it dynamically
|
|
||||||
repoDir, err := p.WorkingDir.GetWorkingDir(ctx.Pull.BaseRepo, ctx.Pull, DefaultWorkspace)
|
|
||||||
if os.IsNotExist(errors.Cause(err)) {
|
|
||||||
return projCtx, errors.New("no working directory found–did you run plan?")
|
|
||||||
} else if err != nil {
|
|
||||||
return projCtx, err
|
|
||||||
}
|
|
||||||
|
|
||||||
repoRelDir := DefaultRepoRelDir
|
|
||||||
if cmd.RepoRelDir != "" {
|
|
||||||
repoRelDir = cmd.RepoRelDir
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.buildProjectCommandCtx(
|
|
||||||
ctx,
|
|
||||||
command.Version,
|
|
||||||
cmd.ProjectName,
|
|
||||||
cmd.Flags,
|
|
||||||
repoDir,
|
|
||||||
repoRelDir,
|
|
||||||
workspace,
|
|
||||||
cmd.Verbose,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildProjectImportCommand builds a import command for the single project
|
|
||||||
// identified by cmd.
|
|
||||||
func (p *DefaultProjectCommandBuilder) buildProjectImportCommand(ctx *command.Context, cmd *CommentCommand) ([]command.ProjectContext, error) {
|
|
||||||
workspace := DefaultWorkspace
|
|
||||||
if cmd.Workspace != "" {
|
|
||||||
workspace = cmd.Workspace
|
|
||||||
}
|
|
||||||
|
|
||||||
var projCtx []command.ProjectContext
|
|
||||||
unlockFn, err := p.WorkingDirLocker.TryLock(ctx.Pull.BaseRepo.FullName, ctx.Pull.Num, workspace, DefaultRepoRelDir)
|
|
||||||
if err != nil {
|
|
||||||
return projCtx, err
|
|
||||||
}
|
|
||||||
defer unlockFn()
|
|
||||||
|
|
||||||
// use the default repository workspace because it is the only one guaranteed to have an atlantis.yaml,
|
|
||||||
// other workspaces will not have the file if they are using pre_workflow_hooks to generate it dynamically
|
|
||||||
repoDir, err := p.WorkingDir.GetWorkingDir(ctx.Pull.BaseRepo, ctx.Pull, DefaultWorkspace)
|
|
||||||
if os.IsNotExist(errors.Cause(err)) {
|
|
||||||
return projCtx, errors.New("no working directory found–did you run plan?")
|
|
||||||
} else if err != nil {
|
|
||||||
return projCtx, err
|
|
||||||
}
|
|
||||||
|
|
||||||
repoRelDir := DefaultRepoRelDir
|
|
||||||
if cmd.RepoRelDir != "" {
|
|
||||||
repoRelDir = cmd.RepoRelDir
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.buildProjectCommandCtx(
|
|
||||||
ctx,
|
|
||||||
command.Import,
|
|
||||||
cmd.ProjectName,
|
cmd.ProjectName,
|
||||||
cmd.Flags,
|
cmd.Flags,
|
||||||
repoDir,
|
repoDir,
|
||||||
@@ -731,6 +669,7 @@ func (p *DefaultProjectCommandBuilder) buildProjectImportCommand(ctx *command.Co
|
|||||||
// by the parameters.
|
// by the parameters.
|
||||||
func (p *DefaultProjectCommandBuilder) buildProjectCommandCtx(ctx *command.Context,
|
func (p *DefaultProjectCommandBuilder) buildProjectCommandCtx(ctx *command.Context,
|
||||||
cmd command.Name,
|
cmd command.Name,
|
||||||
|
subCmd string,
|
||||||
projectName string,
|
projectName string,
|
||||||
commentFlags []string,
|
commentFlags []string,
|
||||||
repoDir string,
|
repoDir string,
|
||||||
@@ -767,6 +706,7 @@ func (p *DefaultProjectCommandBuilder) buildProjectCommandCtx(ctx *command.Conte
|
|||||||
p.ProjectCommandContextBuilder.BuildProjectContext(
|
p.ProjectCommandContextBuilder.BuildProjectContext(
|
||||||
ctx,
|
ctx,
|
||||||
cmd,
|
cmd,
|
||||||
|
subCmd,
|
||||||
projCfg,
|
projCfg,
|
||||||
commentFlags,
|
commentFlags,
|
||||||
repoDir,
|
repoDir,
|
||||||
@@ -783,6 +723,7 @@ func (p *DefaultProjectCommandBuilder) buildProjectCommandCtx(ctx *command.Conte
|
|||||||
p.ProjectCommandContextBuilder.BuildProjectContext(
|
p.ProjectCommandContextBuilder.BuildProjectContext(
|
||||||
ctx,
|
ctx,
|
||||||
cmd,
|
cmd,
|
||||||
|
subCmd,
|
||||||
projCfg,
|
projCfg,
|
||||||
commentFlags,
|
commentFlags,
|
||||||
repoDir,
|
repoDir,
|
||||||
|
|||||||
@@ -668,7 +668,7 @@ projects:
|
|||||||
PullRequestStatus: models.PullReqStatus{
|
PullRequestStatus: models.PullReqStatus{
|
||||||
Mergeable: true,
|
Mergeable: true,
|
||||||
},
|
},
|
||||||
}, cmd, "", []string{"flag"}, tmp, "project1", "myworkspace", true)
|
}, cmd, "", "", []string{"flag"}, tmp, "project1", "myworkspace", true)
|
||||||
|
|
||||||
if c.expErr != "" {
|
if c.expErr != "" {
|
||||||
ErrEquals(t, c.expErr, err)
|
ErrEquals(t, c.expErr, err)
|
||||||
@@ -876,7 +876,7 @@ projects:
|
|||||||
PullRequestStatus: models.PullReqStatus{
|
PullRequestStatus: models.PullReqStatus{
|
||||||
Mergeable: true,
|
Mergeable: true,
|
||||||
},
|
},
|
||||||
}, cmd, "myproject_[1-2]", []string{"flag"}, tmp, "project1", "myworkspace", true)
|
}, cmd, "", "myproject_[1-2]", []string{"flag"}, tmp, "project1", "myworkspace", true)
|
||||||
|
|
||||||
if c.expErr != "" {
|
if c.expErr != "" {
|
||||||
ErrEquals(t, c.expErr, err)
|
ErrEquals(t, c.expErr, err)
|
||||||
@@ -1113,7 +1113,7 @@ workflows:
|
|||||||
PullRequestStatus: models.PullReqStatus{
|
PullRequestStatus: models.PullReqStatus{
|
||||||
Mergeable: true,
|
Mergeable: true,
|
||||||
},
|
},
|
||||||
}, command.Plan, "", []string{"flag"}, tmp, "project1", "myworkspace", true)
|
}, command.Plan, "", "", []string{"flag"}, tmp, "project1", "myworkspace", true)
|
||||||
|
|
||||||
if c.expErr != "" {
|
if c.expErr != "" {
|
||||||
ErrEquals(t, c.expErr, err)
|
ErrEquals(t, c.expErr, err)
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ type ProjectCommandContextBuilder interface {
|
|||||||
BuildProjectContext(
|
BuildProjectContext(
|
||||||
ctx *command.Context,
|
ctx *command.Context,
|
||||||
cmdName command.Name,
|
cmdName command.Name,
|
||||||
|
subCmdName string,
|
||||||
prjCfg valid.MergedProjectCfg,
|
prjCfg valid.MergedProjectCfg,
|
||||||
commentFlags []string,
|
commentFlags []string,
|
||||||
repoDir string,
|
repoDir string,
|
||||||
@@ -53,6 +54,7 @@ type CommandScopedStatsProjectCommandContextBuilder struct {
|
|||||||
func (cb *CommandScopedStatsProjectCommandContextBuilder) BuildProjectContext(
|
func (cb *CommandScopedStatsProjectCommandContextBuilder) BuildProjectContext(
|
||||||
ctx *command.Context,
|
ctx *command.Context,
|
||||||
cmdName command.Name,
|
cmdName command.Name,
|
||||||
|
subCmdName string,
|
||||||
prjCfg valid.MergedProjectCfg,
|
prjCfg valid.MergedProjectCfg,
|
||||||
commentFlags []string,
|
commentFlags []string,
|
||||||
repoDir string,
|
repoDir string,
|
||||||
@@ -61,7 +63,7 @@ func (cb *CommandScopedStatsProjectCommandContextBuilder) BuildProjectContext(
|
|||||||
cb.ProjectCounter.Inc(1)
|
cb.ProjectCounter.Inc(1)
|
||||||
|
|
||||||
cmds := cb.ProjectCommandContextBuilder.BuildProjectContext(
|
cmds := cb.ProjectCommandContextBuilder.BuildProjectContext(
|
||||||
ctx, cmdName, prjCfg, commentFlags, repoDir, automerge, parallelApply, parallelPlan, verbose, terraformClient,
|
ctx, cmdName, subCmdName, prjCfg, commentFlags, repoDir, automerge, parallelApply, parallelPlan, verbose, terraformClient,
|
||||||
)
|
)
|
||||||
|
|
||||||
projectCmds = []command.ProjectContext{}
|
projectCmds = []command.ProjectContext{}
|
||||||
@@ -85,6 +87,7 @@ type DefaultProjectCommandContextBuilder struct {
|
|||||||
func (cb *DefaultProjectCommandContextBuilder) BuildProjectContext(
|
func (cb *DefaultProjectCommandContextBuilder) BuildProjectContext(
|
||||||
ctx *command.Context,
|
ctx *command.Context,
|
||||||
cmdName command.Name,
|
cmdName command.Name,
|
||||||
|
subName string,
|
||||||
prjCfg valid.MergedProjectCfg,
|
prjCfg valid.MergedProjectCfg,
|
||||||
commentFlags []string,
|
commentFlags []string,
|
||||||
repoDir string,
|
repoDir string,
|
||||||
@@ -105,6 +108,15 @@ func (cb *DefaultProjectCommandContextBuilder) BuildProjectContext(
|
|||||||
}}
|
}}
|
||||||
case command.Import:
|
case command.Import:
|
||||||
steps = prjCfg.Workflow.Import.Steps
|
steps = prjCfg.Workflow.Import.Steps
|
||||||
|
case command.State:
|
||||||
|
switch subName {
|
||||||
|
case "rm":
|
||||||
|
steps = prjCfg.Workflow.StateRm.Steps
|
||||||
|
default:
|
||||||
|
// comment_parser prevent invalid subcommand, so not need to handle this.
|
||||||
|
// if comes here, state_command_runner will respond on PR, so it's enough to do log only.
|
||||||
|
ctx.Log.Err("unknown state subcommand: %s", subName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If TerraformVersion not defined in config file look for a
|
// If TerraformVersion not defined in config file look for a
|
||||||
@@ -143,6 +155,7 @@ type PolicyCheckProjectCommandContextBuilder struct {
|
|||||||
func (cb *PolicyCheckProjectCommandContextBuilder) BuildProjectContext(
|
func (cb *PolicyCheckProjectCommandContextBuilder) BuildProjectContext(
|
||||||
ctx *command.Context,
|
ctx *command.Context,
|
||||||
cmdName command.Name,
|
cmdName command.Name,
|
||||||
|
subCmdName string,
|
||||||
prjCfg valid.MergedProjectCfg,
|
prjCfg valid.MergedProjectCfg,
|
||||||
commentFlags []string,
|
commentFlags []string,
|
||||||
repoDir string,
|
repoDir string,
|
||||||
@@ -159,6 +172,7 @@ func (cb *PolicyCheckProjectCommandContextBuilder) BuildProjectContext(
|
|||||||
projectCmds = cb.ProjectCommandContextBuilder.BuildProjectContext(
|
projectCmds = cb.ProjectCommandContextBuilder.BuildProjectContext(
|
||||||
ctx,
|
ctx,
|
||||||
cmdName,
|
cmdName,
|
||||||
|
subCmdName,
|
||||||
prjCfg,
|
prjCfg,
|
||||||
commentFlags,
|
commentFlags,
|
||||||
repoDir,
|
repoDir,
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func TestProjectCommandContextBuilder_PullStatus(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
result := subject.BuildProjectContext(commandCtx, command.Plan, projCfg, []string{}, "some/dir", false, false, false, false, terraformClient)
|
result := subject.BuildProjectContext(commandCtx, command.Plan, "", projCfg, []string{}, "some/dir", false, false, false, false, terraformClient)
|
||||||
assert.Equal(t, models.ErroredPolicyCheckStatus, result[0].ProjectPlanStatus)
|
assert.Equal(t, models.ErroredPolicyCheckStatus, result[0].ProjectPlanStatus)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ func TestProjectCommandContextBuilder_PullStatus(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
result := subject.BuildProjectContext(commandCtx, command.Plan, projCfg, []string{}, "some/dir", false, false, false, false, terraformClient)
|
result := subject.BuildProjectContext(commandCtx, command.Plan, "", projCfg, []string{}, "some/dir", false, false, false, false, terraformClient)
|
||||||
|
|
||||||
assert.Equal(t, models.ErroredPolicyCheckStatus, result[0].ProjectPlanStatus)
|
assert.Equal(t, models.ErroredPolicyCheckStatus, result[0].ProjectPlanStatus)
|
||||||
})
|
})
|
||||||
@@ -101,7 +101,7 @@ func TestProjectCommandContextBuilder_PullStatus(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
result := subject.BuildProjectContext(commandCtx, command.Plan, projCfg, []string{}, "some/dir", false, true, false, false, terraformClient)
|
result := subject.BuildProjectContext(commandCtx, command.Plan, "", projCfg, []string{}, "some/dir", false, true, false, false, terraformClient)
|
||||||
|
|
||||||
assert.True(t, result[0].ParallelApplyEnabled)
|
assert.True(t, result[0].ParallelApplyEnabled)
|
||||||
assert.False(t, result[0].ParallelPlanEnabled)
|
assert.False(t, result[0].ParallelPlanEnabled)
|
||||||
|
|||||||
@@ -118,6 +118,11 @@ type ProjectImportCommandRunner interface {
|
|||||||
Import(ctx command.ProjectContext) command.ProjectResult
|
Import(ctx command.ProjectContext) command.ProjectResult
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ProjectStateCommandRunner interface {
|
||||||
|
// StateRm runs terraform state rm for the project described by ctx.
|
||||||
|
StateRm(ctx command.ProjectContext) command.ProjectResult
|
||||||
|
}
|
||||||
|
|
||||||
// ProjectCommandRunner runs project commands. A project command is a command
|
// ProjectCommandRunner runs project commands. A project command is a command
|
||||||
// for a specific TF project.
|
// for a specific TF project.
|
||||||
type ProjectCommandRunner interface {
|
type ProjectCommandRunner interface {
|
||||||
@@ -127,6 +132,7 @@ type ProjectCommandRunner interface {
|
|||||||
ProjectApprovePoliciesCommandRunner
|
ProjectApprovePoliciesCommandRunner
|
||||||
ProjectVersionCommandRunner
|
ProjectVersionCommandRunner
|
||||||
ProjectImportCommandRunner
|
ProjectImportCommandRunner
|
||||||
|
ProjectStateCommandRunner
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate pegomock generate -m --package mocks -o mocks/mock_job_url_setter.go JobURLSetter
|
//go:generate pegomock generate -m --package mocks -o mocks/mock_job_url_setter.go JobURLSetter
|
||||||
@@ -200,6 +206,7 @@ type DefaultProjectCommandRunner struct {
|
|||||||
PolicyCheckStepRunner StepRunner
|
PolicyCheckStepRunner StepRunner
|
||||||
VersionStepRunner StepRunner
|
VersionStepRunner StepRunner
|
||||||
ImportStepRunner StepRunner
|
ImportStepRunner StepRunner
|
||||||
|
StateRmStepRunner StepRunner
|
||||||
RunStepRunner CustomStepRunner
|
RunStepRunner CustomStepRunner
|
||||||
EnvStepRunner EnvStepRunner
|
EnvStepRunner EnvStepRunner
|
||||||
MultiEnvStepRunner MultiEnvStepRunner
|
MultiEnvStepRunner MultiEnvStepRunner
|
||||||
@@ -292,6 +299,21 @@ func (p *DefaultProjectCommandRunner) Import(ctx command.ProjectContext) command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StateRm runs terraform state rm for the project described by ctx.
|
||||||
|
func (p *DefaultProjectCommandRunner) StateRm(ctx command.ProjectContext) command.ProjectResult {
|
||||||
|
stateRmSuccess, failure, err := p.doStateRm(ctx)
|
||||||
|
return command.ProjectResult{
|
||||||
|
Command: command.State,
|
||||||
|
SubCommand: "rm",
|
||||||
|
StateRmSuccess: stateRmSuccess,
|
||||||
|
Error: err,
|
||||||
|
Failure: failure,
|
||||||
|
RepoRelDir: ctx.RepoRelDir,
|
||||||
|
Workspace: ctx.Workspace,
|
||||||
|
ProjectName: ctx.ProjectName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p *DefaultProjectCommandRunner) doApprovePolicies(ctx command.ProjectContext) (*models.PolicyCheckSuccess, string, error) {
|
func (p *DefaultProjectCommandRunner) doApprovePolicies(ctx command.ProjectContext) (*models.PolicyCheckSuccess, string, error) {
|
||||||
|
|
||||||
// TODO: Make this a bit smarter
|
// TODO: Make this a bit smarter
|
||||||
@@ -539,6 +561,47 @@ func (p *DefaultProjectCommandRunner) doImport(ctx command.ProjectContext) (out
|
|||||||
}, "", nil
|
}, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *DefaultProjectCommandRunner) doStateRm(ctx command.ProjectContext) (out *models.StateRmSuccess, failure string, err error) {
|
||||||
|
// Clone is idempotent so okay to run even if the repo was already cloned.
|
||||||
|
repoDir, _, cloneErr := p.WorkingDir.Clone(ctx.Log, ctx.HeadRepo, ctx.Pull, ctx.Workspace)
|
||||||
|
if cloneErr != nil {
|
||||||
|
return nil, "", cloneErr
|
||||||
|
}
|
||||||
|
projAbsPath := filepath.Join(repoDir, ctx.RepoRelDir)
|
||||||
|
if _, err = os.Stat(projAbsPath); os.IsNotExist(err) {
|
||||||
|
return nil, "", DirNotExistErr{RepoRelDir: ctx.RepoRelDir}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acquire Atlantis lock for this repo/dir/workspace.
|
||||||
|
lockAttempt, err := p.Locker.TryLock(ctx.Log, ctx.Pull, ctx.User, ctx.Workspace, models.NewProject(ctx.Pull.BaseRepo.FullName, ctx.RepoRelDir), ctx.RepoLocking)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", errors.Wrap(err, "acquiring lock")
|
||||||
|
}
|
||||||
|
if !lockAttempt.LockAcquired {
|
||||||
|
return nil, lockAttempt.LockFailureReason, nil
|
||||||
|
}
|
||||||
|
ctx.Log.Debug("acquired lock for project")
|
||||||
|
|
||||||
|
// Acquire internal lock for the directory we're going to operate in.
|
||||||
|
unlockFn, err := p.WorkingDirLocker.TryLock(ctx.Pull.BaseRepo.FullName, ctx.Pull.Num, ctx.Workspace, ctx.RepoRelDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
defer unlockFn()
|
||||||
|
|
||||||
|
outputs, err := p.runSteps(ctx.Steps, ctx, projAbsPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("%s\n%s", err, strings.Join(outputs, "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// after state rm, re-plan command is required without state rm args
|
||||||
|
rePlanCmd := strings.TrimSpace(strings.Split(ctx.RePlanCmd, "--")[0])
|
||||||
|
return &models.StateRmSuccess{
|
||||||
|
Output: strings.Join(outputs, "\n"),
|
||||||
|
RePlanCmd: rePlanCmd,
|
||||||
|
}, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p *DefaultProjectCommandRunner) runSteps(steps []valid.Step, ctx command.ProjectContext, absPath string) ([]string, error) {
|
func (p *DefaultProjectCommandRunner) runSteps(steps []valid.Step, ctx command.ProjectContext, absPath string) ([]string, error) {
|
||||||
var outputs []string
|
var outputs []string
|
||||||
|
|
||||||
@@ -561,6 +624,8 @@ func (p *DefaultProjectCommandRunner) runSteps(steps []valid.Step, ctx command.P
|
|||||||
out, err = p.VersionStepRunner.Run(ctx, step.ExtraArgs, absPath, envs)
|
out, err = p.VersionStepRunner.Run(ctx, step.ExtraArgs, absPath, envs)
|
||||||
case "import":
|
case "import":
|
||||||
out, err = p.ImportStepRunner.Run(ctx, step.ExtraArgs, absPath, envs)
|
out, err = p.ImportStepRunner.Run(ctx, step.ExtraArgs, absPath, envs)
|
||||||
|
case "state_rm":
|
||||||
|
out, err = p.StateRmStepRunner.Run(ctx, step.ExtraArgs, absPath, envs)
|
||||||
case "run":
|
case "run":
|
||||||
out, err = p.RunStepRunner.Run(ctx, step.RunCommand, absPath, envs, true)
|
out, err = p.RunStepRunner.Run(ctx, step.RunCommand, absPath, envs, true)
|
||||||
case "env":
|
case "env":
|
||||||
|
|||||||
@@ -684,6 +684,7 @@ func TestDefaultProjectCommandRunner_Import(t *testing.T) {
|
|||||||
RegisterMockTestingT(t)
|
RegisterMockTestingT(t)
|
||||||
mockInit := mocks.NewMockStepRunner()
|
mockInit := mocks.NewMockStepRunner()
|
||||||
mockImport := mocks.NewMockStepRunner()
|
mockImport := mocks.NewMockStepRunner()
|
||||||
|
mockStateRm := mocks.NewMockStepRunner()
|
||||||
mockWorkingDir := mocks.NewMockWorkingDir()
|
mockWorkingDir := mocks.NewMockWorkingDir()
|
||||||
mockLocker := mocks.NewMockProjectLocker()
|
mockLocker := mocks.NewMockProjectLocker()
|
||||||
mockSender := mocks.NewMockWebhooksSender()
|
mockSender := mocks.NewMockWebhooksSender()
|
||||||
@@ -696,6 +697,7 @@ func TestDefaultProjectCommandRunner_Import(t *testing.T) {
|
|||||||
LockURLGenerator: mockURLGenerator{},
|
LockURLGenerator: mockURLGenerator{},
|
||||||
InitStepRunner: mockInit,
|
InitStepRunner: mockInit,
|
||||||
ImportStepRunner: mockImport,
|
ImportStepRunner: mockImport,
|
||||||
|
StateRmStepRunner: mockStateRm,
|
||||||
WorkingDir: mockWorkingDir,
|
WorkingDir: mockWorkingDir,
|
||||||
Webhooks: mockSender,
|
Webhooks: mockSender,
|
||||||
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
|
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func (c *PullUpdater) updatePull(ctx *command.Context, cmd PullCommand, res comm
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
comment := c.MarkdownRenderer.Render(res, cmd.CommandName(), ctx.Log.GetHistory(), cmd.IsVerbose(), ctx.Pull.BaseRepo.VCSHost.Type)
|
comment := c.MarkdownRenderer.Render(res, cmd.CommandName(), cmd.SubCommandName(), ctx.Log.GetHistory(), cmd.IsVerbose(), ctx.Pull.BaseRepo.VCSHost.Type)
|
||||||
if err := c.VCSClient.CreateComment(ctx.Pull.BaseRepo, ctx.Pull.Num, comment, cmd.CommandName().String()); err != nil {
|
if err := c.VCSClient.CreateComment(ctx.Pull.BaseRepo, ctx.Pull.Num, comment, cmd.CommandName().String()); err != nil {
|
||||||
ctx.Log.Err("unable to comment: %s", err)
|
ctx.Log.Err("unable to comment: %s", err)
|
||||||
}
|
}
|
||||||
|
|||||||
46
server/events/state_command_runner.go
Normal file
46
server/events/state_command_runner.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package events
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/runatlantis/atlantis/server/events/command"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewStateCommandRunner(
|
||||||
|
pullUpdater *PullUpdater,
|
||||||
|
prjCmdBuilder ProjectStateCommandBuilder,
|
||||||
|
prjCmdRunner ProjectStateCommandRunner,
|
||||||
|
) *StateCommandRunner {
|
||||||
|
return &StateCommandRunner{
|
||||||
|
pullUpdater: pullUpdater,
|
||||||
|
prjCmdBuilder: prjCmdBuilder,
|
||||||
|
prjCmdRunner: prjCmdRunner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type StateCommandRunner struct {
|
||||||
|
pullUpdater *PullUpdater
|
||||||
|
prjCmdBuilder ProjectStateCommandBuilder
|
||||||
|
prjCmdRunner ProjectStateCommandRunner
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *StateCommandRunner) Run(ctx *command.Context, cmd *CommentCommand) {
|
||||||
|
var result command.Result
|
||||||
|
switch cmd.SubName {
|
||||||
|
case "rm":
|
||||||
|
result = v.runRm(ctx, cmd)
|
||||||
|
default:
|
||||||
|
result = command.Result{
|
||||||
|
Failure: fmt.Sprintf("unknown state subcommand %s", cmd.SubName),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.pullUpdater.updatePull(ctx, cmd, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *StateCommandRunner) runRm(ctx *command.Context, cmd *CommentCommand) command.Result {
|
||||||
|
projectCmds, err := v.prjCmdBuilder.BuildStateRmCommands(ctx, cmd)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Log.Warn("Error %s", err)
|
||||||
|
}
|
||||||
|
return runProjectCmds(projectCmds, v.prjCmdRunner.StateRm)
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@
|
|||||||
{{ .Output }}
|
{{ .Output }}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
:put_litter_in_its_place: A plan file was discarded. Re-plan would be required before applying.
|
||||||
|
|
||||||
* :repeat: To **plan** this project again, comment:
|
* :repeat: To **plan** this project again, comment:
|
||||||
* `{{ .RePlanCmd }}`
|
* `{{.RePlanCmd}}`
|
||||||
{{ end -}}
|
{{ end -}}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
{{ .Output }}
|
{{ .Output }}
|
||||||
```
|
```
|
||||||
</details>
|
</details>
|
||||||
|
:put_litter_in_its_place: A plan file was discarded. Re-plan would be required before applying.
|
||||||
|
|
||||||
* :repeat: To **plan** this project again, comment:
|
* :repeat: To **plan** this project again, comment:
|
||||||
* `{{ .RePlanCmd }}`
|
* `{{ .RePlanCmd }}`
|
||||||
{{ end -}}
|
{{ end -}}
|
||||||
|
|||||||
10
server/events/templates/multi_project_state_rm.tmpl
Normal file
10
server/events/templates/multi_project_state_rm.tmpl
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{{ define "multiProjectStateRm" -}}
|
||||||
|
{{ template "multiProjectHeader" . }}
|
||||||
|
{{ range $i, $result := .Results -}}
|
||||||
|
### {{ add $i 1 }}. {{ if $result.ProjectName }}project: `{{ $result.ProjectName }}` {{ end }}dir: `{{ $result.RepoRelDir }}` workspace: `{{ $result.Workspace }}`
|
||||||
|
{{ $result.Rendered}}
|
||||||
|
|
||||||
|
---
|
||||||
|
{{ end -}}
|
||||||
|
{{- template "log" . -}}
|
||||||
|
{{ end -}}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{{ define "singleProjectStateRm" -}}
|
||||||
|
{{$result := index .Results 0}}Ran {{.Command}} `{{.SubCommand}}` for {{ if $result.ProjectName }}project: `{{$result.ProjectName}}` {{ end }}dir: `{{$result.RepoRelDir}}` workspace: `{{$result.Workspace}}`
|
||||||
|
|
||||||
|
{{$result.Rendered}}
|
||||||
|
{{ template "log" . }}
|
||||||
|
{{ end }}
|
||||||
10
server/events/templates/state_rm_success_unwrapped.tmpl
Normal file
10
server/events/templates/state_rm_success_unwrapped.tmpl
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{{ define "stateRmSuccessUnwrapped" -}}
|
||||||
|
```diff
|
||||||
|
{{ .Output }}
|
||||||
|
```
|
||||||
|
|
||||||
|
:put_litter_in_its_place: A plan file was discarded. Re-plan would be required before applying.
|
||||||
|
|
||||||
|
* :repeat: To **plan** this project again, comment:
|
||||||
|
* `{{.RePlanCmd}}`
|
||||||
|
{{ end }}
|
||||||
11
server/events/templates/state_rm_success_wrapped.tmpl
Normal file
11
server/events/templates/state_rm_success_wrapped.tmpl
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{{ define "stateRmSuccessWrapped" -}}
|
||||||
|
<details><summary>Show Output</summary>
|
||||||
|
```diff
|
||||||
|
{{ .Output }}
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
:put_litter_in_its_place: A plan file was discarded. Re-plan would be required before applying.
|
||||||
|
|
||||||
|
* :repeat: To **plan** this project again, comment:
|
||||||
|
* `{{.RePlanCmd}}`
|
||||||
|
{{ end }}
|
||||||
@@ -619,6 +619,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
|
|||||||
DefaultTFVersion: defaultTfVersion,
|
DefaultTFVersion: defaultTfVersion,
|
||||||
},
|
},
|
||||||
ImportStepRunner: runtime.NewImportStepRunner(terraformClient, defaultTfVersion),
|
ImportStepRunner: runtime.NewImportStepRunner(terraformClient, defaultTfVersion),
|
||||||
|
StateRmStepRunner: runtime.NewStateRmStepRunner(terraformClient, defaultTfVersion),
|
||||||
WorkingDir: workingDir,
|
WorkingDir: workingDir,
|
||||||
Webhooks: webhooksManager,
|
Webhooks: webhooksManager,
|
||||||
WorkingDirLocker: workingDirLocker,
|
WorkingDirLocker: workingDirLocker,
|
||||||
@@ -730,6 +731,12 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
|
|||||||
instrumentedProjectCmdRunner,
|
instrumentedProjectCmdRunner,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
stateCommandRunner := events.NewStateCommandRunner(
|
||||||
|
pullUpdater,
|
||||||
|
projectCommandBuilder,
|
||||||
|
instrumentedProjectCmdRunner,
|
||||||
|
)
|
||||||
|
|
||||||
commentCommandRunnerByCmd := map[command.Name]events.CommentCommandRunner{
|
commentCommandRunnerByCmd := map[command.Name]events.CommentCommandRunner{
|
||||||
command.Plan: planCommandRunner,
|
command.Plan: planCommandRunner,
|
||||||
command.Apply: applyCommandRunner,
|
command.Apply: applyCommandRunner,
|
||||||
@@ -737,6 +744,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
|
|||||||
command.Unlock: unlockCommandRunner,
|
command.Unlock: unlockCommandRunner,
|
||||||
command.Version: versionCommandRunner,
|
command.Version: versionCommandRunner,
|
||||||
command.Import: importCommandRunner,
|
command.Import: importCommandRunner,
|
||||||
|
command.State: stateCommandRunner,
|
||||||
}
|
}
|
||||||
|
|
||||||
githubTeamAllowlistChecker, err := events.NewTeamAllowlistChecker(userConfig.GithubTeamAllowlist)
|
githubTeamAllowlistChecker, err := events.NewTeamAllowlistChecker(userConfig.GithubTeamAllowlist)
|
||||||
|
|||||||
@@ -19,23 +19,23 @@ func TestUserConfig_ToAllowCommandNames(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "full commands can be parsed by comma",
|
name: "full commands can be parsed by comma",
|
||||||
allowCommands: "apply,plan,unlock,policy_check,approve_policies,version,import",
|
allowCommands: "apply,plan,unlock,policy_check,approve_policies,version,import,state",
|
||||||
want: []command.Name{
|
want: []command.Name{
|
||||||
command.Apply, command.Plan, command.Unlock, command.PolicyCheck, command.ApprovePolicies, command.Version, command.Import,
|
command.Apply, command.Plan, command.Unlock, command.PolicyCheck, command.ApprovePolicies, command.Version, command.Import, command.State,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "all",
|
name: "all",
|
||||||
allowCommands: "all",
|
allowCommands: "all",
|
||||||
want: []command.Name{
|
want: []command.Name{
|
||||||
command.Version, command.Plan, command.Apply, command.Unlock, command.ApprovePolicies, command.Import,
|
command.Version, command.Plan, command.Apply, command.Unlock, command.ApprovePolicies, command.Import, command.State,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "all with others returns same with all result",
|
name: "all with others returns same with all result",
|
||||||
allowCommands: "all,plan",
|
allowCommands: "all,plan",
|
||||||
want: []command.Name{
|
want: []command.Name{
|
||||||
command.Version, command.Plan, command.Apply, command.Unlock, command.ApprovePolicies, command.Import,
|
command.Version, command.Plan, command.Apply, command.Unlock, command.ApprovePolicies, command.Import, command.State,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user