Skip to content

Commit

Permalink
Detect remote ops for TF >= 0.12.0 (#705)
Browse files Browse the repository at this point in the history
The error message we use to detect remote ops changed between 0.11.14
and 0.12.0. Recognize this new error message.
  • Loading branch information
lkysow authored Jul 12, 2019
1 parent 9a21eef commit b7d4e99
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 83 deletions.
18 changes: 14 additions & 4 deletions server/events/runtime/plan_step_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func (p *PlanStepRunner) isRemoteOpsErr(output string, err error) bool {
if err == nil {
return false
}
return strings.Contains(output, remoteOpsErr)
return strings.Contains(output, remoteOpsErr01114) || strings.Contains(output, remoteOpsErr012)
}

// remotePlan runs a terraform plan command compatible with TFE remote
Expand Down Expand Up @@ -298,15 +298,25 @@ func (p *PlanStepRunner) runRemotePlan(

var vTwelveAndUp = MustConstraint(">=0.12-a")

// remoteOpsErr is the error terraform plan will return if this project is
// using TFE remote operations.
var remoteOpsErr = `Error: Saving a generated plan is currently not supported!
// remoteOpsErr01114 is the error terraform plan will return if this project is
// using TFE remote operations in TF 0.11.14.
var remoteOpsErr01114 = `Error: Saving a generated plan is currently not supported!
The "remote" backend does not support saving the generated execution
plan locally at this time.
`

// remoteOpsErr012 is the error terraform plan will return if this project is
// using TFE remote operations in TF 0.12.{0-4}. Later versions haven't been
// released yet at this time.
var remoteOpsErr012 = `Error: Saving a generated plan is currently not supported
The "remote" backend 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"
171 changes: 92 additions & 79 deletions server/events/runtime/plan_step_runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"strings"
"testing"

version "github.com/hashicorp/go-version"
"github.com/hashicorp/go-version"
mocks2 "github.com/runatlantis/atlantis/server/events/mocks"
"github.com/runatlantis/atlantis/server/events/terraform"

Expand Down Expand Up @@ -659,82 +659,93 @@ func TestRun_NoOptionalVarsIn012(t *testing.T) {

// Test plans if using remote ops.
func TestRun_RemoteOps(t *testing.T) {
RegisterMockTestingT(t)
terraform := mocks.NewMockClient()
asyncTf := &remotePlanMock{}
cases := map[string]string{
"0.11.14 error": `Error: Saving a generated plan is currently not supported!
tfVersion, _ := version.NewVersion("0.11.12")
updater := mocks2.NewMockCommitStatusUpdater()
s := runtime.PlanStepRunner{
TerraformExecutor: terraform,
DefaultTFVersion: tfVersion,
AsyncTFExec: asyncTf,
CommitStatusUpdater: updater,
}
absProjectPath, cleanup := TempDir(t)
defer cleanup()
The "remote" backend does not support saving the generated execution
plan locally at this time.
// First, terraform workspace gets run.
When(terraform.RunCommandWithVersion(
nil,
absProjectPath,
[]string{"workspace", "show"},
tfVersion,
"default")).ThenReturn("default\n", nil)
`,
"0.12.* error": `Error: Saving a generated plan is currently not supported
// Then the first call to terraform plan should return the remote ops error.
expPlanArgs := []string{"plan",
"-input=false",
"-refresh",
"-no-color",
"-out",
fmt.Sprintf("%q", filepath.Join(absProjectPath, "default.tfplan")),
"-var",
"atlantis_user=\"username\"",
"-var",
"atlantis_repo=\"owner/repo\"",
"-var",
"atlantis_repo_name=\"repo\"",
"-var",
"atlantis_repo_owner=\"owner\"",
"-var",
"atlantis_pull_num=2",
"extra",
"args",
"comment",
"args",
The "remote" backend does not support saving the generated execution plan
locally at this time.
`,
}
for name, remoteOpsErr := range cases {
t.Run(name, func(t *testing.T) {

planErr := errors.New("exit status 1: err")
planOutput := `
Error: Saving a generated plan is currently not supported!
RegisterMockTestingT(t)
terraform := mocks.NewMockClient()
asyncTf := &remotePlanMock{}

The "remote" backend does not support saving the generated execution
plan locally at this time.
tfVersion, _ := version.NewVersion("0.11.12")
updater := mocks2.NewMockCommitStatusUpdater()
s := runtime.PlanStepRunner{
TerraformExecutor: terraform,
DefaultTFVersion: tfVersion,
AsyncTFExec: asyncTf,
CommitStatusUpdater: updater,
}
absProjectPath, cleanup := TempDir(t)
defer cleanup()

// First, terraform workspace gets run.
When(terraform.RunCommandWithVersion(
nil,
absProjectPath,
[]string{"workspace", "show"},
tfVersion,
"default")).ThenReturn("default\n", nil)

`
asyncTf.LinesToSend = remotePlanOutput
When(terraform.RunCommandWithVersion(nil, absProjectPath, expPlanArgs, tfVersion, "default")).
ThenReturn(planOutput, planErr)
// Then the first call to terraform plan should return the remote ops error.
expPlanArgs := []string{"plan",
"-input=false",
"-refresh",
"-no-color",
"-out",
fmt.Sprintf("%q", filepath.Join(absProjectPath, "default.tfplan")),
"-var",
"atlantis_user=\"username\"",
"-var",
"atlantis_repo=\"owner/repo\"",
"-var",
"atlantis_repo_name=\"repo\"",
"-var",
"atlantis_repo_owner=\"owner\"",
"-var",
"atlantis_pull_num=2",
"extra",
"args",
"comment",
"args",
}

// Now that mocking is set up, we're ready to run the plan.
ctx := models.ProjectCommandContext{
Workspace: "default",
RepoRelDir: ".",
User: models.User{Username: "username"},
EscapedCommentArgs: []string{"comment", "args"},
Pull: models.PullRequest{
Num: 2,
},
BaseRepo: models.Repo{
FullName: "owner/repo",
Owner: "owner",
Name: "repo",
},
}
output, err := s.Run(ctx, []string{"extra", "args"}, absProjectPath)
Ok(t, err)
Equals(t, `
planErr := errors.New("exit status 1: err")
planOutput := "\n" + remoteOpsErr
asyncTf.LinesToSend = remotePlanOutput
When(terraform.RunCommandWithVersion(nil, absProjectPath, expPlanArgs, tfVersion, "default")).
ThenReturn(planOutput, planErr)

// Now that mocking is set up, we're ready to run the plan.
ctx := models.ProjectCommandContext{
Workspace: "default",
RepoRelDir: ".",
User: models.User{Username: "username"},
EscapedCommentArgs: []string{"comment", "args"},
Pull: models.PullRequest{
Num: 2,
},
BaseRepo: models.Repo{
FullName: "owner/repo",
Owner: "owner",
Name: "repo",
},
}
output, err := s.Run(ctx, []string{"extra", "args"}, absProjectPath)
Ok(t, err)
Equals(t, `
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
- destroy
Expand All @@ -746,13 +757,13 @@ Terraform will perform the following actions:
Plan: 0 to add, 0 to change, 1 to destroy.`, output)

expRemotePlanArgs := []string{"plan", "-input=false", "-refresh", "-no-color", "extra", "args", "comment", "args"}
Equals(t, expRemotePlanArgs, asyncTf.CalledArgs)
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 := ioutil.ReadFile(filepath.Join(absProjectPath, "default.tfplan"))
Ok(t, err)
Equals(t, `Atlantis: this plan was created by remote ops
// Verify that the fake plan file we write has the correct contents.
bytes, err := ioutil.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:
Expand All @@ -765,10 +776,12 @@ Terraform will perform the following actions:
Plan: 0 to add, 0 to change, 1 to destroy.`, string(bytes))

// Ensure that the status was updated with the runURL.
runURL := "https://app.terraform.io/app/lkysow-enterprises/atlantis-tfe-test/runs/run-is4oVvJfrkud1KvE"
updater.VerifyWasCalledOnce().UpdateProject(ctx, models.PlanCommand, models.PendingCommitStatus, runURL)
updater.VerifyWasCalledOnce().UpdateProject(ctx, models.PlanCommand, models.SuccessCommitStatus, runURL)
// Ensure that the status was updated with the runURL.
runURL := "https://app.terraform.io/app/lkysow-enterprises/atlantis-tfe-test/runs/run-is4oVvJfrkud1KvE"
updater.VerifyWasCalledOnce().UpdateProject(ctx, models.PlanCommand, models.PendingCommitStatus, runURL)
updater.VerifyWasCalledOnce().UpdateProject(ctx, models.PlanCommand, models.SuccessCommitStatus, runURL)
})
}
}

type remotePlanMock struct {
Expand Down

0 comments on commit b7d4e99

Please sign in to comment.