github.com/jpedro/terraform@v0.11.12-beta1/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_planWithVariables(t *testing.T) {
   162  	b, cleanup := TestLocal(t)
   163  	defer cleanup()
   164  	TestLocalProvider(t, b, "test")
   165  
   166  	mod, modCleanup := module.TestTree(t, "./test-fixtures/plan-vars")
   167  	defer modCleanup()
   168  
   169  	op := testOperationPlan()
   170  	op.Module = mod
   171  	op.PlanRefresh = true
   172  	op.Variables = map[string]interface{}{"cli": "var"}
   173  
   174  	// Set some variables that would have been read from
   175  	// any terraform.tfvars or *.auto.tfvars files.
   176  	b.ContextOpts.Variables = map[string]interface{}{"foo": "bar"}
   177  
   178  	run, err := b.Operation(context.Background(), op)
   179  	if err != nil {
   180  		t.Fatalf("bad: %s", err)
   181  	}
   182  	<-run.Done()
   183  	if run.Err != nil {
   184  		t.Fatalf("err: %s", run.Err)
   185  	}
   186  
   187  	if run.PlanEmpty {
   188  		t.Fatal("plan should not be empty")
   189  	}
   190  }
   191  
   192  func TestLocal_planDestroy(t *testing.T) {
   193  	b, cleanup := TestLocal(t)
   194  	defer cleanup()
   195  	p := TestLocalProvider(t, b, "test")
   196  	terraform.TestStateFile(t, b.StatePath, testPlanState())
   197  
   198  	mod, modCleanup := module.TestTree(t, "./test-fixtures/plan")
   199  	defer modCleanup()
   200  
   201  	outDir := testTempDir(t)
   202  	defer os.RemoveAll(outDir)
   203  	planPath := filepath.Join(outDir, "plan.tfplan")
   204  
   205  	op := testOperationPlan()
   206  	op.Destroy = true
   207  	op.PlanRefresh = true
   208  	op.Module = mod
   209  	op.PlanOutPath = planPath
   210  
   211  	run, err := b.Operation(context.Background(), op)
   212  	if err != nil {
   213  		t.Fatalf("bad: %s", err)
   214  	}
   215  	<-run.Done()
   216  	if run.Err != nil {
   217  		t.Fatalf("err: %s", err)
   218  	}
   219  
   220  	if !p.RefreshCalled {
   221  		t.Fatal("refresh should be called")
   222  	}
   223  
   224  	if run.PlanEmpty {
   225  		t.Fatal("plan should not be empty")
   226  	}
   227  
   228  	plan := testReadPlan(t, planPath)
   229  	for _, m := range plan.Diff.Modules {
   230  		for _, r := range m.Resources {
   231  			if !r.Destroy {
   232  				t.Fatalf("bad: %#v", r)
   233  			}
   234  		}
   235  	}
   236  }
   237  
   238  func TestLocal_planOutPathNoChange(t *testing.T) {
   239  	b, cleanup := TestLocal(t)
   240  	defer cleanup()
   241  	TestLocalProvider(t, b, "test")
   242  	terraform.TestStateFile(t, b.StatePath, testPlanState())
   243  
   244  	mod, modCleanup := module.TestTree(t, "./test-fixtures/plan")
   245  	defer modCleanup()
   246  
   247  	outDir := testTempDir(t)
   248  	defer os.RemoveAll(outDir)
   249  	planPath := filepath.Join(outDir, "plan.tfplan")
   250  
   251  	op := testOperationPlan()
   252  	op.Module = mod
   253  	op.PlanOutPath = planPath
   254  
   255  	run, err := b.Operation(context.Background(), op)
   256  	if err != nil {
   257  		t.Fatalf("bad: %s", err)
   258  	}
   259  	<-run.Done()
   260  	if run.Err != nil {
   261  		t.Fatalf("err: %s", err)
   262  	}
   263  
   264  	plan := testReadPlan(t, planPath)
   265  	if !plan.Diff.Empty() {
   266  		t.Fatalf("expected empty plan to be written")
   267  	}
   268  }
   269  
   270  // TestLocal_planScaleOutNoDupeCount tests a Refresh/Plan sequence when a
   271  // resource count is scaled out. The scaled out node needs to exist in the
   272  // graph and run through a plan-style sequence during the refresh phase, but
   273  // can conflate the count if its post-diff count hooks are not skipped. This
   274  // checks to make sure the correct resource count is ultimately given to the
   275  // UI.
   276  func TestLocal_planScaleOutNoDupeCount(t *testing.T) {
   277  	b, cleanup := TestLocal(t)
   278  	defer cleanup()
   279  	TestLocalProvider(t, b, "test")
   280  	state := &terraform.State{
   281  		Version: 2,
   282  		Modules: []*terraform.ModuleState{
   283  			&terraform.ModuleState{
   284  				Path: []string{"root"},
   285  				Resources: map[string]*terraform.ResourceState{
   286  					"test_instance.foo.0": &terraform.ResourceState{
   287  						Type: "test_instance",
   288  						Primary: &terraform.InstanceState{
   289  							ID: "bar",
   290  						},
   291  					},
   292  					"test_instance.foo.1": &terraform.ResourceState{
   293  						Type: "test_instance",
   294  						Primary: &terraform.InstanceState{
   295  							ID: "bar",
   296  						},
   297  					},
   298  				},
   299  			},
   300  		},
   301  	}
   302  	terraform.TestStateFile(t, b.StatePath, state)
   303  
   304  	actual := new(CountHook)
   305  	b.ContextOpts.Hooks = append(b.ContextOpts.Hooks, actual)
   306  
   307  	mod, modCleanup := module.TestTree(t, "./test-fixtures/plan-scaleout")
   308  	defer modCleanup()
   309  
   310  	outDir := testTempDir(t)
   311  	defer os.RemoveAll(outDir)
   312  
   313  	op := testOperationPlan()
   314  	op.Module = mod
   315  	op.PlanRefresh = true
   316  
   317  	run, err := b.Operation(context.Background(), op)
   318  	if err != nil {
   319  		t.Fatalf("bad: %s", err)
   320  	}
   321  	<-run.Done()
   322  	if run.Err != nil {
   323  		t.Fatalf("err: %s", err)
   324  	}
   325  
   326  	expected := new(CountHook)
   327  	expected.ToAdd = 1
   328  	expected.ToChange = 0
   329  	expected.ToRemoveAndAdd = 0
   330  	expected.ToRemove = 0
   331  
   332  	if !reflect.DeepEqual(expected, actual) {
   333  		t.Fatalf("Expected %#v, got %#v instead.",
   334  			expected, actual)
   335  	}
   336  }
   337  
   338  func testOperationPlan() *backend.Operation {
   339  	return &backend.Operation{
   340  		Type: backend.OperationTypePlan,
   341  	}
   342  }
   343  
   344  // testPlanState is just a common state that we use for testing refresh.
   345  func testPlanState() *terraform.State {
   346  	return &terraform.State{
   347  		Version: 2,
   348  		Modules: []*terraform.ModuleState{
   349  			&terraform.ModuleState{
   350  				Path: []string{"root"},
   351  				Resources: map[string]*terraform.ResourceState{
   352  					"test_instance.foo": &terraform.ResourceState{
   353  						Type: "test_instance",
   354  						Primary: &terraform.InstanceState{
   355  							ID: "bar",
   356  						},
   357  					},
   358  				},
   359  			},
   360  		},
   361  	}
   362  }
   363  
   364  func testReadPlan(t *testing.T, path string) *terraform.Plan {
   365  	f, err := os.Open(path)
   366  	if err != nil {
   367  		t.Fatalf("err: %s", err)
   368  	}
   369  	defer f.Close()
   370  
   371  	p, err := terraform.ReadPlan(f)
   372  	if err != nil {
   373  		t.Fatalf("err: %s", err)
   374  	}
   375  
   376  	return p
   377  }