github.com/hooklift/terraform@v0.11.0-beta1.0.20171117000744-6786c1361ffe/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_planOutPathNoChange(t *testing.T) {
   203  	b := TestLocal(t)
   204  	TestLocalProvider(t, b, "test")
   205  	terraform.TestStateFile(t, b.StatePath, testPlanState())
   206  
   207  	mod, modCleanup := module.TestTree(t, "./test-fixtures/plan")
   208  	defer modCleanup()
   209  
   210  	outDir := testTempDir(t)
   211  	defer os.RemoveAll(outDir)
   212  	planPath := filepath.Join(outDir, "plan.tfplan")
   213  
   214  	op := testOperationPlan()
   215  	op.Module = mod
   216  	op.PlanOutPath = planPath
   217  
   218  	run, err := b.Operation(context.Background(), op)
   219  	if err != nil {
   220  		t.Fatalf("bad: %s", err)
   221  	}
   222  	<-run.Done()
   223  	if run.Err != nil {
   224  		t.Fatalf("err: %s", err)
   225  	}
   226  
   227  	plan := testReadPlan(t, planPath)
   228  	if !plan.Diff.Empty() {
   229  		t.Fatalf("expected empty plan to be written")
   230  	}
   231  }
   232  
   233  // TestLocal_planScaleOutNoDupeCount tests a Refresh/Plan sequence when a
   234  // resource count is scaled out. The scaled out node needs to exist in the
   235  // graph and run through a plan-style sequence during the refresh phase, but
   236  // can conflate the count if its post-diff count hooks are not skipped. This
   237  // checks to make sure the correct resource count is ultimately given to the
   238  // UI.
   239  func TestLocal_planScaleOutNoDupeCount(t *testing.T) {
   240  	b := TestLocal(t)
   241  	TestLocalProvider(t, b, "test")
   242  	state := &terraform.State{
   243  		Version: 2,
   244  		Modules: []*terraform.ModuleState{
   245  			&terraform.ModuleState{
   246  				Path: []string{"root"},
   247  				Resources: map[string]*terraform.ResourceState{
   248  					"test_instance.foo.0": &terraform.ResourceState{
   249  						Type: "test_instance",
   250  						Primary: &terraform.InstanceState{
   251  							ID: "bar",
   252  						},
   253  					},
   254  					"test_instance.foo.1": &terraform.ResourceState{
   255  						Type: "test_instance",
   256  						Primary: &terraform.InstanceState{
   257  							ID: "bar",
   258  						},
   259  					},
   260  				},
   261  			},
   262  		},
   263  	}
   264  	terraform.TestStateFile(t, b.StatePath, state)
   265  
   266  	actual := new(CountHook)
   267  	b.ContextOpts.Hooks = append(b.ContextOpts.Hooks, actual)
   268  
   269  	mod, modCleanup := module.TestTree(t, "./test-fixtures/plan-scaleout")
   270  	defer modCleanup()
   271  
   272  	outDir := testTempDir(t)
   273  	defer os.RemoveAll(outDir)
   274  
   275  	op := testOperationPlan()
   276  	op.Module = mod
   277  	op.PlanRefresh = true
   278  
   279  	run, err := b.Operation(context.Background(), op)
   280  	if err != nil {
   281  		t.Fatalf("bad: %s", err)
   282  	}
   283  	<-run.Done()
   284  	if run.Err != nil {
   285  		t.Fatalf("err: %s", err)
   286  	}
   287  
   288  	expected := new(CountHook)
   289  	expected.ToAdd = 1
   290  	expected.ToChange = 0
   291  	expected.ToRemoveAndAdd = 0
   292  	expected.ToRemove = 0
   293  
   294  	if !reflect.DeepEqual(expected, actual) {
   295  		t.Fatalf("Expected %#v, got %#v instead.",
   296  			expected, actual)
   297  	}
   298  }
   299  
   300  func testOperationPlan() *backend.Operation {
   301  	return &backend.Operation{
   302  		Type: backend.OperationTypePlan,
   303  	}
   304  }
   305  
   306  // testPlanState is just a common state that we use for testing refresh.
   307  func testPlanState() *terraform.State {
   308  	return &terraform.State{
   309  		Version: 2,
   310  		Modules: []*terraform.ModuleState{
   311  			&terraform.ModuleState{
   312  				Path: []string{"root"},
   313  				Resources: map[string]*terraform.ResourceState{
   314  					"test_instance.foo": &terraform.ResourceState{
   315  						Type: "test_instance",
   316  						Primary: &terraform.InstanceState{
   317  							ID: "bar",
   318  						},
   319  					},
   320  				},
   321  			},
   322  		},
   323  	}
   324  }
   325  
   326  func testReadPlan(t *testing.T, path string) *terraform.Plan {
   327  	f, err := os.Open(path)
   328  	if err != nil {
   329  		t.Fatalf("err: %s", err)
   330  	}
   331  	defer f.Close()
   332  
   333  	p, err := terraform.ReadPlan(f)
   334  	if err != nil {
   335  		t.Fatalf("err: %s", err)
   336  	}
   337  
   338  	return p
   339  }