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