mirror of
https://git.vectorsigma.ru/public/atlantis.git
synced 2026-08-04 07:38:35 +00:00
This is two changes: Replacing all usages of SimpleLogger with it's interface SimpleLogging which makes it easier to swap out loggers going forward Nukes SimpleLogger in favor of StructuredLogger which allows us to better analyze logs in our log management system we choose.
953 lines
27 KiB
Go
953 lines
27 KiB
Go
package runtime_test
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/go-version"
|
|
mocks2 "github.com/runatlantis/atlantis/server/events/mocks"
|
|
"github.com/runatlantis/atlantis/server/events/terraform"
|
|
|
|
. "github.com/petergtz/pegomock"
|
|
"github.com/pkg/errors"
|
|
"github.com/runatlantis/atlantis/server/events/mocks/matchers"
|
|
"github.com/runatlantis/atlantis/server/events/models"
|
|
"github.com/runatlantis/atlantis/server/events/runtime"
|
|
"github.com/runatlantis/atlantis/server/events/terraform/mocks"
|
|
matchers2 "github.com/runatlantis/atlantis/server/events/terraform/mocks/matchers"
|
|
"github.com/runatlantis/atlantis/server/logging"
|
|
logging_matchers "github.com/runatlantis/atlantis/server/logging/mocks/matchers"
|
|
. "github.com/runatlantis/atlantis/testing"
|
|
)
|
|
|
|
func TestRun_NoWorkspaceIn08(t *testing.T) {
|
|
// We don't want any workspace commands to be run in 0.8.
|
|
RegisterMockTestingT(t)
|
|
terraform := mocks.NewMockClient()
|
|
|
|
tfVersion, _ := version.NewVersion("0.8")
|
|
logger := logging.NewNoopLogger(t)
|
|
workspace := "default"
|
|
s := runtime.PlanStepRunner{
|
|
DefaultTFVersion: tfVersion,
|
|
TerraformExecutor: terraform,
|
|
}
|
|
|
|
When(terraform.RunCommandWithVersion(logging_matchers.AnyLoggingSimpleLogging(), AnyString(), AnyStringSlice(), matchers2.AnyMapOfStringToString(), matchers2.AnyPtrToGoVersionVersion(), AnyString())).
|
|
ThenReturn("output", nil)
|
|
output, err := s.Run(models.ProjectCommandContext{
|
|
Log: logger,
|
|
EscapedCommentArgs: []string{"comment", "args"},
|
|
Workspace: workspace,
|
|
RepoRelDir: ".",
|
|
User: models.User{Username: "username"},
|
|
Pull: models.PullRequest{
|
|
Num: 2,
|
|
},
|
|
BaseRepo: models.Repo{
|
|
FullName: "owner/repo",
|
|
Owner: "owner",
|
|
Name: "repo",
|
|
},
|
|
}, []string{"extra", "args"}, "/path", map[string]string(nil))
|
|
Ok(t, err)
|
|
|
|
Equals(t, "output", output)
|
|
terraform.VerifyWasCalledOnce().RunCommandWithVersion(
|
|
logger,
|
|
"/path",
|
|
[]string{"plan",
|
|
"-input=false",
|
|
"-refresh",
|
|
"-no-color",
|
|
"-out",
|
|
"\"/path/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"},
|
|
map[string]string(nil),
|
|
tfVersion,
|
|
workspace)
|
|
|
|
// Verify that no env or workspace commands were run
|
|
terraform.VerifyWasCalled(Never()).RunCommandWithVersion(logger,
|
|
"/path",
|
|
[]string{"env",
|
|
"select",
|
|
"-no-color",
|
|
"workspace"},
|
|
map[string]string(nil),
|
|
tfVersion,
|
|
workspace)
|
|
terraform.VerifyWasCalled(Never()).RunCommandWithVersion(logger,
|
|
"/path",
|
|
[]string{"workspace",
|
|
"select",
|
|
"-no-color",
|
|
"workspace"},
|
|
map[string]string(nil),
|
|
tfVersion,
|
|
workspace)
|
|
}
|
|
|
|
func TestRun_ErrWorkspaceIn08(t *testing.T) {
|
|
// If they attempt to use a workspace other than default in 0.8
|
|
// we should error.
|
|
RegisterMockTestingT(t)
|
|
terraform := mocks.NewMockClient()
|
|
|
|
tfVersion, _ := version.NewVersion("0.8")
|
|
logger := logging.NewNoopLogger(t)
|
|
workspace := "notdefault"
|
|
s := runtime.PlanStepRunner{
|
|
TerraformExecutor: terraform,
|
|
DefaultTFVersion: tfVersion,
|
|
}
|
|
|
|
When(terraform.RunCommandWithVersion(logging_matchers.AnyLoggingSimpleLogging(), AnyString(), AnyStringSlice(), matchers2.AnyMapOfStringToString(), matchers2.AnyPtrToGoVersionVersion(), AnyString())).
|
|
ThenReturn("output", nil)
|
|
_, err := s.Run(models.ProjectCommandContext{
|
|
Log: logger,
|
|
Workspace: workspace,
|
|
RepoRelDir: ".",
|
|
User: models.User{Username: "username"},
|
|
}, []string{"extra", "args"}, "/path", map[string]string(nil))
|
|
ErrEquals(t, "terraform version 0.8.0 does not support workspaces", err)
|
|
}
|
|
|
|
func TestRun_SwitchesWorkspace(t *testing.T) {
|
|
RegisterMockTestingT(t)
|
|
|
|
cases := []struct {
|
|
tfVersion string
|
|
expWorkspaceCmd string
|
|
}{
|
|
{
|
|
"0.9.0",
|
|
"env",
|
|
},
|
|
{
|
|
"0.9.11",
|
|
"env",
|
|
},
|
|
{
|
|
"0.10.0",
|
|
"workspace",
|
|
},
|
|
{
|
|
"0.11.0",
|
|
"workspace",
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.tfVersion, func(t *testing.T) {
|
|
terraform := mocks.NewMockClient()
|
|
|
|
tfVersion, _ := version.NewVersion(c.tfVersion)
|
|
logger := logging.NewNoopLogger(t)
|
|
|
|
s := runtime.PlanStepRunner{
|
|
TerraformExecutor: terraform,
|
|
DefaultTFVersion: tfVersion,
|
|
}
|
|
|
|
When(terraform.RunCommandWithVersion(logging_matchers.AnyLoggingSimpleLogging(), AnyString(), AnyStringSlice(), matchers2.AnyMapOfStringToString(), matchers2.AnyPtrToGoVersionVersion(), AnyString())).
|
|
ThenReturn("output", nil)
|
|
output, err := s.Run(models.ProjectCommandContext{
|
|
Log: logger,
|
|
Workspace: "workspace",
|
|
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",
|
|
},
|
|
}, []string{"extra", "args"}, "/path", map[string]string(nil))
|
|
Ok(t, err)
|
|
|
|
Equals(t, "output", output)
|
|
// Verify that env select was called as well as plan.
|
|
terraform.VerifyWasCalledOnce().RunCommandWithVersion(logger,
|
|
"/path",
|
|
[]string{c.expWorkspaceCmd,
|
|
"select",
|
|
"-no-color",
|
|
"workspace"},
|
|
map[string]string(nil),
|
|
tfVersion,
|
|
"workspace")
|
|
terraform.VerifyWasCalledOnce().RunCommandWithVersion(logger,
|
|
"/path",
|
|
[]string{"plan",
|
|
"-input=false",
|
|
"-refresh",
|
|
"-no-color",
|
|
"-out",
|
|
"\"/path/workspace.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"},
|
|
map[string]string(nil),
|
|
tfVersion,
|
|
"workspace")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRun_CreatesWorkspace(t *testing.T) {
|
|
// Test that if `workspace select` fails, we call `workspace new`.
|
|
RegisterMockTestingT(t)
|
|
|
|
cases := []struct {
|
|
tfVersion string
|
|
expWorkspaceCommand string
|
|
}{
|
|
{
|
|
"0.9.0",
|
|
"env",
|
|
},
|
|
{
|
|
"0.9.11",
|
|
"env",
|
|
},
|
|
{
|
|
"0.10.0",
|
|
"workspace",
|
|
},
|
|
{
|
|
"0.11.0",
|
|
"workspace",
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.tfVersion, func(t *testing.T) {
|
|
terraform := mocks.NewMockClient()
|
|
tfVersion, _ := version.NewVersion(c.tfVersion)
|
|
logger := logging.NewNoopLogger(t)
|
|
s := runtime.PlanStepRunner{
|
|
TerraformExecutor: terraform,
|
|
DefaultTFVersion: tfVersion,
|
|
}
|
|
|
|
// Ensure that we actually try to switch workspaces by making the
|
|
// output of `workspace show` to be a different name.
|
|
When(terraform.RunCommandWithVersion(logger, "/path", []string{"workspace", "show"}, map[string]string(nil), tfVersion, "workspace")).ThenReturn("diffworkspace\n", nil)
|
|
|
|
expWorkspaceArgs := []string{c.expWorkspaceCommand, "select", "-no-color", "workspace"}
|
|
When(terraform.RunCommandWithVersion(logger, "/path", expWorkspaceArgs, map[string]string(nil), tfVersion, "workspace")).ThenReturn("", errors.New("workspace does not exist"))
|
|
|
|
expPlanArgs := []string{"plan",
|
|
"-input=false",
|
|
"-refresh",
|
|
"-no-color",
|
|
"-out",
|
|
"\"/path/workspace.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"}
|
|
When(terraform.RunCommandWithVersion(logger, "/path", expPlanArgs, map[string]string(nil), tfVersion, "workspace")).ThenReturn("output", nil)
|
|
|
|
output, err := s.Run(models.ProjectCommandContext{
|
|
Log: logger,
|
|
Workspace: "workspace",
|
|
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",
|
|
},
|
|
}, []string{"extra", "args"}, "/path", map[string]string(nil))
|
|
Ok(t, err)
|
|
|
|
Equals(t, "output", output)
|
|
// Verify that env select was called as well as plan.
|
|
terraform.VerifyWasCalledOnce().RunCommandWithVersion(logger, "/path", expWorkspaceArgs, map[string]string(nil), tfVersion, "workspace")
|
|
terraform.VerifyWasCalledOnce().RunCommandWithVersion(logger, "/path", expPlanArgs, map[string]string(nil), tfVersion, "workspace")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRun_NoWorkspaceSwitchIfNotNecessary(t *testing.T) {
|
|
// Tests that if workspace show says we're on the right workspace we don't
|
|
// switch.
|
|
RegisterMockTestingT(t)
|
|
terraform := mocks.NewMockClient()
|
|
tfVersion, _ := version.NewVersion("0.10.0")
|
|
logger := logging.NewNoopLogger(t)
|
|
s := runtime.PlanStepRunner{
|
|
TerraformExecutor: terraform,
|
|
DefaultTFVersion: tfVersion,
|
|
}
|
|
When(terraform.RunCommandWithVersion(logger, "/path", []string{"workspace", "show"}, map[string]string(nil), tfVersion, "workspace")).ThenReturn("workspace\n", nil)
|
|
|
|
expPlanArgs := []string{"plan",
|
|
"-input=false",
|
|
"-refresh",
|
|
"-no-color",
|
|
"-out",
|
|
"\"/path/workspace.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"}
|
|
When(terraform.RunCommandWithVersion(logger, "/path", expPlanArgs, map[string]string(nil), tfVersion, "workspace")).ThenReturn("output", nil)
|
|
|
|
output, err := s.Run(models.ProjectCommandContext{
|
|
Log: logger,
|
|
Workspace: "workspace",
|
|
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",
|
|
},
|
|
}, []string{"extra", "args"}, "/path", map[string]string(nil))
|
|
Ok(t, err)
|
|
|
|
Equals(t, "output", output)
|
|
terraform.VerifyWasCalledOnce().RunCommandWithVersion(logger, "/path", expPlanArgs, map[string]string(nil), tfVersion, "workspace")
|
|
|
|
// Verify that workspace select was never called.
|
|
terraform.VerifyWasCalled(Never()).RunCommandWithVersion(logger, "/path", []string{"workspace", "select", "-no-color", "workspace"}, map[string]string(nil), tfVersion, "workspace")
|
|
}
|
|
|
|
func TestRun_AddsEnvVarFile(t *testing.T) {
|
|
// Test that if env/workspace.tfvars file exists we use -var-file option.
|
|
RegisterMockTestingT(t)
|
|
terraform := mocks.NewMockClient()
|
|
|
|
// Create the env/workspace.tfvars file.
|
|
tmpDir, cleanup := TempDir(t)
|
|
defer cleanup()
|
|
err := os.MkdirAll(filepath.Join(tmpDir, "env"), 0700)
|
|
Ok(t, err)
|
|
envVarsFile := filepath.Join(tmpDir, "env/workspace.tfvars")
|
|
err = ioutil.WriteFile(envVarsFile, nil, 0644)
|
|
Ok(t, err)
|
|
|
|
// Using version >= 0.10 here so we don't expect any env commands.
|
|
tfVersion, _ := version.NewVersion("0.10.0")
|
|
logger := logging.NewNoopLogger(t)
|
|
s := runtime.PlanStepRunner{
|
|
TerraformExecutor: terraform,
|
|
DefaultTFVersion: tfVersion,
|
|
}
|
|
|
|
expPlanArgs := []string{"plan",
|
|
"-input=false",
|
|
"-refresh",
|
|
"-no-color",
|
|
"-out",
|
|
fmt.Sprintf("%q", filepath.Join(tmpDir, "workspace.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",
|
|
"-var-file",
|
|
envVarsFile,
|
|
}
|
|
When(terraform.RunCommandWithVersion(logger, tmpDir, expPlanArgs, map[string]string(nil), tfVersion, "workspace")).ThenReturn("output", nil)
|
|
|
|
output, err := s.Run(models.ProjectCommandContext{
|
|
Log: logger,
|
|
Workspace: "workspace",
|
|
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",
|
|
},
|
|
}, []string{"extra", "args"}, tmpDir, map[string]string(nil))
|
|
Ok(t, err)
|
|
|
|
// Verify that env select was never called since we're in version >= 0.10
|
|
terraform.VerifyWasCalled(Never()).RunCommandWithVersion(logger, tmpDir, []string{"env", "select", "-no-color", "workspace"}, map[string]string(nil), tfVersion, "workspace")
|
|
terraform.VerifyWasCalledOnce().RunCommandWithVersion(logger, tmpDir, expPlanArgs, map[string]string(nil), tfVersion, "workspace")
|
|
Equals(t, "output", output)
|
|
}
|
|
|
|
func TestRun_UsesDiffPathForProject(t *testing.T) {
|
|
// Test that if running for a project, uses a different path for the plan
|
|
// file.
|
|
RegisterMockTestingT(t)
|
|
terraform := mocks.NewMockClient()
|
|
tfVersion, _ := version.NewVersion("0.10.0")
|
|
logger := logging.NewNoopLogger(t)
|
|
s := runtime.PlanStepRunner{
|
|
TerraformExecutor: terraform,
|
|
DefaultTFVersion: tfVersion,
|
|
}
|
|
When(terraform.RunCommandWithVersion(logger, "/path", []string{"workspace", "show"}, map[string]string(nil), tfVersion, "workspace")).ThenReturn("workspace\n", nil)
|
|
|
|
expPlanArgs := []string{"plan",
|
|
"-input=false",
|
|
"-refresh",
|
|
"-no-color",
|
|
"-out",
|
|
"\"/path/projectname-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",
|
|
}
|
|
When(terraform.RunCommandWithVersion(logger, "/path", expPlanArgs, map[string]string(nil), tfVersion, "default")).ThenReturn("output", nil)
|
|
|
|
output, err := s.Run(models.ProjectCommandContext{
|
|
Log: logger,
|
|
Workspace: "default",
|
|
RepoRelDir: ".",
|
|
User: models.User{Username: "username"},
|
|
EscapedCommentArgs: []string{"comment", "args"},
|
|
ProjectName: "projectname",
|
|
Pull: models.PullRequest{
|
|
Num: 2,
|
|
},
|
|
BaseRepo: models.Repo{
|
|
FullName: "owner/repo",
|
|
Owner: "owner",
|
|
Name: "repo",
|
|
},
|
|
}, []string{"extra", "args"}, "/path", map[string]string(nil))
|
|
Ok(t, err)
|
|
Equals(t, "output", output)
|
|
}
|
|
|
|
// Test that we format the plan output for better rendering.
|
|
func TestRun_PlanFmt(t *testing.T) {
|
|
rawOutput := `Refreshing Terraform state in-memory prior to plan...
|
|
The refreshed state will be used to calculate this plan, but will not be
|
|
persisted to local or remote state storage.
|
|
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
An execution plan has been generated and is shown below.
|
|
Resource actions are indicated with the following symbols:
|
|
+ create
|
|
~ update in-place
|
|
- destroy
|
|
|
|
Terraform will perform the following actions:
|
|
|
|
+ null_resource.test[0]
|
|
id: <computed>
|
|
|
|
+ null_resource.test[1]
|
|
id: <computed>
|
|
|
|
~ aws_security_group_rule.allow_all
|
|
description: "" => "test3"
|
|
|
|
- aws_security_group_rule.allow_all
|
|
`
|
|
RegisterMockTestingT(t)
|
|
terraform := mocks.NewMockClient()
|
|
tfVersion, _ := version.NewVersion("0.10.0")
|
|
s := runtime.PlanStepRunner{
|
|
TerraformExecutor: terraform,
|
|
DefaultTFVersion: tfVersion,
|
|
}
|
|
When(terraform.RunCommandWithVersion(
|
|
matchers.AnyPtrToLoggingSimpleLogger(),
|
|
AnyString(),
|
|
AnyStringSlice(),
|
|
matchers2.AnyMapOfStringToString(),
|
|
matchers2.AnyPtrToGoVersionVersion(),
|
|
AnyString())).
|
|
Then(func(params []Param) ReturnValues {
|
|
// This code allows us to return different values depending on the
|
|
// tf command being run while still using the wildcard matchers above.
|
|
tfArgs := params[2].([]string)
|
|
if stringSliceEquals(tfArgs, []string{"workspace", "show"}) {
|
|
return []ReturnValue{"default", nil}
|
|
} else if tfArgs[0] == "plan" {
|
|
return []ReturnValue{rawOutput, nil}
|
|
} else {
|
|
return []ReturnValue{"", errors.New("unexpected call to RunCommandWithVersion")}
|
|
}
|
|
})
|
|
actOutput, err := s.Run(models.ProjectCommandContext{Workspace: "default"}, nil, "", map[string]string(nil))
|
|
Ok(t, err)
|
|
Equals(t, `
|
|
An execution plan has been generated and is shown below.
|
|
Resource actions are indicated with the following symbols:
|
|
+ create
|
|
~ update in-place
|
|
- destroy
|
|
|
|
Terraform will perform the following actions:
|
|
|
|
+ null_resource.test[0]
|
|
id: <computed>
|
|
|
|
+ null_resource.test[1]
|
|
id: <computed>
|
|
|
|
~ aws_security_group_rule.allow_all
|
|
description: "" => "test3"
|
|
|
|
- aws_security_group_rule.allow_all
|
|
`, actOutput)
|
|
}
|
|
|
|
// Test that even if there's an error, we get the returned output.
|
|
func TestRun_OutputOnErr(t *testing.T) {
|
|
RegisterMockTestingT(t)
|
|
terraform := mocks.NewMockClient()
|
|
tfVersion, _ := version.NewVersion("0.10.0")
|
|
s := runtime.PlanStepRunner{
|
|
TerraformExecutor: terraform,
|
|
DefaultTFVersion: tfVersion,
|
|
}
|
|
expOutput := "expected output"
|
|
expErrMsg := "error!"
|
|
When(terraform.RunCommandWithVersion(
|
|
matchers.AnyPtrToLoggingSimpleLogger(),
|
|
AnyString(),
|
|
AnyStringSlice(),
|
|
matchers2.AnyMapOfStringToString(),
|
|
matchers2.AnyPtrToGoVersionVersion(),
|
|
AnyString())).
|
|
Then(func(params []Param) ReturnValues {
|
|
// This code allows us to return different values depending on the
|
|
// tf command being run while still using the wildcard matchers above.
|
|
tfArgs := params[2].([]string)
|
|
if stringSliceEquals(tfArgs, []string{"workspace", "show"}) {
|
|
return []ReturnValue{"default\n", nil}
|
|
} else if tfArgs[0] == "plan" {
|
|
return []ReturnValue{expOutput, errors.New(expErrMsg)}
|
|
} else {
|
|
return []ReturnValue{"", errors.New("unexpected call to RunCommandWithVersion")}
|
|
}
|
|
})
|
|
actOutput, actErr := s.Run(models.ProjectCommandContext{Workspace: "default"}, nil, "", map[string]string(nil))
|
|
ErrEquals(t, expErrMsg, actErr)
|
|
Equals(t, expOutput, actOutput)
|
|
}
|
|
|
|
// Test that if we're using 0.12, we don't set the optional -var atlantis_repo_name
|
|
// flags because in >= 0.12 you can't set -var flags if those variables aren't
|
|
// being used.
|
|
func TestRun_NoOptionalVarsIn012(t *testing.T) {
|
|
RegisterMockTestingT(t)
|
|
|
|
expPlanArgs := []string{
|
|
"plan",
|
|
"-input=false",
|
|
"-refresh",
|
|
"-no-color",
|
|
"-out",
|
|
fmt.Sprintf("%q", "/path/default.tfplan"),
|
|
"extra",
|
|
"args",
|
|
"comment",
|
|
"args",
|
|
}
|
|
|
|
cases := []struct {
|
|
name string
|
|
tfVersion string
|
|
}{
|
|
{
|
|
"stable version",
|
|
"0.12.0",
|
|
},
|
|
{
|
|
"with prerelease",
|
|
"0.14.0-rc1",
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
terraform := mocks.NewMockClient()
|
|
When(terraform.RunCommandWithVersion(
|
|
matchers.AnyPtrToLoggingSimpleLogger(),
|
|
AnyString(),
|
|
AnyStringSlice(),
|
|
matchers2.AnyMapOfStringToString(),
|
|
matchers2.AnyPtrToGoVersionVersion(),
|
|
AnyString())).ThenReturn("output", nil)
|
|
|
|
tfVersion, _ := version.NewVersion(c.tfVersion)
|
|
s := runtime.PlanStepRunner{
|
|
TerraformExecutor: terraform,
|
|
DefaultTFVersion: tfVersion,
|
|
}
|
|
|
|
output, err := s.Run(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",
|
|
},
|
|
}, []string{"extra", "args"}, "/path", map[string]string(nil))
|
|
Ok(t, err)
|
|
Equals(t, "output", output)
|
|
|
|
terraform.VerifyWasCalledOnce().RunCommandWithVersion(nil, "/path", expPlanArgs, map[string]string(nil), tfVersion, "default")
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
// Test plans if using remote ops.
|
|
func TestRun_RemoteOps(t *testing.T) {
|
|
cases := map[string]string{
|
|
"0.11.14 error": `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
|
|
|
|
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) {
|
|
|
|
logger := logging.NewNoopLogger(t)
|
|
|
|
RegisterMockTestingT(t)
|
|
terraform := mocks.NewMockClient()
|
|
asyncTf := &remotePlanMock{}
|
|
|
|
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(
|
|
logger,
|
|
absProjectPath,
|
|
[]string{"workspace", "show"},
|
|
map[string]string(nil),
|
|
tfVersion,
|
|
"default")).ThenReturn("default\n", nil)
|
|
|
|
// 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",
|
|
}
|
|
|
|
planErr := errors.New("exit status 1: err")
|
|
planOutput := "\n" + remoteOpsErr
|
|
asyncTf.LinesToSend = remotePlanOutput
|
|
When(terraform.RunCommandWithVersion(logger, absProjectPath, expPlanArgs, map[string]string(nil), tfVersion, "default")).
|
|
ThenReturn(planOutput, planErr)
|
|
|
|
// Now that mocking is set up, we're ready to run the plan.
|
|
ctx := models.ProjectCommandContext{
|
|
Log: logger,
|
|
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, map[string]string(nil))
|
|
Ok(t, err)
|
|
Equals(t, `
|
|
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.`, output)
|
|
|
|
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
|
|
|
|
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))
|
|
|
|
// 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)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Test striping output method
|
|
func TestStripRefreshingFromPlanOutput(t *testing.T) {
|
|
tfVersion0135, _ := version.NewVersion("0.13.5")
|
|
tfVersion0140, _ := version.NewVersion("0.14.0")
|
|
cases := []struct {
|
|
out string
|
|
tfVersion *version.Version
|
|
}{
|
|
{
|
|
remotePlanOutput,
|
|
tfVersion0135,
|
|
},
|
|
{
|
|
`Running plan in the remote backend. Output will stream here. Pressing Ctrl-C
|
|
will stop streaming the logs, but will not stop the plan running remotely.
|
|
|
|
Preparing the remote plan...
|
|
|
|
To view this run in a browser, visit:
|
|
https://app.terraform.io/app/lkysow-enterprises/atlantis-tfe-test/runs/run-is4oVvJfrkud1KvE
|
|
|
|
Waiting for the plan to start...
|
|
|
|
Terraform v0.14.0
|
|
|
|
Configuring remote state backend...
|
|
Initializing Terraform configuration...
|
|
2019/02/20 22:40:52 [DEBUG] Using modified User-Agent: Terraform/0.14.0TFE/202eeff
|
|
Refreshing Terraform state in-memory prior to plan...
|
|
The refreshed state will be used to calculate this plan, but will not be
|
|
persisted to local or remote state storage.
|
|
|
|
null_resource.hi: Refreshing state... (ID: 217661332516885645)
|
|
null_resource.hi[1]: Refreshing state... (ID: 6064510335076839362)
|
|
|
|
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.`,
|
|
tfVersion0140,
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
output := runtime.StripRefreshingFromPlanOutput(c.out, c.tfVersion)
|
|
Equals(t, `
|
|
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.`, output)
|
|
}
|
|
}
|
|
|
|
type remotePlanMock struct {
|
|
// LinesToSend will be sent on the channel.
|
|
LinesToSend string
|
|
// CalledArgs is what args we were called with.
|
|
CalledArgs []string
|
|
}
|
|
|
|
func (r *remotePlanMock) RunCommandAsync(log logging.SimpleLogging, path string, args []string, envs map[string]string, v *version.Version, workspace string) (chan<- string, <-chan terraform.Line) {
|
|
r.CalledArgs = args
|
|
in := make(chan string)
|
|
out := make(chan terraform.Line)
|
|
go func() {
|
|
for _, line := range strings.Split(r.LinesToSend, "\n") {
|
|
out <- terraform.Line{Line: line}
|
|
}
|
|
close(out)
|
|
close(in)
|
|
}()
|
|
return in, out
|
|
}
|
|
|
|
func stringSliceEquals(a, b []string) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i, v := range a {
|
|
if v != b[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
var remotePlanOutput = `Running plan in the remote backend. Output will stream here. Pressing Ctrl-C
|
|
will stop streaming the logs, but will not stop the plan running remotely.
|
|
|
|
Preparing the remote plan...
|
|
|
|
To view this run in a browser, visit:
|
|
https://app.terraform.io/app/lkysow-enterprises/atlantis-tfe-test/runs/run-is4oVvJfrkud1KvE
|
|
|
|
Waiting for the plan to start...
|
|
|
|
Terraform v0.11.11
|
|
|
|
Configuring remote state backend...
|
|
Initializing Terraform configuration...
|
|
2019/02/20 22:40:52 [DEBUG] Using modified User-Agent: Terraform/0.11.11 TFE/202eeff
|
|
Refreshing Terraform state in-memory prior to plan...
|
|
The refreshed state will be used to calculate this plan, but will not be
|
|
persisted to local or remote state storage.
|
|
|
|
null_resource.hi: Refreshing state... (ID: 217661332516885645)
|
|
null_resource.hi[1]: Refreshing state... (ID: 6064510335076839362)
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
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.`
|