diff --git a/server/events/runtime/plan_step_runner.go b/server/events/runtime/plan_step_runner.go index 46e6bf9e6c..3a8014e627 100644 --- a/server/events/runtime/plan_step_runner.go +++ b/server/events/runtime/plan_step_runner.go @@ -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 @@ -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" diff --git a/server/events/runtime/plan_step_runner_test.go b/server/events/runtime/plan_step_runner_test.go index eebd803bf5..58a00a5e85 100644 --- a/server/events/runtime/plan_step_runner_test.go +++ b/server/events/runtime/plan_step_runner_test.go @@ -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" @@ -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 @@ -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: @@ -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 {