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