Skip to content

Commit

Permalink
Fix remote execution on Terraform Cloud after v1.1.0 (#2793)
Browse files Browse the repository at this point in the history
* Fix remote execution on Terraform Cloud after v1.1.0

* Do not check terraform version for -no-color

* Move string comparison for newer version earlier

* Update remote apply test
  • Loading branch information
lilincmu authored Dec 15, 2022
1 parent 6ecb7a6 commit 812db63
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 26 deletions.
2 changes: 1 addition & 1 deletion server/core/runtime/apply_step_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (a *ApplyStepRunner) Run(ctx command.ProjectContext, extraArgs []string, pa

// TODO: Leverage PlanTypeStepRunnerDelegate here
if IsRemotePlan(contents) {
args := append(append([]string{"apply", "-input=false"}, extraArgs...), ctx.EscapedCommentArgs...)
args := append(append([]string{"apply", "-input=false", "-no-color"}, extraArgs...), ctx.EscapedCommentArgs...)
out, err = a.runRemoteApply(ctx, args, path, planPath, ctx.TerraformVersion, envs)
if err == nil {
out = a.cleanRemoteApplyOutput(out)
Expand Down
2 changes: 1 addition & 1 deletion server/core/runtime/apply_step_runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ null_resource.dir2[1]: Destruction complete after 0s
Apply complete! Resources: 0 added, 0 changed, 1 destroyed.
`, output)

Equals(t, []string{"apply", "-input=false", "extra", "args", "comment", "args"}, tfExec.CalledArgs)
Equals(t, []string{"apply", "-input=false", "-no-color", "extra", "args", "comment", "args"}, tfExec.CalledArgs)
_, err = os.Stat(planPath)
Assert(t, os.IsNotExist(err), "planfile should be deleted")

Expand Down
14 changes: 12 additions & 2 deletions server/core/runtime/plan_step_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,14 @@ func (p *PlanStepRunner) isRemoteOpsErr(output string, err error) bool {
if err == nil {
return false
}
return strings.Contains(output, remoteOpsErr01114) || strings.Contains(output, remoteOpsErr012) || strings.Contains(output, remoteOpsErr100)
return strings.Contains(output, remoteOpsErr110) || strings.Contains(output, remoteOpsErr01114) || strings.Contains(output, remoteOpsErr012) || strings.Contains(output, remoteOpsErr100)
}

// remotePlan runs a terraform plan command compatible with TFE remote
// operations.
func (p *PlanStepRunner) remotePlan(ctx command.ProjectContext, extraArgs []string, path string, tfVersion *version.Version, planFile string, envs map[string]string) (string, error) {
argList := [][]string{
{"plan", "-input=false", "-refresh"},
{"plan", "-input=false", "-refresh", "-no-color"},
extraArgs,
ctx.EscapedCommentArgs,
}
Expand Down Expand Up @@ -340,6 +340,16 @@ The "remote" backend does not support saving the generated execution plan
locally at this time.
`

// remoteOpsErr110 is the error terraform plan will return if this project is
// using Terraform Cloud remote operations in TF 1.1.0 and above
var remoteOpsErr110 = `╷
│ Error: Saving a generated plan is currently not supported
│ Terraform Cloud does not support saving the generated execution plan
│ locally at this time.
`

// remoteOpsHeader is the header we add to the planfile if this plan was
// generated using TFE remote operations.
var remoteOpsHeader = "Atlantis: this plan was created by remote ops\n"
68 changes: 46 additions & 22 deletions server/core/runtime/plan_step_runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -685,22 +685,45 @@ func TestRun_NoOptionalVarsIn012(t *testing.T) {

// Test plans if using remote ops.
func TestRun_RemoteOps(t *testing.T) {
cases := map[string]string{
"0.11.15 error": `Error: Saving a generated plan is currently not supported!
cases := []struct {
name string
tfVersion string
remoteOpsErr string
}{
{
name: "0.11.15 error",
tfVersion: "0.11.15",
remoteOpsErr: `Error: Saving a generated plan is currently not supported!
The "remote" backend does not support saving the generated execution
plan locally at this time.
`,
"0.12.* error": `Error: Saving a generated plan is currently not supported
},
{
name: "0.12.* error",
tfVersion: "0.12.0",
remoteOpsErr: `Error: Saving a generated plan is currently not supported
The "remote" backend does not support saving the generated execution plan
locally at this time.
`,
},
{
name: "1.1.0 error",
tfVersion: "1.1.0",
remoteOpsErr: `╷
│ Error: Saving a generated plan is currently not supported
│ Terraform Cloud does not support saving the generated execution plan
│ locally at this time.
`,
},
}
for name, remoteOpsErr := range cases {
t.Run(name, func(t *testing.T) {
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {

logger := logging.NewNoopLogger(t)
// Now that mocking is set up, we're ready to run the plan.
Expand All @@ -722,7 +745,7 @@ locally at this time.
RegisterMockTestingT(t)
terraform := mocks.NewMockClient()

tfVersion, _ := version.NewVersion("0.11.12")
tfVersion, _ := version.NewVersion(c.tfVersion)
updater := mocks2.NewMockCommitStatusUpdater()
asyncTf := &remotePlanMock{}
s := runtime.PlanStepRunner{
Expand Down Expand Up @@ -763,16 +786,28 @@ locally at this time.
"comment",
"args",
}
if tfVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.12.0"))) {
expPlanArgs = []string{"plan",
"-input=false",
"-refresh",
"-out",
fmt.Sprintf("%q", filepath.Join(absProjectPath, "default.tfplan")),
"extra",
"args",
"comment",
"args",
}
}

planErr := errors.New("exit status 1: err")
planOutput := "\n" + remoteOpsErr
planOutput := "\n" + c.remoteOpsErr
asyncTf.LinesToSend = remotePlanOutput
When(terraform.RunCommandWithVersion(ctx, absProjectPath, expPlanArgs, map[string]string(nil), tfVersion, "default")).
ThenReturn(planOutput, planErr)

output, err := s.Run(ctx, []string{"extra", "args"}, absProjectPath, map[string]string(nil))
Ok(t, err)
Equals(t, `
Assert(t, strings.Contains(output, `
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
- destroy
Expand All @@ -782,26 +817,15 @@ Terraform will perform the following actions:
- null_resource.hi[1]
Plan: 0 to add, 0 to change, 1 to destroy.`, output)
Plan: 0 to add, 0 to change, 1 to destroy.`), "expect plan success")

expRemotePlanArgs := []string{"plan", "-input=false", "-refresh", "extra", "args", "comment", "args"}
expRemotePlanArgs := []string{"plan", "-input=false", "-refresh", "-no-color", "extra", "args", "comment", "args"}
Equals(t, expRemotePlanArgs, asyncTf.CalledArgs)

// Verify that the fake plan file we write has the correct contents.
bytes, err := os.ReadFile(filepath.Join(absProjectPath, "default.tfplan"))
Ok(t, err)
Equals(t, `Atlantis: this plan was created by remote ops
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
- null_resource.hi[1]
Plan: 0 to add, 0 to change, 1 to destroy.`, string(bytes))
Assert(t, strings.HasPrefix(string(bytes), "Atlantis: this plan was created by remote ops"), "expect remote plan")

// Ensure that the status was updated with the runURL.
runURL := "https://app.terraform.io/app/lkysow-enterprises/atlantis-tfe-test/runs/run-is4oVvJfrkud1KvE"
Expand Down

0 comments on commit 812db63

Please sign in to comment.