github.com/trawler/terraform@v0.10.8-0.20171106022149-4b1c7a1d9b48/backend/local/backend_plan_test.go (about)

     1  package local
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"path/filepath"
     7  	"reflect"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/hashicorp/terraform/backend"
    12  	"github.com/hashicorp/terraform/config/module"
    13  	"github.com/hashicorp/terraform/terraform"
    14  	"github.com/mitchellh/cli"
    15  )
    16  
    17  func TestLocal_planBasic(t *testing.T) {
    18  	b := TestLocal(t)
    19  	p := TestLocalProvider(t, b, "test")
    20  
    21  	mod, modCleanup := module.TestTree(t, "./test-fixtures/plan")
    22  	defer modCleanup()
    23  
    24  	op := testOperationPlan()
    25  	op.Module = mod
    26  	op.PlanRefresh = true
    27  
    28  	run, err := b.Operation(context.Background(), op)
    29  	if err != nil {
    30  		t.Fatalf("bad: %s", err)
    31  	}
    32  	<-run.Done()
    33  	if run.Err != nil {
    34  		t.Fatalf("err: %s", err)
    35  	}
    36  
    37  	if !p.DiffCalled {
    38  		t.Fatal("diff should be called")
    39  	}
    40  }
    41  
    42  func TestLocal_planInAutomation(t *testing.T) {
    43  	b := TestLocal(t)
    44  	TestLocalProvider(t, b, "test")
    45  
    46  	mod, modCleanup := module.TestTree(t, "./test-fixtures/plan")
    47  	defer modCleanup()
    48  
    49  	const msg = `You didn't specify an "-out" parameter`
    50  
    51  	// When we're "in automation" we omit certain text from the
    52  	// plan output. However, testing for the absense of text is
    53  	// unreliable in the face of future copy changes, so we'll
    54  	// mitigate that by running both with and without the flag
    55  	// set so we can ensure that the expected messages _are_
    56  	// included the first time.
    57  	b.RunningInAutomation = false
    58  	b.CLI = cli.NewMockUi()
    59  	{
    60  		op := testOperationPlan()
    61  		op.Module = mod
    62  		op.PlanRefresh = true
    63  
    64  		run, err := b.Operation(context.Background(), op)
    65  		if err != nil {
    66  			t.Fatalf("unexpected error: %s", err)
    67  		}
    68  		<-run.Done()
    69  		if run.Err != nil {
    70  			t.Fatalf("unexpected error: %s", err)
    71  		}
    72  
    73  		output := b.CLI.(*cli.MockUi).OutputWriter.String()
    74  		if !strings.Contains(output, msg) {
    75  			t.Fatalf("missing next-steps message when not in automation")
    76  		}
    77  	}
    78  
    79  	// On the second run, we expect the next-steps messaging to be absent
    80  	// since we're now "running in automation".
    81  	b.RunningInAutomation = true
    82  	b.CLI = cli.NewMockUi()
    83  	{
    84  		op := testOperationPlan()
    85  		op.Module = mod
    86  		op.PlanRefresh = true
    87  
    88  		run, err := b.Operation(context.Background(), op)
    89  		if err != nil {
    90  			t.Fatalf("unexpected error: %s", err)
    91  		}
    92  		<-run.Done()
    93  		if run.Err != nil {
    94  			t.Fatalf("unexpected error: %s", err)
    95  		}
    96  
    97  		output := b.CLI.(*cli.MockUi).OutputWriter.String()
    98  		if strings.Contains(output, msg) {
    99  			t.Fatalf("next-steps message present when in automation")
   100  		}
   101  	}
   102  
   103  }
   104  
   105  func TestLocal_planNoConfig(t *testing.T) {
   106  	b := TestLocal(t)
   107  	TestLocalProvider(t, b, "test")
   108  
   109  	op := testOperationPlan()
   110  	op.Module = nil
   111  	op.PlanRefresh = true
   112  
   113  	run, err := b.Operation(context.Background(), op)
   114  	if err != nil {
   115  		t.Fatalf("bad: %s", err)
   116  	}
   117  	<-run.Done()
   118  
   119  	err = run.Err
   120  	if err == nil {
   121  		t.Fatal("should error")
   122  	}
   123  	if !strings.Contains(err.Error(), "configuration") {
   124  		t.Fatalf("bad: %s", err)
   125  	}
   126  }
   127  
   128  func TestLocal_planRefreshFalse(t *testing.T) {
   129  	b := TestLocal(t)
   130  	p := TestLocalProvider(t, b, "test")
   131  	terraform.TestStateFile(t, b.StatePath, testPlanState())
   132  
   133  	mod, modCleanup := module.TestTree(t, "./test-fixtures/plan")
   134  	defer modCleanup()
   135  
   136  	op := testOperationPlan()
   137  	op.Module = mod
   138  
   139  	run, err := b.Operation(context.Background(), op)
   140  	if err != nil {
   141  		t.Fatalf("bad: %s", err)
   142  	}
   143  	<-run.Done()
   144  	if run.Err != nil {
   145  		t.Fatalf("err: %s", err)
   146  	}
   147  
   148  	if p.RefreshCalled {
   149  		t.Fatal("refresh should not be called")
   150  	}
   151  
   152  	if !run.PlanEmpty {
   153  		t.Fatal("plan should be empty")
   154  	}
   155  }
   156  
   157  func TestLocal_planDestroy(t *testing.T) {
   158  	b := TestLocal(t)
   159  	p := TestLocalProvider(t, b, "test")
   160  	terraform.TestStateFile(t, b.StatePath, testPlanState())
   161  
   162  	mod, modCleanup := module.TestTree(t, "./test-fixtures/plan")
   163  	defer modCleanup()
   164  
   165  	outDir := testTempDir(t)
   166  	defer os.RemoveAll(outDir)
   167  	planPath := filepath.Join(outDir, "plan.tfplan")
   168  
   169  	op := testOperationPlan()
   170  	op.Destroy = true
   171  	op.PlanRefresh = true
   172  	op.Module = mod
   173  	op.PlanOutPath = planPath
   174  
   175  	run, err := b.Operation(context.Background(), op)
   176  	if err != nil {
   177  		t.Fatalf("bad: %s", err)
   178  	}
   179  	<-run.Done()
   180  	if run.Err != nil {
   181  		t.Fatalf("err: %s", err)
   182  	}
   183  
   184  	if !p.RefreshCalled {
   185  		t.Fatal("refresh should be called")
   186  	}
   187  
   188  	if run.PlanEmpty {
   189  		t.Fatal("plan should not be empty")
   190  	}
   191  
   192  	plan := testReadPlan(t, planPath)
   193  	for _, m := range plan.Diff.Modules {
   194  		for _, r := range m.Resources {
   195  			if !r.Destroy {
   196  				t.Fatalf("bad: %#v", r)
   197  			}
   198  		}
   199  	}
   200  }
   201  
   202  func TestLocal_planDestroyNoConfig(t *testing.T) {
   203  	b := TestLocal(t)
   204  	p := TestLocalProvider(t, b, "test")
   205  	terraform.TestStateFile(t, b.StatePath, testPlanState())
   206  
   207  	outDir := testTempDir(t)
   208  	defer os.RemoveAll(outDir)
   209  	planPath := filepath.Join(outDir, "plan.tfplan")
   210  
   211  	op := testOperationPlan()
   212  	op.Destroy = true
   213  	op.PlanRefresh = true
   214  	op.Module = nil
   215  	op.PlanOutPath = planPath
   216  
   217  	run, err := b.Operation(context.Background(), op)
   218  	if err != nil {
   219  		t.Fatalf("bad: %s", err)
   220  	}
   221  	<-run.Done()
   222  	if run.Err != nil {
   223  		t.Fatalf("err: %s", err)
   224  	}
   225  
   226  	if !p.RefreshCalled {
   227  		t.Fatal("refresh should be called")
   228  	}
   229  
   230  	if run.PlanEmpty {
   231  		t.Fatal("plan should not be empty")
   232  	}
   233  
   234  	plan := testReadPlan(t, planPath)
   235  	for _, m := range plan.Diff.Modules {
   236  		for _, r := range m.Resources {
   237  			if !r.Destroy {
   238  				t.Fatalf("bad: %#v", r)
   239  			}
   240  		}
   241  	}
   242  }
   243  
   244  func TestLocal_planOutPathNoChange(t *testing.T) {
   245  	b := TestLocal(t)
   246  	TestLocalProvider(t, b, "test")
   247  	terraform.TestStateFile(t, b.StatePath, testPlanState())
   248  
   249  	mod, modCleanup := module.TestTree(t, "./test-fixtures/plan")
   250  	defer modCleanup()
   251  
   252  	outDir := testTempDir(t)
   253  	defer os.RemoveAll(outDir)
   254  	planPath := filepath.Join(outDir, "plan.tfplan")
   255  
   256  	op := testOperationPlan()
   257  	op.Module = mod
   258  	op.PlanOutPath = planPath
   259  
   260  	run, err := b.Operation(context.Background(), op)
   261  	if err != nil {
   262  		t.Fatalf("bad: %s", err)
   263  	}
   264  	<-run.Done()
   265  	if run.Err != nil {
   266  		t.Fatalf("err: %s", err)
   267  	}
   268  
   269  	plan := testReadPlan(t, planPath)
   270  	if !plan.Diff.Empty() {
   271  		t.Fatalf("expected empty plan to be written")
   272  	}
   273  }
   274  
   275  // TestLocal_planScaleOutNoDupeCount tests a Refresh/Plan sequence when a
   276  // resource count is scaled out. The scaled out node needs to exist in the
   277  // graph and run through a plan-style sequence during the refresh phase, but
   278  // can conflate the count if its post-diff count hooks are not skipped. This
   279  // checks to make sure the correct resource count is ultimately given to the
   280  // UI.
   281  func TestLocal_planScaleOutNoDupeCount(t *testing.T) {
   282  	b := TestLocal(t)
   283  	TestLocalProvider(t, b, "test")
   284  	state := &terraform.State{
   285  		Version: 2,
   286  		Modules: []*terraform.ModuleState{
   287  			&terraform.ModuleState{
   288  				Path: []string{"root"},
   289  				Resources: map[string]*terraform.ResourceState{
   290  					"test_instance.foo.0": &terraform.ResourceState{
   291  						Type: "test_instance",
   292  						Primary: &terraform.InstanceState{
   293  							ID: "bar",
   294  						},
   295  					},
   296  					"test_instance.foo.1": &terraform.ResourceState{
   297  						Type: "test_instance",
   298  						Primary: &terraform.InstanceState{
   299  							ID: "bar",
   300  						},
   301  					},
   302  				},
   303  			},
   304  		},
   305  	}
   306  	terraform.TestStateFile(t, b.StatePath, state)
   307  
   308  	actual := new(CountHook)
   309  	b.ContextOpts.Hooks = append(b.ContextOpts.Hooks, actual)
   310  
   311  	mod, modCleanup := module.TestTree(t, "./test-fixtures/plan-scaleout")
   312  	defer modCleanup()
   313  
   314  	outDir := testTempDir(t)
   315  	defer os.RemoveAll(outDir)
   316  
   317  	op := testOperationPlan()
   318  	op.Module = mod
   319  	op.PlanRefresh = true
   320  
   321  	run, err := b.Operation(context.Background(), op)
   322  	if err != nil {
   323  		t.Fatalf("bad: %s", err)
   324  	}
   325  	<-run.Done()
   326  	if run.Err != nil {
   327  		t.Fatalf("err: %s", err)
   328  	}
   329  
   330  	expected := new(CountHook)
   331  	expected.ToAdd = 1
   332  	expected.ToChange = 0
   333  	expected.ToRemoveAndAdd = 0
   334  	expected.ToRemove = 0
   335  
   336  	if !reflect.DeepEqual(expected, actual) {
   337  		t.Fatalf("Expected %#v, got %#v instead.",
   338  			expected, actual)
   339  	}
   340  }
   341  
   342  func testOperationPlan() *backend.Operation {
   343  	return &backend.Operation{
   344  		Type: backend.OperationTypePlan,
   345  	}
   346  }
   347  
   348  // testPlanState is just a common state that we use for testing refresh.
   349  func testPlanState() *terraform.State {
   350  	return &terraform.State{
   351  		Version: 2,
   352  		Modules: []*terraform.ModuleState{
   353  			&terraform.ModuleState{
   354  				Path: []string{"root"},
   355  				Resources: map[string]*terraform.ResourceState{
   356  					"test_instance.foo": &terraform.ResourceState{
   357  						Type: "test_instance",
   358  						Primary: &terraform.InstanceState{
   359  							ID: "bar",
   360  						},
   361  					},
   362  				},
   363  			},
   364  		},
   365  	}
   366  }
   367  
   368  func testReadPlan(t *testing.T, path string) *terraform.Plan {
   369  	f, err := os.Open(path)
   370  	if err != nil {
   371  		t.Fatalf("err: %s", err)
   372  	}
   373  	defer f.Close()
   374  
   375  	p, err := terraform.ReadPlan(f)
   376  	if err != nil {
   377  		t.Fatalf("err: %s", err)
   378  	}
   379  
   380  	return p
   381  }