terraform/backend/local/backend_plan_test.go

382 lines
9.6 KiB
Go
Raw Normal View History

package local
import (
"context"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/configs/configload"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/plans/planfile"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform"
cli: allow disabling "next steps" message in terraform plan In #15884 we adjusted the plan output to give an explicit command to run to apply a plan, whereas before this command was just alluded to in the prose. Since releasing that, we've got good feedback that it's confusing to include such instructions when Terraform is running in a workflow automation tool, because such tools usually abstract away exactly what commands are run and require users to take different actions to proceed through the workflow. To accommodate such environments while retaining helpful messages for normal CLI usage, here we introduce a new environment variable TF_IN_AUTOMATION which, when set to a non-empty value, is a hint to Terraform that it isn't being run in an interactive command shell and it should thus tone down the "next steps" messaging. The documentation for this setting is included as part of the "...in automation" guide since it's not generally useful in other cases. We also intentionally disclaim comprehensive support for this since we want to avoid creating an extreme number of "if running in automation..." codepaths that would increase the testing matrix and hurt maintainability. The focus is specifically on the output of the three commands we give in the automation guide, which at present means the following two situations: * "terraform init" does not include the final paragraphs that suggest running "terraform plan" and tell you in what situations you might need to re-run "terraform init". * "terraform plan" does not include the final paragraphs that either warn about not specifying "-out=..." or instruct to run "terraform apply" with the generated plan file.
2017-09-09 02:14:37 +02:00
"github.com/mitchellh/cli"
"github.com/zclconf/go-cty/cty"
)
func TestLocal_planBasic(t *testing.T) {
2018-03-28 16:54:08 +02:00
b, cleanup := TestLocal(t)
defer cleanup()
p := TestLocalProvider(t, b, "test", planFixtureSchema())
op, configCleanup := testOperationPlan(t, "./test-fixtures/plan")
defer configCleanup()
op.PlanRefresh = true
run, err := b.Operation(context.Background(), op)
if err != nil {
t.Fatalf("bad: %s", err)
}
<-run.Done()
if run.Result != backend.OperationSuccess {
t.Fatalf("plan operation failed")
}
if !p.PlanResourceChangeCalled {
t.Fatal("PlanResourceChange should be called")
}
}
cli: allow disabling "next steps" message in terraform plan In #15884 we adjusted the plan output to give an explicit command to run to apply a plan, whereas before this command was just alluded to in the prose. Since releasing that, we've got good feedback that it's confusing to include such instructions when Terraform is running in a workflow automation tool, because such tools usually abstract away exactly what commands are run and require users to take different actions to proceed through the workflow. To accommodate such environments while retaining helpful messages for normal CLI usage, here we introduce a new environment variable TF_IN_AUTOMATION which, when set to a non-empty value, is a hint to Terraform that it isn't being run in an interactive command shell and it should thus tone down the "next steps" messaging. The documentation for this setting is included as part of the "...in automation" guide since it's not generally useful in other cases. We also intentionally disclaim comprehensive support for this since we want to avoid creating an extreme number of "if running in automation..." codepaths that would increase the testing matrix and hurt maintainability. The focus is specifically on the output of the three commands we give in the automation guide, which at present means the following two situations: * "terraform init" does not include the final paragraphs that suggest running "terraform plan" and tell you in what situations you might need to re-run "terraform init". * "terraform plan" does not include the final paragraphs that either warn about not specifying "-out=..." or instruct to run "terraform apply" with the generated plan file.
2017-09-09 02:14:37 +02:00
func TestLocal_planInAutomation(t *testing.T) {
2018-03-28 16:54:08 +02:00
b, cleanup := TestLocal(t)
defer cleanup()
TestLocalProvider(t, b, "test", planFixtureSchema())
cli: allow disabling "next steps" message in terraform plan In #15884 we adjusted the plan output to give an explicit command to run to apply a plan, whereas before this command was just alluded to in the prose. Since releasing that, we've got good feedback that it's confusing to include such instructions when Terraform is running in a workflow automation tool, because such tools usually abstract away exactly what commands are run and require users to take different actions to proceed through the workflow. To accommodate such environments while retaining helpful messages for normal CLI usage, here we introduce a new environment variable TF_IN_AUTOMATION which, when set to a non-empty value, is a hint to Terraform that it isn't being run in an interactive command shell and it should thus tone down the "next steps" messaging. The documentation for this setting is included as part of the "...in automation" guide since it's not generally useful in other cases. We also intentionally disclaim comprehensive support for this since we want to avoid creating an extreme number of "if running in automation..." codepaths that would increase the testing matrix and hurt maintainability. The focus is specifically on the output of the three commands we give in the automation guide, which at present means the following two situations: * "terraform init" does not include the final paragraphs that suggest running "terraform plan" and tell you in what situations you might need to re-run "terraform init". * "terraform plan" does not include the final paragraphs that either warn about not specifying "-out=..." or instruct to run "terraform apply" with the generated plan file.
2017-09-09 02:14:37 +02:00
const msg = `You didn't specify an "-out" parameter`
// When we're "in automation" we omit certain text from the
// plan output. However, testing for the absense of text is
// unreliable in the face of future copy changes, so we'll
// mitigate that by running both with and without the flag
// set so we can ensure that the expected messages _are_
// included the first time.
b.RunningInAutomation = false
b.CLI = cli.NewMockUi()
{
op, configCleanup := testOperationPlan(t, "./test-fixtures/plan")
defer configCleanup()
cli: allow disabling "next steps" message in terraform plan In #15884 we adjusted the plan output to give an explicit command to run to apply a plan, whereas before this command was just alluded to in the prose. Since releasing that, we've got good feedback that it's confusing to include such instructions when Terraform is running in a workflow automation tool, because such tools usually abstract away exactly what commands are run and require users to take different actions to proceed through the workflow. To accommodate such environments while retaining helpful messages for normal CLI usage, here we introduce a new environment variable TF_IN_AUTOMATION which, when set to a non-empty value, is a hint to Terraform that it isn't being run in an interactive command shell and it should thus tone down the "next steps" messaging. The documentation for this setting is included as part of the "...in automation" guide since it's not generally useful in other cases. We also intentionally disclaim comprehensive support for this since we want to avoid creating an extreme number of "if running in automation..." codepaths that would increase the testing matrix and hurt maintainability. The focus is specifically on the output of the three commands we give in the automation guide, which at present means the following two situations: * "terraform init" does not include the final paragraphs that suggest running "terraform plan" and tell you in what situations you might need to re-run "terraform init". * "terraform plan" does not include the final paragraphs that either warn about not specifying "-out=..." or instruct to run "terraform apply" with the generated plan file.
2017-09-09 02:14:37 +02:00
op.PlanRefresh = true
run, err := b.Operation(context.Background(), op)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
<-run.Done()
if run.Result != backend.OperationSuccess {
t.Fatalf("plan operation failed")
cli: allow disabling "next steps" message in terraform plan In #15884 we adjusted the plan output to give an explicit command to run to apply a plan, whereas before this command was just alluded to in the prose. Since releasing that, we've got good feedback that it's confusing to include such instructions when Terraform is running in a workflow automation tool, because such tools usually abstract away exactly what commands are run and require users to take different actions to proceed through the workflow. To accommodate such environments while retaining helpful messages for normal CLI usage, here we introduce a new environment variable TF_IN_AUTOMATION which, when set to a non-empty value, is a hint to Terraform that it isn't being run in an interactive command shell and it should thus tone down the "next steps" messaging. The documentation for this setting is included as part of the "...in automation" guide since it's not generally useful in other cases. We also intentionally disclaim comprehensive support for this since we want to avoid creating an extreme number of "if running in automation..." codepaths that would increase the testing matrix and hurt maintainability. The focus is specifically on the output of the three commands we give in the automation guide, which at present means the following two situations: * "terraform init" does not include the final paragraphs that suggest running "terraform plan" and tell you in what situations you might need to re-run "terraform init". * "terraform plan" does not include the final paragraphs that either warn about not specifying "-out=..." or instruct to run "terraform apply" with the generated plan file.
2017-09-09 02:14:37 +02:00
}
output := b.CLI.(*cli.MockUi).OutputWriter.String()
if !strings.Contains(output, msg) {
t.Fatalf("missing next-steps message when not in automation")
}
}
// On the second run, we expect the next-steps messaging to be absent
// since we're now "running in automation".
b.RunningInAutomation = true
b.CLI = cli.NewMockUi()
{
op, configCleanup := testOperationPlan(t, "./test-fixtures/plan")
defer configCleanup()
cli: allow disabling "next steps" message in terraform plan In #15884 we adjusted the plan output to give an explicit command to run to apply a plan, whereas before this command was just alluded to in the prose. Since releasing that, we've got good feedback that it's confusing to include such instructions when Terraform is running in a workflow automation tool, because such tools usually abstract away exactly what commands are run and require users to take different actions to proceed through the workflow. To accommodate such environments while retaining helpful messages for normal CLI usage, here we introduce a new environment variable TF_IN_AUTOMATION which, when set to a non-empty value, is a hint to Terraform that it isn't being run in an interactive command shell and it should thus tone down the "next steps" messaging. The documentation for this setting is included as part of the "...in automation" guide since it's not generally useful in other cases. We also intentionally disclaim comprehensive support for this since we want to avoid creating an extreme number of "if running in automation..." codepaths that would increase the testing matrix and hurt maintainability. The focus is specifically on the output of the three commands we give in the automation guide, which at present means the following two situations: * "terraform init" does not include the final paragraphs that suggest running "terraform plan" and tell you in what situations you might need to re-run "terraform init". * "terraform plan" does not include the final paragraphs that either warn about not specifying "-out=..." or instruct to run "terraform apply" with the generated plan file.
2017-09-09 02:14:37 +02:00
op.PlanRefresh = true
run, err := b.Operation(context.Background(), op)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
<-run.Done()
if run.Result != backend.OperationSuccess {
t.Fatalf("plan operation failed")
cli: allow disabling "next steps" message in terraform plan In #15884 we adjusted the plan output to give an explicit command to run to apply a plan, whereas before this command was just alluded to in the prose. Since releasing that, we've got good feedback that it's confusing to include such instructions when Terraform is running in a workflow automation tool, because such tools usually abstract away exactly what commands are run and require users to take different actions to proceed through the workflow. To accommodate such environments while retaining helpful messages for normal CLI usage, here we introduce a new environment variable TF_IN_AUTOMATION which, when set to a non-empty value, is a hint to Terraform that it isn't being run in an interactive command shell and it should thus tone down the "next steps" messaging. The documentation for this setting is included as part of the "...in automation" guide since it's not generally useful in other cases. We also intentionally disclaim comprehensive support for this since we want to avoid creating an extreme number of "if running in automation..." codepaths that would increase the testing matrix and hurt maintainability. The focus is specifically on the output of the three commands we give in the automation guide, which at present means the following two situations: * "terraform init" does not include the final paragraphs that suggest running "terraform plan" and tell you in what situations you might need to re-run "terraform init". * "terraform plan" does not include the final paragraphs that either warn about not specifying "-out=..." or instruct to run "terraform apply" with the generated plan file.
2017-09-09 02:14:37 +02:00
}
output := b.CLI.(*cli.MockUi).OutputWriter.String()
if strings.Contains(output, msg) {
t.Fatalf("next-steps message present when in automation")
}
}
}
func TestLocal_planNoConfig(t *testing.T) {
2018-03-28 16:54:08 +02:00
b, cleanup := TestLocal(t)
defer cleanup()
TestLocalProvider(t, b, "test", &terraform.ProviderSchema{})
b.CLI = cli.NewMockUi()
op, configCleanup := testOperationPlan(t, "./test-fixtures/empty")
defer configCleanup()
op.PlanRefresh = true
run, err := b.Operation(context.Background(), op)
if err != nil {
t.Fatalf("bad: %s", err)
}
<-run.Done()
if run.Result == backend.OperationSuccess {
t.Fatal("plan operation succeeded; want failure")
}
output := b.CLI.(*cli.MockUi).ErrorWriter.String()
if !strings.Contains(output, "configuration") {
t.Fatalf("bad: %s", err)
}
}
func TestLocal_planRefreshFalse(t *testing.T) {
2018-03-28 16:54:08 +02:00
b, cleanup := TestLocal(t)
defer cleanup()
p := TestLocalProvider(t, b, "test", planFixtureSchema())
testStateFile(t, b.StatePath, testPlanState())
op, configCleanup := testOperationPlan(t, "./test-fixtures/plan")
defer configCleanup()
run, err := b.Operation(context.Background(), op)
if err != nil {
t.Fatalf("bad: %s", err)
}
<-run.Done()
if run.Result != backend.OperationSuccess {
t.Fatalf("plan operation failed")
}
if p.ReadResourceCalled {
t.Fatal("ReadResource should not be called")
}
if !run.PlanEmpty {
t.Fatal("plan should be empty")
}
}
func TestLocal_planDestroy(t *testing.T) {
2018-03-28 16:54:08 +02:00
b, cleanup := TestLocal(t)
defer cleanup()
p := TestLocalProvider(t, b, "test", planFixtureSchema())
testStateFile(t, b.StatePath, testPlanState())
outDir := testTempDir(t)
defer os.RemoveAll(outDir)
planPath := filepath.Join(outDir, "plan.tfplan")
op, configCleanup := testOperationPlan(t, "./test-fixtures/plan")
defer configCleanup()
op.Destroy = true
op.PlanRefresh = true
op.PlanOutPath = planPath
cfg := cty.ObjectVal(map[string]cty.Value{
"path": cty.StringVal(b.StatePath),
})
cfgRaw, err := plans.NewDynamicValue(cfg, cfg.Type())
if err != nil {
t.Fatal(err)
}
op.PlanOutBackend = &plans.Backend{
// Just a placeholder so that we can generate a valid plan file.
Type: "local",
Config: cfgRaw,
}
run, err := b.Operation(context.Background(), op)
if err != nil {
t.Fatalf("bad: %s", err)
}
<-run.Done()
if run.Result != backend.OperationSuccess {
t.Fatalf("plan operation failed")
}
if !p.ReadResourceCalled {
t.Fatal("ReadResource should be called")
}
if run.PlanEmpty {
t.Fatal("plan should not be empty")
}
plan := testReadPlan(t, planPath)
for _, r := range plan.Changes.Resources {
if r.Action.String() != "Delete" {
t.Fatalf("bad: %#v", r.Action.String())
}
}
}
func TestLocal_planOutPathNoChange(t *testing.T) {
2018-03-28 16:54:08 +02:00
b, cleanup := TestLocal(t)
defer cleanup()
TestLocalProvider(t, b, "test", planFixtureSchema())
testStateFile(t, b.StatePath, testPlanState())
outDir := testTempDir(t)
defer os.RemoveAll(outDir)
planPath := filepath.Join(outDir, "plan.tfplan")
op, configCleanup := testOperationPlan(t, "./test-fixtures/plan")
defer configCleanup()
op.PlanOutPath = planPath
cfg := cty.ObjectVal(map[string]cty.Value{
"path": cty.StringVal(b.StatePath),
})
cfgRaw, err := plans.NewDynamicValue(cfg, cfg.Type())
if err != nil {
t.Fatal(err)
}
op.PlanOutBackend = &plans.Backend{
// Just a placeholder so that we can generate a valid plan file.
Type: "local",
Config: cfgRaw,
}
run, err := b.Operation(context.Background(), op)
if err != nil {
t.Fatalf("bad: %s", err)
}
<-run.Done()
if run.Result != backend.OperationSuccess {
t.Fatalf("plan operation failed")
}
plan := testReadPlan(t, planPath)
if !plan.Changes.Empty() {
t.Fatalf("expected empty plan to be written")
}
}
// TestLocal_planScaleOutNoDupeCount tests a Refresh/Plan sequence when a
// resource count is scaled out. The scaled out node needs to exist in the
// graph and run through a plan-style sequence during the refresh phase, but
// can conflate the count if its post-diff count hooks are not skipped. This
// checks to make sure the correct resource count is ultimately given to the
// UI.
func TestLocal_planScaleOutNoDupeCount(t *testing.T) {
2018-03-28 16:54:08 +02:00
b, cleanup := TestLocal(t)
defer cleanup()
TestLocalProvider(t, b, "test", planFixtureSchema())
testStateFile(t, b.StatePath, testPlanState())
actual := new(CountHook)
b.ContextOpts.Hooks = append(b.ContextOpts.Hooks, actual)
outDir := testTempDir(t)
defer os.RemoveAll(outDir)
op, configCleanup := testOperationPlan(t, "./test-fixtures/plan-scaleout")
defer configCleanup()
op.PlanRefresh = true
run, err := b.Operation(context.Background(), op)
if err != nil {
t.Fatalf("bad: %s", err)
}
<-run.Done()
if run.Result != backend.OperationSuccess {
t.Fatalf("plan operation failed")
}
expected := new(CountHook)
expected.ToAdd = 1
expected.ToChange = 0
expected.ToRemoveAndAdd = 0
expected.ToRemove = 0
if !reflect.DeepEqual(expected, actual) {
t.Fatalf("Expected %#v, got %#v instead.",
expected, actual)
}
}
func testOperationPlan(t *testing.T, configDir string) (*backend.Operation, func()) {
t.Helper()
_, configLoader, configCleanup := configload.MustLoadConfigForTests(t, configDir)
return &backend.Operation{
Type: backend.OperationTypePlan,
ConfigDir: configDir,
ConfigLoader: configLoader,
}, configCleanup
}
// testPlanState is just a common state that we use for testing plan.
func testPlanState() *states.State {
state := states.NewState()
rootModule := state.RootModule()
rootModule.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.IntKey(0)),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
SchemaVersion: 1,
AttrsJSON: []byte(`{
"ami": "bar",
"network_interface": [{
"device_index": 0,
"description": "Main network interface"
}]
}`),
},
addrs.ProviderConfig{
Type: "test",
}.Absolute(addrs.RootModuleInstance),
)
return state
}
func testReadPlan(t *testing.T, path string) *plans.Plan {
t.Helper()
p, err := planfile.Open(path)
if err != nil {
t.Fatalf("err: %s", err)
}
defer p.Close()
plan, err := p.ReadPlan()
if err != nil {
t.Fatalf("err: %s", err)
}
return plan
}
// planFixtureSchema returns a schema suitable for processing the
// configuration in test-fixtures/plan . This schema should be
// assigned to a mock provider named "test".
func planFixtureSchema() *terraform.ProviderSchema {
return &terraform.ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"test_instance": {
Attributes: map[string]*configschema.Attribute{
"ami": {Type: cty.String, Optional: true},
},
BlockTypes: map[string]*configschema.NestedBlock{
"network_interface": {
Nesting: configschema.NestingList,
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"device_index": {Type: cty.Number, Optional: true},
"description": {Type: cty.String, Optional: true},
},
},
},
},
},
},
}
}