github.com/pulumi/terraform@v1.4.0/pkg/command/plan_test.go (about)

     1  package command
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"os"
     9  	"path"
    10  	"path/filepath"
    11  	"strings"
    12  	"sync"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/davecgh/go-spew/spew"
    17  	"github.com/zclconf/go-cty/cty"
    18  
    19  	"github.com/pulumi/terraform/pkg/addrs"
    20  	backendinit "github.com/pulumi/terraform/pkg/backend/init"
    21  	"github.com/pulumi/terraform/pkg/checks"
    22  	"github.com/pulumi/terraform/pkg/configs/configschema"
    23  	"github.com/pulumi/terraform/pkg/plans"
    24  	"github.com/pulumi/terraform/pkg/providers"
    25  	"github.com/pulumi/terraform/pkg/states"
    26  	"github.com/pulumi/terraform/pkg/terraform"
    27  	"github.com/pulumi/terraform/pkg/tfdiags"
    28  )
    29  
    30  func TestPlan(t *testing.T) {
    31  	td := t.TempDir()
    32  	testCopyDir(t, testFixturePath("plan"), td)
    33  	defer testChdir(t, td)()
    34  
    35  	p := planFixtureProvider()
    36  	view, done := testView(t)
    37  	c := &PlanCommand{
    38  		Meta: Meta{
    39  			testingOverrides: metaOverridesForProvider(p),
    40  			View:             view,
    41  		},
    42  	}
    43  
    44  	args := []string{}
    45  	code := c.Run(args)
    46  	output := done(t)
    47  	if code != 0 {
    48  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
    49  	}
    50  }
    51  
    52  func TestPlan_lockedState(t *testing.T) {
    53  	td := t.TempDir()
    54  	testCopyDir(t, testFixturePath("plan"), td)
    55  	defer testChdir(t, td)()
    56  
    57  	unlock, err := testLockState(t, testDataDir, filepath.Join(td, DefaultStateFilename))
    58  	if err != nil {
    59  		t.Fatal(err)
    60  	}
    61  	defer unlock()
    62  
    63  	p := planFixtureProvider()
    64  	view, done := testView(t)
    65  	c := &PlanCommand{
    66  		Meta: Meta{
    67  			testingOverrides: metaOverridesForProvider(p),
    68  			View:             view,
    69  		},
    70  	}
    71  
    72  	args := []string{}
    73  	code := c.Run(args)
    74  	if code == 0 {
    75  		t.Fatal("expected error", done(t).Stdout())
    76  	}
    77  
    78  	output := done(t).Stderr()
    79  	if !strings.Contains(output, "lock") {
    80  		t.Fatal("command output does not look like a lock error:", output)
    81  	}
    82  }
    83  
    84  func TestPlan_plan(t *testing.T) {
    85  	testCwd(t)
    86  
    87  	planPath := testPlanFileNoop(t)
    88  
    89  	p := testProvider()
    90  	view, done := testView(t)
    91  	c := &PlanCommand{
    92  		Meta: Meta{
    93  			testingOverrides: metaOverridesForProvider(p),
    94  			View:             view,
    95  		},
    96  	}
    97  
    98  	args := []string{planPath}
    99  	code := c.Run(args)
   100  	output := done(t)
   101  	if code != 1 {
   102  		t.Fatalf("wrong exit status %d; want 1\nstderr: %s", code, output.Stderr())
   103  	}
   104  }
   105  
   106  func TestPlan_destroy(t *testing.T) {
   107  	td := t.TempDir()
   108  	testCopyDir(t, testFixturePath("plan"), td)
   109  	defer testChdir(t, td)()
   110  
   111  	originalState := states.BuildState(func(s *states.SyncState) {
   112  		s.SetResourceInstanceCurrent(
   113  			addrs.Resource{
   114  				Mode: addrs.ManagedResourceMode,
   115  				Type: "test_instance",
   116  				Name: "foo",
   117  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   118  			&states.ResourceInstanceObjectSrc{
   119  				AttrsJSON: []byte(`{"id":"bar"}`),
   120  				Status:    states.ObjectReady,
   121  			},
   122  			addrs.AbsProviderConfig{
   123  				Provider: addrs.NewDefaultProvider("test"),
   124  				Module:   addrs.RootModule,
   125  			},
   126  		)
   127  	})
   128  	outPath := testTempFile(t)
   129  	statePath := testStateFile(t, originalState)
   130  
   131  	p := planFixtureProvider()
   132  	view, done := testView(t)
   133  	c := &PlanCommand{
   134  		Meta: Meta{
   135  			testingOverrides: metaOverridesForProvider(p),
   136  			View:             view,
   137  		},
   138  	}
   139  
   140  	args := []string{
   141  		"-destroy",
   142  		"-out", outPath,
   143  		"-state", statePath,
   144  	}
   145  	code := c.Run(args)
   146  	output := done(t)
   147  	if code != 0 {
   148  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   149  	}
   150  
   151  	plan := testReadPlan(t, outPath)
   152  	for _, rc := range plan.Changes.Resources {
   153  		if got, want := rc.Action, plans.Delete; got != want {
   154  			t.Fatalf("wrong action %s for %s; want %s\nplanned change: %s", got, rc.Addr, want, spew.Sdump(rc))
   155  		}
   156  	}
   157  }
   158  
   159  func TestPlan_noState(t *testing.T) {
   160  	td := t.TempDir()
   161  	testCopyDir(t, testFixturePath("plan"), td)
   162  	defer testChdir(t, td)()
   163  
   164  	p := planFixtureProvider()
   165  	view, done := testView(t)
   166  	c := &PlanCommand{
   167  		Meta: Meta{
   168  			testingOverrides: metaOverridesForProvider(p),
   169  			View:             view,
   170  		},
   171  	}
   172  
   173  	args := []string{}
   174  	code := c.Run(args)
   175  	output := done(t)
   176  	if code != 0 {
   177  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   178  	}
   179  
   180  	// Verify that refresh was called
   181  	if p.ReadResourceCalled {
   182  		t.Fatal("ReadResource should not be called")
   183  	}
   184  
   185  	// Verify that the provider was called with the existing state
   186  	actual := p.PlanResourceChangeRequest.PriorState
   187  	expected := cty.NullVal(p.GetProviderSchemaResponse.ResourceTypes["test_instance"].Block.ImpliedType())
   188  	if !expected.RawEquals(actual) {
   189  		t.Fatalf("wrong prior state\ngot:  %#v\nwant: %#v", actual, expected)
   190  	}
   191  }
   192  
   193  func TestPlan_outPath(t *testing.T) {
   194  	td := t.TempDir()
   195  	testCopyDir(t, testFixturePath("plan"), td)
   196  	defer testChdir(t, td)()
   197  
   198  	outPath := filepath.Join(td, "test.plan")
   199  
   200  	p := planFixtureProvider()
   201  	view, done := testView(t)
   202  	c := &PlanCommand{
   203  		Meta: Meta{
   204  			testingOverrides: metaOverridesForProvider(p),
   205  			View:             view,
   206  		},
   207  	}
   208  
   209  	p.PlanResourceChangeResponse = &providers.PlanResourceChangeResponse{
   210  		PlannedState: cty.NullVal(cty.EmptyObject),
   211  	}
   212  
   213  	args := []string{
   214  		"-out", outPath,
   215  	}
   216  	code := c.Run(args)
   217  	output := done(t)
   218  	if code != 0 {
   219  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   220  	}
   221  
   222  	testReadPlan(t, outPath) // will call t.Fatal itself if the file cannot be read
   223  }
   224  
   225  func TestPlan_outPathNoChange(t *testing.T) {
   226  	td := t.TempDir()
   227  	testCopyDir(t, testFixturePath("plan"), td)
   228  	defer testChdir(t, td)()
   229  
   230  	originalState := states.BuildState(func(s *states.SyncState) {
   231  		s.SetResourceInstanceCurrent(
   232  			addrs.Resource{
   233  				Mode: addrs.ManagedResourceMode,
   234  				Type: "test_instance",
   235  				Name: "foo",
   236  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   237  			&states.ResourceInstanceObjectSrc{
   238  				// Aside from "id" (which is computed) the values here must
   239  				// exactly match the values in the "plan" test fixture in order
   240  				// to produce the empty plan we need for this test.
   241  				AttrsJSON: []byte(`{"id":"bar","ami":"bar","network_interface":[{"description":"Main network interface","device_index":"0"}]}`),
   242  				Status:    states.ObjectReady,
   243  			},
   244  			addrs.AbsProviderConfig{
   245  				Provider: addrs.NewDefaultProvider("test"),
   246  				Module:   addrs.RootModule,
   247  			},
   248  		)
   249  	})
   250  	statePath := testStateFile(t, originalState)
   251  
   252  	outPath := filepath.Join(td, "test.plan")
   253  
   254  	p := planFixtureProvider()
   255  	view, done := testView(t)
   256  	c := &PlanCommand{
   257  		Meta: Meta{
   258  			testingOverrides: metaOverridesForProvider(p),
   259  			View:             view,
   260  		},
   261  	}
   262  
   263  	args := []string{
   264  		"-out", outPath,
   265  		"-state", statePath,
   266  	}
   267  	code := c.Run(args)
   268  	output := done(t)
   269  	if code != 0 {
   270  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   271  	}
   272  
   273  	plan := testReadPlan(t, outPath)
   274  	if !plan.Changes.Empty() {
   275  		t.Fatalf("Expected empty plan to be written to plan file, got: %s", spew.Sdump(plan))
   276  	}
   277  }
   278  
   279  func TestPlan_outPathWithError(t *testing.T) {
   280  	td := t.TempDir()
   281  	testCopyDir(t, testFixturePath("plan-fail-condition"), td)
   282  	defer testChdir(t, td)()
   283  
   284  	outPath := filepath.Join(td, "test.plan")
   285  
   286  	p := planFixtureProvider()
   287  	view, done := testView(t)
   288  	c := &PlanCommand{
   289  		Meta: Meta{
   290  			testingOverrides: metaOverridesForProvider(p),
   291  			View:             view,
   292  		},
   293  	}
   294  
   295  	p.PlanResourceChangeResponse = &providers.PlanResourceChangeResponse{
   296  		PlannedState: cty.NullVal(cty.EmptyObject),
   297  	}
   298  
   299  	args := []string{
   300  		"-out", outPath,
   301  	}
   302  	code := c.Run(args)
   303  	output := done(t)
   304  	if code == 0 {
   305  		t.Fatal("expected non-zero exit status", output)
   306  	}
   307  
   308  	plan := testReadPlan(t, outPath) // will call t.Fatal itself if the file cannot be read
   309  	if !plan.Errored {
   310  		t.Fatal("plan should be marked with Errored")
   311  	}
   312  
   313  	if plan.Checks == nil {
   314  		t.Fatal("plan contains no checks")
   315  	}
   316  
   317  	// the checks should only contain one failure
   318  	results := plan.Checks.ConfigResults.Elements()
   319  	if len(results) != 1 {
   320  		t.Fatal("incorrect number of check results", len(results))
   321  	}
   322  	if results[0].Value.Status != checks.StatusFail {
   323  		t.Errorf("incorrect status, got %s", results[0].Value.Status)
   324  	}
   325  }
   326  
   327  // When using "-out" with a backend, the plan should encode the backend config
   328  func TestPlan_outBackend(t *testing.T) {
   329  	// Create a temporary working directory that is empty
   330  	td := t.TempDir()
   331  	testCopyDir(t, testFixturePath("plan-out-backend"), td)
   332  	defer testChdir(t, td)()
   333  
   334  	originalState := states.BuildState(func(s *states.SyncState) {
   335  		s.SetResourceInstanceCurrent(
   336  			addrs.Resource{
   337  				Mode: addrs.ManagedResourceMode,
   338  				Type: "test_instance",
   339  				Name: "foo",
   340  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   341  			&states.ResourceInstanceObjectSrc{
   342  				AttrsJSON: []byte(`{"id":"bar","ami":"bar"}`),
   343  				Status:    states.ObjectReady,
   344  			},
   345  			addrs.AbsProviderConfig{
   346  				Provider: addrs.NewDefaultProvider("test"),
   347  				Module:   addrs.RootModule,
   348  			},
   349  		)
   350  	})
   351  
   352  	// Set up our backend state
   353  	dataState, srv := testBackendState(t, originalState, 200)
   354  	defer srv.Close()
   355  	testStateFileRemote(t, dataState)
   356  
   357  	outPath := "foo"
   358  	p := testProvider()
   359  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
   360  		ResourceTypes: map[string]providers.Schema{
   361  			"test_instance": {
   362  				Block: &configschema.Block{
   363  					Attributes: map[string]*configschema.Attribute{
   364  						"id": {
   365  							Type:     cty.String,
   366  							Computed: true,
   367  						},
   368  						"ami": {
   369  							Type:     cty.String,
   370  							Optional: true,
   371  						},
   372  					},
   373  				},
   374  			},
   375  		},
   376  	}
   377  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
   378  		return providers.PlanResourceChangeResponse{
   379  			PlannedState: req.ProposedNewState,
   380  		}
   381  	}
   382  	view, done := testView(t)
   383  	c := &PlanCommand{
   384  		Meta: Meta{
   385  			testingOverrides: metaOverridesForProvider(p),
   386  			View:             view,
   387  		},
   388  	}
   389  
   390  	args := []string{
   391  		"-out", outPath,
   392  	}
   393  	code := c.Run(args)
   394  	output := done(t)
   395  	if code != 0 {
   396  		t.Logf("stdout: %s", output.Stdout())
   397  		t.Fatalf("plan command failed with exit code %d\n\n%s", code, output.Stderr())
   398  	}
   399  
   400  	plan := testReadPlan(t, outPath)
   401  	if !plan.Changes.Empty() {
   402  		t.Fatalf("Expected empty plan to be written to plan file, got: %s", spew.Sdump(plan))
   403  	}
   404  
   405  	if got, want := plan.Backend.Type, "http"; got != want {
   406  		t.Errorf("wrong backend type %q; want %q", got, want)
   407  	}
   408  	if got, want := plan.Backend.Workspace, "default"; got != want {
   409  		t.Errorf("wrong backend workspace %q; want %q", got, want)
   410  	}
   411  	{
   412  		httpBackend := backendinit.Backend("http")()
   413  		schema := httpBackend.ConfigSchema()
   414  		got, err := plan.Backend.Config.Decode(schema.ImpliedType())
   415  		if err != nil {
   416  			t.Fatalf("failed to decode backend config in plan: %s", err)
   417  		}
   418  		want, err := dataState.Backend.Config(schema)
   419  		if err != nil {
   420  			t.Fatalf("failed to decode cached config: %s", err)
   421  		}
   422  		if !want.RawEquals(got) {
   423  			t.Errorf("wrong backend config\ngot:  %#v\nwant: %#v", got, want)
   424  		}
   425  	}
   426  }
   427  
   428  func TestPlan_refreshFalse(t *testing.T) {
   429  	// Create a temporary working directory that is empty
   430  	td := t.TempDir()
   431  	testCopyDir(t, testFixturePath("plan"), td)
   432  	defer testChdir(t, td)()
   433  
   434  	p := planFixtureProvider()
   435  	view, done := testView(t)
   436  	c := &PlanCommand{
   437  		Meta: Meta{
   438  			testingOverrides: metaOverridesForProvider(p),
   439  			View:             view,
   440  		},
   441  	}
   442  
   443  	args := []string{
   444  		"-refresh=false",
   445  	}
   446  	code := c.Run(args)
   447  	output := done(t)
   448  	if code != 0 {
   449  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   450  	}
   451  
   452  	if p.ReadResourceCalled {
   453  		t.Fatal("ReadResource should not have been called")
   454  	}
   455  }
   456  
   457  func TestPlan_state(t *testing.T) {
   458  	// Create a temporary working directory that is empty
   459  	td := t.TempDir()
   460  	testCopyDir(t, testFixturePath("plan"), td)
   461  	defer testChdir(t, td)()
   462  
   463  	originalState := testState()
   464  	statePath := testStateFile(t, originalState)
   465  
   466  	p := planFixtureProvider()
   467  	view, done := testView(t)
   468  	c := &PlanCommand{
   469  		Meta: Meta{
   470  			testingOverrides: metaOverridesForProvider(p),
   471  			View:             view,
   472  		},
   473  	}
   474  
   475  	args := []string{
   476  		"-state", statePath,
   477  	}
   478  	code := c.Run(args)
   479  	output := done(t)
   480  	if code != 0 {
   481  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   482  	}
   483  
   484  	// Verify that the provider was called with the existing state
   485  	actual := p.PlanResourceChangeRequest.PriorState
   486  	expected := cty.ObjectVal(map[string]cty.Value{
   487  		"id":  cty.StringVal("bar"),
   488  		"ami": cty.NullVal(cty.String),
   489  		"network_interface": cty.ListValEmpty(cty.Object(map[string]cty.Type{
   490  			"device_index": cty.String,
   491  			"description":  cty.String,
   492  		})),
   493  	})
   494  	if !expected.RawEquals(actual) {
   495  		t.Fatalf("wrong prior state\ngot:  %#v\nwant: %#v", actual, expected)
   496  	}
   497  }
   498  
   499  func TestPlan_stateDefault(t *testing.T) {
   500  	// Create a temporary working directory that is empty
   501  	td := t.TempDir()
   502  	testCopyDir(t, testFixturePath("plan"), td)
   503  	defer testChdir(t, td)()
   504  
   505  	// Generate state and move it to the default path
   506  	originalState := testState()
   507  	statePath := testStateFile(t, originalState)
   508  	os.Rename(statePath, path.Join(td, "terraform.tfstate"))
   509  
   510  	p := planFixtureProvider()
   511  	view, done := testView(t)
   512  	c := &PlanCommand{
   513  		Meta: Meta{
   514  			testingOverrides: metaOverridesForProvider(p),
   515  			View:             view,
   516  		},
   517  	}
   518  
   519  	args := []string{}
   520  	code := c.Run(args)
   521  	output := done(t)
   522  	if code != 0 {
   523  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   524  	}
   525  
   526  	// Verify that the provider was called with the existing state
   527  	actual := p.PlanResourceChangeRequest.PriorState
   528  	expected := cty.ObjectVal(map[string]cty.Value{
   529  		"id":  cty.StringVal("bar"),
   530  		"ami": cty.NullVal(cty.String),
   531  		"network_interface": cty.ListValEmpty(cty.Object(map[string]cty.Type{
   532  			"device_index": cty.String,
   533  			"description":  cty.String,
   534  		})),
   535  	})
   536  	if !expected.RawEquals(actual) {
   537  		t.Fatalf("wrong prior state\ngot:  %#v\nwant: %#v", actual, expected)
   538  	}
   539  }
   540  
   541  func TestPlan_validate(t *testing.T) {
   542  	// This is triggered by not asking for input so we have to set this to false
   543  	test = false
   544  	defer func() { test = true }()
   545  
   546  	td := t.TempDir()
   547  	testCopyDir(t, testFixturePath("plan-invalid"), td)
   548  	defer testChdir(t, td)()
   549  
   550  	p := testProvider()
   551  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
   552  		ResourceTypes: map[string]providers.Schema{
   553  			"test_instance": {
   554  				Block: &configschema.Block{
   555  					Attributes: map[string]*configschema.Attribute{
   556  						"id": {Type: cty.String, Optional: true, Computed: true},
   557  					},
   558  				},
   559  			},
   560  		},
   561  	}
   562  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
   563  		return providers.PlanResourceChangeResponse{
   564  			PlannedState: req.ProposedNewState,
   565  		}
   566  	}
   567  	view, done := testView(t)
   568  	c := &PlanCommand{
   569  		Meta: Meta{
   570  			testingOverrides: metaOverridesForProvider(p),
   571  			View:             view,
   572  		},
   573  	}
   574  
   575  	args := []string{"-no-color"}
   576  	code := c.Run(args)
   577  	output := done(t)
   578  	if code != 1 {
   579  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   580  	}
   581  
   582  	actual := output.Stderr()
   583  	if want := "Error: Invalid count argument"; !strings.Contains(actual, want) {
   584  		t.Fatalf("unexpected error output\ngot:\n%s\n\nshould contain: %s", actual, want)
   585  	}
   586  	if want := "9:   count = timestamp()"; !strings.Contains(actual, want) {
   587  		t.Fatalf("unexpected error output\ngot:\n%s\n\nshould contain: %s", actual, want)
   588  	}
   589  }
   590  
   591  func TestPlan_vars(t *testing.T) {
   592  	// Create a temporary working directory that is empty
   593  	td := t.TempDir()
   594  	testCopyDir(t, testFixturePath("plan-vars"), td)
   595  	defer testChdir(t, td)()
   596  
   597  	p := planVarsFixtureProvider()
   598  	view, done := testView(t)
   599  	c := &PlanCommand{
   600  		Meta: Meta{
   601  			testingOverrides: metaOverridesForProvider(p),
   602  			View:             view,
   603  		},
   604  	}
   605  
   606  	actual := ""
   607  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
   608  		actual = req.ProposedNewState.GetAttr("value").AsString()
   609  		resp.PlannedState = req.ProposedNewState
   610  		return
   611  	}
   612  
   613  	args := []string{
   614  		"-var", "foo=bar",
   615  	}
   616  	code := c.Run(args)
   617  	output := done(t)
   618  	if code != 0 {
   619  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   620  	}
   621  
   622  	if actual != "bar" {
   623  		t.Fatal("didn't work")
   624  	}
   625  }
   626  
   627  func TestPlan_varsInvalid(t *testing.T) {
   628  	testCases := []struct {
   629  		args    []string
   630  		wantErr string
   631  	}{
   632  		{
   633  			[]string{"-var", "foo"},
   634  			`The given -var option "foo" is not correctly specified.`,
   635  		},
   636  		{
   637  			[]string{"-var", "foo = bar"},
   638  			`Variable name "foo " is invalid due to trailing space.`,
   639  		},
   640  	}
   641  
   642  	// Create a temporary working directory that is empty
   643  	td := t.TempDir()
   644  	testCopyDir(t, testFixturePath("plan-vars"), td)
   645  	defer testChdir(t, td)()
   646  
   647  	for _, tc := range testCases {
   648  		t.Run(strings.Join(tc.args, " "), func(t *testing.T) {
   649  			p := planVarsFixtureProvider()
   650  			view, done := testView(t)
   651  			c := &PlanCommand{
   652  				Meta: Meta{
   653  					testingOverrides: metaOverridesForProvider(p),
   654  					View:             view,
   655  				},
   656  			}
   657  
   658  			code := c.Run(tc.args)
   659  			output := done(t)
   660  			if code != 1 {
   661  				t.Fatalf("bad: %d\n\n%s", code, output.Stdout())
   662  			}
   663  
   664  			got := output.Stderr()
   665  			if !strings.Contains(got, tc.wantErr) {
   666  				t.Fatalf("bad error output, want %q, got:\n%s", tc.wantErr, got)
   667  			}
   668  		})
   669  	}
   670  }
   671  
   672  func TestPlan_varsUnset(t *testing.T) {
   673  	// Create a temporary working directory that is empty
   674  	td := t.TempDir()
   675  	testCopyDir(t, testFixturePath("plan-vars"), td)
   676  	defer testChdir(t, td)()
   677  
   678  	// The plan command will prompt for interactive input of var.foo.
   679  	// We'll answer "bar" to that prompt, which should then allow this
   680  	// configuration to apply even though var.foo doesn't have a
   681  	// default value and there are no -var arguments on our command line.
   682  
   683  	// This will (helpfully) panic if more than one variable is requested during plan:
   684  	// https://github.com/pulumi/terraform/issues/26027
   685  	close := testInteractiveInput(t, []string{"bar"})
   686  	defer close()
   687  
   688  	p := planVarsFixtureProvider()
   689  	view, done := testView(t)
   690  	c := &PlanCommand{
   691  		Meta: Meta{
   692  			testingOverrides: metaOverridesForProvider(p),
   693  			View:             view,
   694  		},
   695  	}
   696  
   697  	args := []string{}
   698  	code := c.Run(args)
   699  	output := done(t)
   700  	if code != 0 {
   701  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   702  	}
   703  }
   704  
   705  // This test adds a required argument to the test provider to validate
   706  // processing of user input:
   707  // https://github.com/pulumi/terraform/issues/26035
   708  func TestPlan_providerArgumentUnset(t *testing.T) {
   709  	// Create a temporary working directory that is empty
   710  	td := t.TempDir()
   711  	testCopyDir(t, testFixturePath("plan"), td)
   712  	defer testChdir(t, td)()
   713  
   714  	// Disable test mode so input would be asked
   715  	test = false
   716  	defer func() { test = true }()
   717  
   718  	// The plan command will prompt for interactive input of provider.test.region
   719  	defaultInputReader = bytes.NewBufferString("us-east-1\n")
   720  
   721  	p := planFixtureProvider()
   722  	// override the planFixtureProvider schema to include a required provider argument
   723  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
   724  		Provider: providers.Schema{
   725  			Block: &configschema.Block{
   726  				Attributes: map[string]*configschema.Attribute{
   727  					"region": {Type: cty.String, Required: true},
   728  				},
   729  			},
   730  		},
   731  		ResourceTypes: map[string]providers.Schema{
   732  			"test_instance": {
   733  				Block: &configschema.Block{
   734  					Attributes: map[string]*configschema.Attribute{
   735  						"id":  {Type: cty.String, Optional: true, Computed: true},
   736  						"ami": {Type: cty.String, Optional: true, Computed: true},
   737  					},
   738  					BlockTypes: map[string]*configschema.NestedBlock{
   739  						"network_interface": {
   740  							Nesting: configschema.NestingList,
   741  							Block: configschema.Block{
   742  								Attributes: map[string]*configschema.Attribute{
   743  									"device_index": {Type: cty.String, Optional: true},
   744  									"description":  {Type: cty.String, Optional: true},
   745  								},
   746  							},
   747  						},
   748  					},
   749  				},
   750  			},
   751  		},
   752  		DataSources: map[string]providers.Schema{
   753  			"test_data_source": {
   754  				Block: &configschema.Block{
   755  					Attributes: map[string]*configschema.Attribute{
   756  						"id": {
   757  							Type:     cty.String,
   758  							Required: true,
   759  						},
   760  						"valid": {
   761  							Type:     cty.Bool,
   762  							Computed: true,
   763  						},
   764  					},
   765  				},
   766  			},
   767  		},
   768  	}
   769  	view, done := testView(t)
   770  	c := &PlanCommand{
   771  		Meta: Meta{
   772  			testingOverrides: metaOverridesForProvider(p),
   773  			View:             view,
   774  		},
   775  	}
   776  
   777  	args := []string{}
   778  	code := c.Run(args)
   779  	output := done(t)
   780  	if code != 0 {
   781  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   782  	}
   783  }
   784  
   785  // Test that terraform properly merges provider configuration that's split
   786  // between config files and interactive input variables.
   787  // https://github.com/pulumi/terraform/issues/28956
   788  func TestPlan_providerConfigMerge(t *testing.T) {
   789  	td := t.TempDir()
   790  	testCopyDir(t, testFixturePath("plan-provider-input"), td)
   791  	defer testChdir(t, td)()
   792  
   793  	// Disable test mode so input would be asked
   794  	test = false
   795  	defer func() { test = true }()
   796  
   797  	// The plan command will prompt for interactive input of provider.test.region
   798  	defaultInputReader = bytes.NewBufferString("us-east-1\n")
   799  
   800  	p := planFixtureProvider()
   801  	// override the planFixtureProvider schema to include a required provider argument and a nested block
   802  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
   803  		Provider: providers.Schema{
   804  			Block: &configschema.Block{
   805  				Attributes: map[string]*configschema.Attribute{
   806  					"region": {Type: cty.String, Required: true},
   807  					"url":    {Type: cty.String, Required: true},
   808  				},
   809  				BlockTypes: map[string]*configschema.NestedBlock{
   810  					"auth": {
   811  						Nesting: configschema.NestingList,
   812  						Block: configschema.Block{
   813  							Attributes: map[string]*configschema.Attribute{
   814  								"user":     {Type: cty.String, Required: true},
   815  								"password": {Type: cty.String, Required: true},
   816  							},
   817  						},
   818  					},
   819  				},
   820  			},
   821  		},
   822  		ResourceTypes: map[string]providers.Schema{
   823  			"test_instance": {
   824  				Block: &configschema.Block{
   825  					Attributes: map[string]*configschema.Attribute{
   826  						"id": {Type: cty.String, Optional: true, Computed: true},
   827  					},
   828  				},
   829  			},
   830  		},
   831  	}
   832  
   833  	view, done := testView(t)
   834  	c := &PlanCommand{
   835  		Meta: Meta{
   836  			testingOverrides: metaOverridesForProvider(p),
   837  			View:             view,
   838  		},
   839  	}
   840  
   841  	args := []string{}
   842  	code := c.Run(args)
   843  	output := done(t)
   844  	if code != 0 {
   845  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   846  	}
   847  
   848  	if !p.ConfigureProviderCalled {
   849  		t.Fatal("configure provider not called")
   850  	}
   851  
   852  	// For this test, we want to confirm that we've sent the expected config
   853  	// value *to* the provider.
   854  	got := p.ConfigureProviderRequest.Config
   855  	want := cty.ObjectVal(map[string]cty.Value{
   856  		"auth": cty.ListVal([]cty.Value{
   857  			cty.ObjectVal(map[string]cty.Value{
   858  				"user":     cty.StringVal("one"),
   859  				"password": cty.StringVal("onepw"),
   860  			}),
   861  			cty.ObjectVal(map[string]cty.Value{
   862  				"user":     cty.StringVal("two"),
   863  				"password": cty.StringVal("twopw"),
   864  			}),
   865  		}),
   866  		"region": cty.StringVal("us-east-1"),
   867  		"url":    cty.StringVal("example.com"),
   868  	})
   869  
   870  	if !got.RawEquals(want) {
   871  		t.Fatal("wrong provider config")
   872  	}
   873  
   874  }
   875  
   876  func TestPlan_varFile(t *testing.T) {
   877  	// Create a temporary working directory that is empty
   878  	td := t.TempDir()
   879  	testCopyDir(t, testFixturePath("plan-vars"), td)
   880  	defer testChdir(t, td)()
   881  
   882  	varFilePath := testTempFile(t)
   883  	if err := ioutil.WriteFile(varFilePath, []byte(planVarFile), 0644); err != nil {
   884  		t.Fatalf("err: %s", err)
   885  	}
   886  
   887  	p := planVarsFixtureProvider()
   888  	view, done := testView(t)
   889  	c := &PlanCommand{
   890  		Meta: Meta{
   891  			testingOverrides: metaOverridesForProvider(p),
   892  			View:             view,
   893  		},
   894  	}
   895  
   896  	actual := ""
   897  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
   898  		actual = req.ProposedNewState.GetAttr("value").AsString()
   899  		resp.PlannedState = req.ProposedNewState
   900  		return
   901  	}
   902  
   903  	args := []string{
   904  		"-var-file", varFilePath,
   905  	}
   906  	code := c.Run(args)
   907  	output := done(t)
   908  	if code != 0 {
   909  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   910  	}
   911  
   912  	if actual != "bar" {
   913  		t.Fatal("didn't work")
   914  	}
   915  }
   916  
   917  func TestPlan_varFileDefault(t *testing.T) {
   918  	// Create a temporary working directory that is empty
   919  	td := t.TempDir()
   920  	testCopyDir(t, testFixturePath("plan-vars"), td)
   921  	defer testChdir(t, td)()
   922  
   923  	varFilePath := filepath.Join(td, "terraform.tfvars")
   924  	if err := ioutil.WriteFile(varFilePath, []byte(planVarFile), 0644); err != nil {
   925  		t.Fatalf("err: %s", err)
   926  	}
   927  
   928  	p := planVarsFixtureProvider()
   929  	view, done := testView(t)
   930  	c := &PlanCommand{
   931  		Meta: Meta{
   932  			testingOverrides: metaOverridesForProvider(p),
   933  			View:             view,
   934  		},
   935  	}
   936  
   937  	actual := ""
   938  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
   939  		actual = req.ProposedNewState.GetAttr("value").AsString()
   940  		resp.PlannedState = req.ProposedNewState
   941  		return
   942  	}
   943  
   944  	args := []string{}
   945  	code := c.Run(args)
   946  	output := done(t)
   947  	if code != 0 {
   948  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   949  	}
   950  
   951  	if actual != "bar" {
   952  		t.Fatal("didn't work")
   953  	}
   954  }
   955  
   956  func TestPlan_varFileWithDecls(t *testing.T) {
   957  	// Create a temporary working directory that is empty
   958  	td := t.TempDir()
   959  	testCopyDir(t, testFixturePath("plan-vars"), td)
   960  	defer testChdir(t, td)()
   961  
   962  	varFilePath := testTempFile(t)
   963  	if err := ioutil.WriteFile(varFilePath, []byte(planVarFileWithDecl), 0644); err != nil {
   964  		t.Fatalf("err: %s", err)
   965  	}
   966  
   967  	p := planVarsFixtureProvider()
   968  	view, done := testView(t)
   969  	c := &PlanCommand{
   970  		Meta: Meta{
   971  			testingOverrides: metaOverridesForProvider(p),
   972  			View:             view,
   973  		},
   974  	}
   975  
   976  	args := []string{
   977  		"-var-file", varFilePath,
   978  	}
   979  	code := c.Run(args)
   980  	output := done(t)
   981  	if code == 0 {
   982  		t.Fatalf("succeeded; want failure\n\n%s", output.Stdout())
   983  	}
   984  
   985  	msg := output.Stderr()
   986  	if got, want := msg, "Variable declaration in .tfvars file"; !strings.Contains(got, want) {
   987  		t.Fatalf("missing expected error message\nwant message containing %q\ngot:\n%s", want, got)
   988  	}
   989  }
   990  
   991  func TestPlan_detailedExitcode(t *testing.T) {
   992  	td := t.TempDir()
   993  	testCopyDir(t, testFixturePath("plan"), td)
   994  	defer testChdir(t, td)()
   995  
   996  	t.Run("return 1", func(t *testing.T) {
   997  		view, done := testView(t)
   998  		c := &PlanCommand{
   999  			Meta: Meta{
  1000  				// Running plan without setting testingOverrides is similar to plan without init
  1001  				View: view,
  1002  			},
  1003  		}
  1004  		code := c.Run([]string{"-detailed-exitcode"})
  1005  		output := done(t)
  1006  		if code != 1 {
  1007  			t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
  1008  		}
  1009  	})
  1010  
  1011  	t.Run("return 2", func(t *testing.T) {
  1012  		p := planFixtureProvider()
  1013  		view, done := testView(t)
  1014  		c := &PlanCommand{
  1015  			Meta: Meta{
  1016  				testingOverrides: metaOverridesForProvider(p),
  1017  				View:             view,
  1018  			},
  1019  		}
  1020  
  1021  		code := c.Run([]string{"-detailed-exitcode"})
  1022  		output := done(t)
  1023  		if code != 2 {
  1024  			t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
  1025  		}
  1026  	})
  1027  }
  1028  
  1029  func TestPlan_detailedExitcode_emptyDiff(t *testing.T) {
  1030  	td := t.TempDir()
  1031  	testCopyDir(t, testFixturePath("plan-emptydiff"), td)
  1032  	defer testChdir(t, td)()
  1033  
  1034  	p := testProvider()
  1035  	view, done := testView(t)
  1036  	c := &PlanCommand{
  1037  		Meta: Meta{
  1038  			testingOverrides: metaOverridesForProvider(p),
  1039  			View:             view,
  1040  		},
  1041  	}
  1042  
  1043  	args := []string{"-detailed-exitcode"}
  1044  	code := c.Run(args)
  1045  	output := done(t)
  1046  	if code != 0 {
  1047  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
  1048  	}
  1049  }
  1050  
  1051  func TestPlan_shutdown(t *testing.T) {
  1052  	// Create a temporary working directory that is empty
  1053  	td := t.TempDir()
  1054  	testCopyDir(t, testFixturePath("apply-shutdown"), td)
  1055  	defer testChdir(t, td)()
  1056  
  1057  	cancelled := make(chan struct{})
  1058  	shutdownCh := make(chan struct{})
  1059  
  1060  	p := testProvider()
  1061  	view, done := testView(t)
  1062  	c := &PlanCommand{
  1063  		Meta: Meta{
  1064  			testingOverrides: metaOverridesForProvider(p),
  1065  			View:             view,
  1066  			ShutdownCh:       shutdownCh,
  1067  		},
  1068  	}
  1069  
  1070  	p.StopFn = func() error {
  1071  		close(cancelled)
  1072  		return nil
  1073  	}
  1074  
  1075  	var once sync.Once
  1076  
  1077  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
  1078  		once.Do(func() {
  1079  			shutdownCh <- struct{}{}
  1080  		})
  1081  
  1082  		// Because of the internal lock in the MockProvider, we can't
  1083  		// coordinate directly with the calling of Stop, and making the
  1084  		// MockProvider concurrent is disruptive to a lot of existing tests.
  1085  		// Wait here a moment to help make sure the main goroutine gets to the
  1086  		// Stop call before we exit, or the plan may finish before it can be
  1087  		// canceled.
  1088  		time.Sleep(200 * time.Millisecond)
  1089  
  1090  		s := req.ProposedNewState.AsValueMap()
  1091  		s["ami"] = cty.StringVal("bar")
  1092  		resp.PlannedState = cty.ObjectVal(s)
  1093  		return
  1094  	}
  1095  
  1096  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
  1097  		ResourceTypes: map[string]providers.Schema{
  1098  			"test_instance": {
  1099  				Block: &configschema.Block{
  1100  					Attributes: map[string]*configschema.Attribute{
  1101  						"ami": {Type: cty.String, Optional: true},
  1102  					},
  1103  				},
  1104  			},
  1105  		},
  1106  	}
  1107  
  1108  	code := c.Run([]string{})
  1109  	output := done(t)
  1110  	if code != 1 {
  1111  		t.Errorf("wrong exit code %d; want 1\noutput:\n%s", code, output.Stdout())
  1112  	}
  1113  
  1114  	select {
  1115  	case <-cancelled:
  1116  	default:
  1117  		t.Error("command not cancelled")
  1118  	}
  1119  }
  1120  
  1121  func TestPlan_init_required(t *testing.T) {
  1122  	td := t.TempDir()
  1123  	testCopyDir(t, testFixturePath("plan"), td)
  1124  	defer testChdir(t, td)()
  1125  
  1126  	view, done := testView(t)
  1127  	c := &PlanCommand{
  1128  		Meta: Meta{
  1129  			// Running plan without setting testingOverrides is similar to plan without init
  1130  			View: view,
  1131  		},
  1132  	}
  1133  
  1134  	args := []string{"-no-color"}
  1135  	code := c.Run(args)
  1136  	output := done(t)
  1137  	if code != 1 {
  1138  		t.Fatalf("expected error, got success")
  1139  	}
  1140  	got := output.Stderr()
  1141  	if !(strings.Contains(got, "terraform init") && strings.Contains(got, "provider registry.terraform.io/hashicorp/test: required by this configuration but no version is selected")) {
  1142  		t.Fatal("wrong error message in output:", got)
  1143  	}
  1144  }
  1145  
  1146  // Config with multiple resources, targeting plan of a subset
  1147  func TestPlan_targeted(t *testing.T) {
  1148  	td := t.TempDir()
  1149  	testCopyDir(t, testFixturePath("apply-targeted"), td)
  1150  	defer testChdir(t, td)()
  1151  
  1152  	p := testProvider()
  1153  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
  1154  		ResourceTypes: map[string]providers.Schema{
  1155  			"test_instance": {
  1156  				Block: &configschema.Block{
  1157  					Attributes: map[string]*configschema.Attribute{
  1158  						"id": {Type: cty.String, Computed: true},
  1159  					},
  1160  				},
  1161  			},
  1162  		},
  1163  	}
  1164  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
  1165  		return providers.PlanResourceChangeResponse{
  1166  			PlannedState: req.ProposedNewState,
  1167  		}
  1168  	}
  1169  
  1170  	view, done := testView(t)
  1171  	c := &PlanCommand{
  1172  		Meta: Meta{
  1173  			testingOverrides: metaOverridesForProvider(p),
  1174  			View:             view,
  1175  		},
  1176  	}
  1177  
  1178  	args := []string{
  1179  		"-target", "test_instance.foo",
  1180  		"-target", "test_instance.baz",
  1181  	}
  1182  	code := c.Run(args)
  1183  	output := done(t)
  1184  	if code != 0 {
  1185  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
  1186  	}
  1187  
  1188  	if got, want := output.Stdout(), "3 to add, 0 to change, 0 to destroy"; !strings.Contains(got, want) {
  1189  		t.Fatalf("bad change summary, want %q, got:\n%s", want, got)
  1190  	}
  1191  }
  1192  
  1193  // Diagnostics for invalid -target flags
  1194  func TestPlan_targetFlagsDiags(t *testing.T) {
  1195  	testCases := map[string]string{
  1196  		"test_instance.": "Dot must be followed by attribute name.",
  1197  		"test_instance":  "Resource specification must include a resource type and name.",
  1198  	}
  1199  
  1200  	for target, wantDiag := range testCases {
  1201  		t.Run(target, func(t *testing.T) {
  1202  			td := testTempDir(t)
  1203  			defer os.RemoveAll(td)
  1204  			defer testChdir(t, td)()
  1205  
  1206  			view, done := testView(t)
  1207  			c := &PlanCommand{
  1208  				Meta: Meta{
  1209  					View: view,
  1210  				},
  1211  			}
  1212  
  1213  			args := []string{
  1214  				"-target", target,
  1215  			}
  1216  			code := c.Run(args)
  1217  			output := done(t)
  1218  			if code != 1 {
  1219  				t.Fatalf("bad: %d\n\n%s", code, output.Stdout())
  1220  			}
  1221  
  1222  			got := output.Stderr()
  1223  			if !strings.Contains(got, target) {
  1224  				t.Fatalf("bad error output, want %q, got:\n%s", target, got)
  1225  			}
  1226  			if !strings.Contains(got, wantDiag) {
  1227  				t.Fatalf("bad error output, want %q, got:\n%s", wantDiag, got)
  1228  			}
  1229  		})
  1230  	}
  1231  }
  1232  
  1233  func TestPlan_replace(t *testing.T) {
  1234  	td := t.TempDir()
  1235  	testCopyDir(t, testFixturePath("plan-replace"), td)
  1236  	defer testChdir(t, td)()
  1237  
  1238  	originalState := states.BuildState(func(s *states.SyncState) {
  1239  		s.SetResourceInstanceCurrent(
  1240  			addrs.Resource{
  1241  				Mode: addrs.ManagedResourceMode,
  1242  				Type: "test_instance",
  1243  				Name: "a",
  1244  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
  1245  			&states.ResourceInstanceObjectSrc{
  1246  				AttrsJSON: []byte(`{"id":"hello"}`),
  1247  				Status:    states.ObjectReady,
  1248  			},
  1249  			addrs.AbsProviderConfig{
  1250  				Provider: addrs.NewDefaultProvider("test"),
  1251  				Module:   addrs.RootModule,
  1252  			},
  1253  		)
  1254  	})
  1255  	statePath := testStateFile(t, originalState)
  1256  
  1257  	p := testProvider()
  1258  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
  1259  		ResourceTypes: map[string]providers.Schema{
  1260  			"test_instance": {
  1261  				Block: &configschema.Block{
  1262  					Attributes: map[string]*configschema.Attribute{
  1263  						"id": {Type: cty.String, Computed: true},
  1264  					},
  1265  				},
  1266  			},
  1267  		},
  1268  	}
  1269  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
  1270  		return providers.PlanResourceChangeResponse{
  1271  			PlannedState: req.ProposedNewState,
  1272  		}
  1273  	}
  1274  
  1275  	view, done := testView(t)
  1276  	c := &PlanCommand{
  1277  		Meta: Meta{
  1278  			testingOverrides: metaOverridesForProvider(p),
  1279  			View:             view,
  1280  		},
  1281  	}
  1282  
  1283  	args := []string{
  1284  		"-state", statePath,
  1285  		"-no-color",
  1286  		"-replace", "test_instance.a",
  1287  	}
  1288  	code := c.Run(args)
  1289  	output := done(t)
  1290  	if code != 0 {
  1291  		t.Fatalf("wrong exit code %d\n\n%s", code, output.Stderr())
  1292  	}
  1293  
  1294  	stdout := output.Stdout()
  1295  	if got, want := stdout, "1 to add, 0 to change, 1 to destroy"; !strings.Contains(got, want) {
  1296  		t.Errorf("wrong plan summary\ngot output:\n%s\n\nwant substring: %s", got, want)
  1297  	}
  1298  	if got, want := stdout, "test_instance.a will be replaced, as requested"; !strings.Contains(got, want) {
  1299  		t.Errorf("missing replace explanation\ngot output:\n%s\n\nwant substring: %s", got, want)
  1300  	}
  1301  }
  1302  
  1303  // Verify that the parallelism flag allows no more than the desired number of
  1304  // concurrent calls to PlanResourceChange.
  1305  func TestPlan_parallelism(t *testing.T) {
  1306  	// Create a temporary working directory that is empty
  1307  	td := t.TempDir()
  1308  	testCopyDir(t, testFixturePath("parallelism"), td)
  1309  	defer testChdir(t, td)()
  1310  
  1311  	par := 4
  1312  
  1313  	// started is a semaphore that we use to ensure that we never have more
  1314  	// than "par" plan operations happening concurrently
  1315  	started := make(chan struct{}, par)
  1316  
  1317  	// beginCtx is used as a starting gate to hold back PlanResourceChange
  1318  	// calls until we reach the desired concurrency. The cancel func "begin" is
  1319  	// called once we reach the desired concurrency, allowing all apply calls
  1320  	// to proceed in unison.
  1321  	beginCtx, begin := context.WithCancel(context.Background())
  1322  
  1323  	// Since our mock provider has its own mutex preventing concurrent calls
  1324  	// to ApplyResourceChange, we need to use a number of separate providers
  1325  	// here. They will all have the same mock implementation function assigned
  1326  	// but crucially they will each have their own mutex.
  1327  	providerFactories := map[addrs.Provider]providers.Factory{}
  1328  	for i := 0; i < 10; i++ {
  1329  		name := fmt.Sprintf("test%d", i)
  1330  		provider := &terraform.MockProvider{}
  1331  		provider.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
  1332  			ResourceTypes: map[string]providers.Schema{
  1333  				name + "_instance": {Block: &configschema.Block{}},
  1334  			},
  1335  		}
  1336  		provider.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
  1337  			// If we ever have more than our intended parallelism number of
  1338  			// plan operations running concurrently, the semaphore will fail.
  1339  			select {
  1340  			case started <- struct{}{}:
  1341  				defer func() {
  1342  					<-started
  1343  				}()
  1344  			default:
  1345  				t.Fatal("too many concurrent apply operations")
  1346  			}
  1347  
  1348  			// If we never reach our intended parallelism, the context will
  1349  			// never be canceled and the test will time out.
  1350  			if len(started) >= par {
  1351  				begin()
  1352  			}
  1353  			<-beginCtx.Done()
  1354  
  1355  			// do some "work"
  1356  			// Not required for correctness, but makes it easier to spot a
  1357  			// failure when there is more overlap.
  1358  			time.Sleep(10 * time.Millisecond)
  1359  			return providers.PlanResourceChangeResponse{
  1360  				PlannedState: req.ProposedNewState,
  1361  			}
  1362  		}
  1363  		providerFactories[addrs.NewDefaultProvider(name)] = providers.FactoryFixed(provider)
  1364  	}
  1365  	testingOverrides := &testingOverrides{
  1366  		Providers: providerFactories,
  1367  	}
  1368  
  1369  	view, done := testView(t)
  1370  	c := &PlanCommand{
  1371  		Meta: Meta{
  1372  			testingOverrides: testingOverrides,
  1373  			View:             view,
  1374  		},
  1375  	}
  1376  
  1377  	args := []string{
  1378  		fmt.Sprintf("-parallelism=%d", par),
  1379  	}
  1380  
  1381  	res := c.Run(args)
  1382  	output := done(t)
  1383  	if res != 0 {
  1384  		t.Fatal(output.Stdout())
  1385  	}
  1386  }
  1387  
  1388  func TestPlan_warnings(t *testing.T) {
  1389  	td := t.TempDir()
  1390  	testCopyDir(t, testFixturePath("plan"), td)
  1391  	defer testChdir(t, td)()
  1392  
  1393  	t.Run("full warnings", func(t *testing.T) {
  1394  		p := planWarningsFixtureProvider()
  1395  		view, done := testView(t)
  1396  		c := &PlanCommand{
  1397  			Meta: Meta{
  1398  				testingOverrides: metaOverridesForProvider(p),
  1399  				View:             view,
  1400  			},
  1401  		}
  1402  		code := c.Run([]string{})
  1403  		output := done(t)
  1404  		if code != 0 {
  1405  			t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
  1406  		}
  1407  		// the output should contain 3 warnings (returned by planWarningsFixtureProvider())
  1408  		wantWarnings := []string{
  1409  			"warning 1",
  1410  			"warning 2",
  1411  			"warning 3",
  1412  		}
  1413  		for _, want := range wantWarnings {
  1414  			if !strings.Contains(output.Stdout(), want) {
  1415  				t.Errorf("missing warning %s", want)
  1416  			}
  1417  		}
  1418  	})
  1419  
  1420  	t.Run("compact warnings", func(t *testing.T) {
  1421  		p := planWarningsFixtureProvider()
  1422  		view, done := testView(t)
  1423  		c := &PlanCommand{
  1424  			Meta: Meta{
  1425  				testingOverrides: metaOverridesForProvider(p),
  1426  				View:             view,
  1427  			},
  1428  		}
  1429  		code := c.Run([]string{"-compact-warnings"})
  1430  		output := done(t)
  1431  		if code != 0 {
  1432  			t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
  1433  		}
  1434  		// the output should contain 3 warnings (returned by planWarningsFixtureProvider())
  1435  		// and the message that plan was run with -compact-warnings
  1436  		wantWarnings := []string{
  1437  			"warning 1",
  1438  			"warning 2",
  1439  			"warning 3",
  1440  			"To see the full warning notes, run Terraform without -compact-warnings.",
  1441  		}
  1442  		for _, want := range wantWarnings {
  1443  			if !strings.Contains(output.Stdout(), want) {
  1444  				t.Errorf("missing warning %s", want)
  1445  			}
  1446  		}
  1447  	})
  1448  }
  1449  
  1450  func TestPlan_jsonGoldenReference(t *testing.T) {
  1451  	// Create a temporary working directory that is empty
  1452  	td := t.TempDir()
  1453  	testCopyDir(t, testFixturePath("plan"), td)
  1454  	defer testChdir(t, td)()
  1455  
  1456  	p := planFixtureProvider()
  1457  	view, done := testView(t)
  1458  	c := &PlanCommand{
  1459  		Meta: Meta{
  1460  			testingOverrides: metaOverridesForProvider(p),
  1461  			View:             view,
  1462  		},
  1463  	}
  1464  
  1465  	args := []string{
  1466  		"-json",
  1467  	}
  1468  	code := c.Run(args)
  1469  	output := done(t)
  1470  	if code != 0 {
  1471  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
  1472  	}
  1473  
  1474  	checkGoldenReference(t, output, "plan")
  1475  }
  1476  
  1477  // planFixtureSchema returns a schema suitable for processing the
  1478  // configuration in testdata/plan . This schema should be
  1479  // assigned to a mock provider named "test".
  1480  func planFixtureSchema() *providers.GetProviderSchemaResponse {
  1481  	return &providers.GetProviderSchemaResponse{
  1482  		ResourceTypes: map[string]providers.Schema{
  1483  			"test_instance": {
  1484  				Block: &configschema.Block{
  1485  					Attributes: map[string]*configschema.Attribute{
  1486  						"id":  {Type: cty.String, Optional: true, Computed: true},
  1487  						"ami": {Type: cty.String, Optional: true},
  1488  					},
  1489  					BlockTypes: map[string]*configschema.NestedBlock{
  1490  						"network_interface": {
  1491  							Nesting: configschema.NestingList,
  1492  							Block: configschema.Block{
  1493  								Attributes: map[string]*configschema.Attribute{
  1494  									"device_index": {Type: cty.String, Optional: true},
  1495  									"description":  {Type: cty.String, Optional: true},
  1496  								},
  1497  							},
  1498  						},
  1499  					},
  1500  				},
  1501  			},
  1502  		},
  1503  		DataSources: map[string]providers.Schema{
  1504  			"test_data_source": {
  1505  				Block: &configschema.Block{
  1506  					Attributes: map[string]*configschema.Attribute{
  1507  						"id": {
  1508  							Type:     cty.String,
  1509  							Required: true,
  1510  						},
  1511  						"valid": {
  1512  							Type:     cty.Bool,
  1513  							Computed: true,
  1514  						},
  1515  					},
  1516  				},
  1517  			},
  1518  		},
  1519  	}
  1520  }
  1521  
  1522  // planFixtureProvider returns a mock provider that is configured for basic
  1523  // operation with the configuration in testdata/plan. This mock has
  1524  // GetSchemaResponse and PlanResourceChangeFn populated, with the plan
  1525  // step just passing through the new object proposed by Terraform Core.
  1526  func planFixtureProvider() *terraform.MockProvider {
  1527  	p := testProvider()
  1528  	p.GetProviderSchemaResponse = planFixtureSchema()
  1529  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
  1530  		return providers.PlanResourceChangeResponse{
  1531  			PlannedState: req.ProposedNewState,
  1532  		}
  1533  	}
  1534  	p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
  1535  		return providers.ReadDataSourceResponse{
  1536  			State: cty.ObjectVal(map[string]cty.Value{
  1537  				"id":    cty.StringVal("zzzzz"),
  1538  				"valid": cty.BoolVal(true),
  1539  			}),
  1540  		}
  1541  	}
  1542  	return p
  1543  }
  1544  
  1545  // planVarsFixtureSchema returns a schema suitable for processing the
  1546  // configuration in testdata/plan-vars . This schema should be
  1547  // assigned to a mock provider named "test".
  1548  func planVarsFixtureSchema() *providers.GetProviderSchemaResponse {
  1549  	return &providers.GetProviderSchemaResponse{
  1550  		ResourceTypes: map[string]providers.Schema{
  1551  			"test_instance": {
  1552  				Block: &configschema.Block{
  1553  					Attributes: map[string]*configschema.Attribute{
  1554  						"id":    {Type: cty.String, Optional: true, Computed: true},
  1555  						"value": {Type: cty.String, Optional: true},
  1556  					},
  1557  				},
  1558  			},
  1559  		},
  1560  	}
  1561  }
  1562  
  1563  // planVarsFixtureProvider returns a mock provider that is configured for basic
  1564  // operation with the configuration in testdata/plan-vars. This mock has
  1565  // GetSchemaResponse and PlanResourceChangeFn populated, with the plan
  1566  // step just passing through the new object proposed by Terraform Core.
  1567  func planVarsFixtureProvider() *terraform.MockProvider {
  1568  	p := testProvider()
  1569  	p.GetProviderSchemaResponse = planVarsFixtureSchema()
  1570  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
  1571  		return providers.PlanResourceChangeResponse{
  1572  			PlannedState: req.ProposedNewState,
  1573  		}
  1574  	}
  1575  	p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
  1576  		return providers.ReadDataSourceResponse{
  1577  			State: cty.ObjectVal(map[string]cty.Value{
  1578  				"id":    cty.StringVal("zzzzz"),
  1579  				"valid": cty.BoolVal(true),
  1580  			}),
  1581  		}
  1582  	}
  1583  	return p
  1584  }
  1585  
  1586  // planFixtureProvider returns a mock provider that is configured for basic
  1587  // operation with the configuration in testdata/plan. This mock has
  1588  // GetSchemaResponse and PlanResourceChangeFn populated, returning 3 warnings.
  1589  func planWarningsFixtureProvider() *terraform.MockProvider {
  1590  	p := testProvider()
  1591  	p.GetProviderSchemaResponse = planFixtureSchema()
  1592  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
  1593  		return providers.PlanResourceChangeResponse{
  1594  			Diagnostics: tfdiags.Diagnostics{
  1595  				tfdiags.SimpleWarning("warning 1"),
  1596  				tfdiags.SimpleWarning("warning 2"),
  1597  				tfdiags.SimpleWarning("warning 3"),
  1598  			},
  1599  			PlannedState: req.ProposedNewState,
  1600  		}
  1601  	}
  1602  	p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
  1603  		return providers.ReadDataSourceResponse{
  1604  			State: cty.ObjectVal(map[string]cty.Value{
  1605  				"id":    cty.StringVal("zzzzz"),
  1606  				"valid": cty.BoolVal(true),
  1607  			}),
  1608  		}
  1609  	}
  1610  	return p
  1611  }
  1612  
  1613  const planVarFile = `
  1614  foo = "bar"
  1615  `
  1616  
  1617  const planVarFileWithDecl = `
  1618  foo = "bar"
  1619  
  1620  variable "nope" {
  1621  }
  1622  `