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