github.com/hugorut/terraform@v1.1.3/src/command/init_test.go (about)

     1  package command
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"log"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  	"testing"
    14  
    15  	"github.com/davecgh/go-spew/spew"
    16  	"github.com/google/go-cmp/cmp"
    17  	"github.com/mitchellh/cli"
    18  	"github.com/zclconf/go-cty/cty"
    19  
    20  	"github.com/hashicorp/go-version"
    21  	"github.com/hugorut/terraform/src/addrs"
    22  	"github.com/hugorut/terraform/src/configs"
    23  	"github.com/hugorut/terraform/src/configs/configschema"
    24  	"github.com/hugorut/terraform/src/depsfile"
    25  	"github.com/hugorut/terraform/src/getproviders"
    26  	"github.com/hugorut/terraform/src/providercache"
    27  	"github.com/hugorut/terraform/src/states"
    28  	"github.com/hugorut/terraform/src/states/statefile"
    29  	"github.com/hugorut/terraform/src/states/statemgr"
    30  )
    31  
    32  func TestInit_empty(t *testing.T) {
    33  	// Create a temporary working directory that is empty
    34  	td := tempDir(t)
    35  	os.MkdirAll(td, 0755)
    36  	defer os.RemoveAll(td)
    37  	defer testChdir(t, td)()
    38  
    39  	ui := new(cli.MockUi)
    40  	view, _ := testView(t)
    41  	c := &InitCommand{
    42  		Meta: Meta{
    43  			testingOverrides: metaOverridesForProvider(testProvider()),
    44  			Ui:               ui,
    45  			View:             view,
    46  		},
    47  	}
    48  
    49  	args := []string{}
    50  	if code := c.Run(args); code != 0 {
    51  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
    52  	}
    53  }
    54  
    55  func TestInit_multipleArgs(t *testing.T) {
    56  	// Create a temporary working directory that is empty
    57  	td := tempDir(t)
    58  	os.MkdirAll(td, 0755)
    59  	defer os.RemoveAll(td)
    60  	defer testChdir(t, td)()
    61  
    62  	ui := new(cli.MockUi)
    63  	view, _ := testView(t)
    64  	c := &InitCommand{
    65  		Meta: Meta{
    66  			testingOverrides: metaOverridesForProvider(testProvider()),
    67  			Ui:               ui,
    68  			View:             view,
    69  		},
    70  	}
    71  
    72  	args := []string{
    73  		"bad",
    74  		"bad",
    75  	}
    76  	if code := c.Run(args); code != 1 {
    77  		t.Fatalf("bad: \n%s", ui.OutputWriter.String())
    78  	}
    79  }
    80  
    81  func TestInit_fromModule_cwdDest(t *testing.T) {
    82  	// Create a temporary working directory that is empty
    83  	td := tempDir(t)
    84  	os.MkdirAll(td, os.ModePerm)
    85  	defer os.RemoveAll(td)
    86  	defer testChdir(t, td)()
    87  
    88  	ui := new(cli.MockUi)
    89  	view, _ := testView(t)
    90  	c := &InitCommand{
    91  		Meta: Meta{
    92  			testingOverrides: metaOverridesForProvider(testProvider()),
    93  			Ui:               ui,
    94  			View:             view,
    95  		},
    96  	}
    97  
    98  	args := []string{
    99  		"-from-module=" + testFixturePath("init"),
   100  	}
   101  	if code := c.Run(args); code != 0 {
   102  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
   103  	}
   104  
   105  	if _, err := os.Stat(filepath.Join(td, "hello.tf")); err != nil {
   106  		t.Fatalf("err: %s", err)
   107  	}
   108  }
   109  
   110  // https://github.com/hugorut/terraform/issues/518
   111  func TestInit_fromModule_dstInSrc(t *testing.T) {
   112  	dir := tempDir(t)
   113  	if err := os.MkdirAll(dir, 0755); err != nil {
   114  		t.Fatalf("err: %s", err)
   115  	}
   116  	defer os.RemoveAll(dir)
   117  
   118  	// Change to the temporary directory
   119  	cwd, err := os.Getwd()
   120  	if err != nil {
   121  		t.Fatalf("err: %s", err)
   122  	}
   123  	if err := os.Chdir(dir); err != nil {
   124  		t.Fatalf("err: %s", err)
   125  	}
   126  	defer os.Chdir(cwd)
   127  
   128  	if err := os.Mkdir("foo", os.ModePerm); err != nil {
   129  		t.Fatal(err)
   130  	}
   131  
   132  	if _, err := os.Create("issue518.tf"); err != nil {
   133  		t.Fatalf("err: %s", err)
   134  	}
   135  
   136  	if err := os.Chdir("foo"); err != nil {
   137  		t.Fatalf("err: %s", err)
   138  	}
   139  
   140  	ui := new(cli.MockUi)
   141  	view, _ := testView(t)
   142  	c := &InitCommand{
   143  		Meta: Meta{
   144  			testingOverrides: metaOverridesForProvider(testProvider()),
   145  			Ui:               ui,
   146  			View:             view,
   147  		},
   148  	}
   149  
   150  	args := []string{
   151  		"-from-module=./..",
   152  	}
   153  	if code := c.Run(args); code != 0 {
   154  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
   155  	}
   156  
   157  	if _, err := os.Stat(filepath.Join(dir, "foo", "issue518.tf")); err != nil {
   158  		t.Fatalf("err: %s", err)
   159  	}
   160  }
   161  
   162  func TestInit_get(t *testing.T) {
   163  	// Create a temporary working directory that is empty
   164  	td := tempDir(t)
   165  	testCopyDir(t, testFixturePath("init-get"), td)
   166  	defer os.RemoveAll(td)
   167  	defer testChdir(t, td)()
   168  
   169  	ui := new(cli.MockUi)
   170  	view, _ := testView(t)
   171  	c := &InitCommand{
   172  		Meta: Meta{
   173  			testingOverrides: metaOverridesForProvider(testProvider()),
   174  			Ui:               ui,
   175  			View:             view,
   176  		},
   177  	}
   178  
   179  	args := []string{}
   180  	if code := c.Run(args); code != 0 {
   181  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
   182  	}
   183  
   184  	// Check output
   185  	output := ui.OutputWriter.String()
   186  	if !strings.Contains(output, "foo in foo") {
   187  		t.Fatalf("doesn't look like we installed module 'foo': %s", output)
   188  	}
   189  }
   190  
   191  func TestInit_getUpgradeModules(t *testing.T) {
   192  	// Create a temporary working directory that is empty
   193  	td := tempDir(t)
   194  	testCopyDir(t, testFixturePath("init-get"), td)
   195  	defer os.RemoveAll(td)
   196  	defer testChdir(t, td)()
   197  
   198  	ui := new(cli.MockUi)
   199  	view, _ := testView(t)
   200  	c := &InitCommand{
   201  		Meta: Meta{
   202  			testingOverrides: metaOverridesForProvider(testProvider()),
   203  			Ui:               ui,
   204  			View:             view,
   205  		},
   206  	}
   207  
   208  	args := []string{
   209  		"-get=true",
   210  		"-upgrade",
   211  	}
   212  	if code := c.Run(args); code != 0 {
   213  		t.Fatalf("command did not complete successfully:\n%s", ui.ErrorWriter.String())
   214  	}
   215  
   216  	// Check output
   217  	output := ui.OutputWriter.String()
   218  	if !strings.Contains(output, "Upgrading modules...") {
   219  		t.Fatalf("doesn't look like get upgrade: %s", output)
   220  	}
   221  }
   222  
   223  func TestInit_backend(t *testing.T) {
   224  	// Create a temporary working directory that is empty
   225  	td := tempDir(t)
   226  	testCopyDir(t, testFixturePath("init-backend"), td)
   227  	defer os.RemoveAll(td)
   228  	defer testChdir(t, td)()
   229  
   230  	ui := new(cli.MockUi)
   231  	view, _ := testView(t)
   232  	c := &InitCommand{
   233  		Meta: Meta{
   234  			testingOverrides: metaOverridesForProvider(testProvider()),
   235  			Ui:               ui,
   236  			View:             view,
   237  		},
   238  	}
   239  
   240  	args := []string{}
   241  	if code := c.Run(args); code != 0 {
   242  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
   243  	}
   244  
   245  	if _, err := os.Stat(filepath.Join(DefaultDataDir, DefaultStateFilename)); err != nil {
   246  		t.Fatalf("err: %s", err)
   247  	}
   248  }
   249  
   250  func TestInit_backendUnset(t *testing.T) {
   251  	// Create a temporary working directory that is empty
   252  	td := tempDir(t)
   253  	testCopyDir(t, testFixturePath("init-backend"), td)
   254  	defer os.RemoveAll(td)
   255  	defer testChdir(t, td)()
   256  
   257  	{
   258  		log.Printf("[TRACE] TestInit_backendUnset: beginning first init")
   259  
   260  		ui := cli.NewMockUi()
   261  		view, _ := testView(t)
   262  		c := &InitCommand{
   263  			Meta: Meta{
   264  				testingOverrides: metaOverridesForProvider(testProvider()),
   265  				Ui:               ui,
   266  				View:             view,
   267  			},
   268  		}
   269  
   270  		// Init
   271  		args := []string{}
   272  		if code := c.Run(args); code != 0 {
   273  			t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
   274  		}
   275  		log.Printf("[TRACE] TestInit_backendUnset: first init complete")
   276  		t.Logf("First run output:\n%s", ui.OutputWriter.String())
   277  		t.Logf("First run errors:\n%s", ui.ErrorWriter.String())
   278  
   279  		if _, err := os.Stat(filepath.Join(DefaultDataDir, DefaultStateFilename)); err != nil {
   280  			t.Fatalf("err: %s", err)
   281  		}
   282  	}
   283  
   284  	{
   285  		log.Printf("[TRACE] TestInit_backendUnset: beginning second init")
   286  
   287  		// Unset
   288  		if err := ioutil.WriteFile("main.tf", []byte(""), 0644); err != nil {
   289  			t.Fatalf("err: %s", err)
   290  		}
   291  
   292  		ui := cli.NewMockUi()
   293  		view, _ := testView(t)
   294  		c := &InitCommand{
   295  			Meta: Meta{
   296  				testingOverrides: metaOverridesForProvider(testProvider()),
   297  				Ui:               ui,
   298  				View:             view,
   299  			},
   300  		}
   301  
   302  		args := []string{"-force-copy"}
   303  		if code := c.Run(args); code != 0 {
   304  			t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
   305  		}
   306  		log.Printf("[TRACE] TestInit_backendUnset: second init complete")
   307  		t.Logf("Second run output:\n%s", ui.OutputWriter.String())
   308  		t.Logf("Second run errors:\n%s", ui.ErrorWriter.String())
   309  
   310  		s := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
   311  		if !s.Backend.Empty() {
   312  			t.Fatal("should not have backend config")
   313  		}
   314  	}
   315  }
   316  
   317  func TestInit_backendConfigFile(t *testing.T) {
   318  	// Create a temporary working directory that is empty
   319  	td := tempDir(t)
   320  	testCopyDir(t, testFixturePath("init-backend-config-file"), td)
   321  	defer os.RemoveAll(td)
   322  	defer testChdir(t, td)()
   323  
   324  	t.Run("good-config-file", func(t *testing.T) {
   325  		ui := new(cli.MockUi)
   326  		view, _ := testView(t)
   327  		c := &InitCommand{
   328  			Meta: Meta{
   329  				testingOverrides: metaOverridesForProvider(testProvider()),
   330  				Ui:               ui,
   331  				View:             view,
   332  			},
   333  		}
   334  		args := []string{"-backend-config", "input.config"}
   335  		if code := c.Run(args); code != 0 {
   336  			t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
   337  		}
   338  
   339  		// Read our saved backend config and verify we have our settings
   340  		state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
   341  		if got, want := normalizeJSON(t, state.Backend.ConfigRaw), `{"path":"hello","workspace_dir":null}`; got != want {
   342  			t.Errorf("wrong config\ngot:  %s\nwant: %s", got, want)
   343  		}
   344  	})
   345  
   346  	// the backend config file must not be a full terraform block
   347  	t.Run("full-backend-config-file", func(t *testing.T) {
   348  		ui := new(cli.MockUi)
   349  		view, _ := testView(t)
   350  		c := &InitCommand{
   351  			Meta: Meta{
   352  				testingOverrides: metaOverridesForProvider(testProvider()),
   353  				Ui:               ui,
   354  				View:             view,
   355  			},
   356  		}
   357  		args := []string{"-backend-config", "backend.config"}
   358  		if code := c.Run(args); code != 1 {
   359  			t.Fatalf("expected error, got success\n")
   360  		}
   361  		if !strings.Contains(ui.ErrorWriter.String(), "Unsupported block type") {
   362  			t.Fatalf("wrong error: %s", ui.ErrorWriter)
   363  		}
   364  	})
   365  
   366  	// the backend config file must match the schema for the backend
   367  	t.Run("invalid-config-file", func(t *testing.T) {
   368  		ui := new(cli.MockUi)
   369  		view, _ := testView(t)
   370  		c := &InitCommand{
   371  			Meta: Meta{
   372  				testingOverrides: metaOverridesForProvider(testProvider()),
   373  				Ui:               ui,
   374  				View:             view,
   375  			},
   376  		}
   377  		args := []string{"-backend-config", "invalid.config"}
   378  		if code := c.Run(args); code != 1 {
   379  			t.Fatalf("expected error, got success\n")
   380  		}
   381  		if !strings.Contains(ui.ErrorWriter.String(), "Unsupported argument") {
   382  			t.Fatalf("wrong error: %s", ui.ErrorWriter)
   383  		}
   384  	})
   385  
   386  	// missing file is an error
   387  	t.Run("missing-config-file", func(t *testing.T) {
   388  		ui := new(cli.MockUi)
   389  		view, _ := testView(t)
   390  		c := &InitCommand{
   391  			Meta: Meta{
   392  				testingOverrides: metaOverridesForProvider(testProvider()),
   393  				Ui:               ui,
   394  				View:             view,
   395  			},
   396  		}
   397  		args := []string{"-backend-config", "missing.config"}
   398  		if code := c.Run(args); code != 1 {
   399  			t.Fatalf("expected error, got success\n")
   400  		}
   401  		if !strings.Contains(ui.ErrorWriter.String(), "Failed to read file") {
   402  			t.Fatalf("wrong error: %s", ui.ErrorWriter)
   403  		}
   404  	})
   405  
   406  	// blank filename clears the backend config
   407  	t.Run("blank-config-file", func(t *testing.T) {
   408  		ui := new(cli.MockUi)
   409  		view, _ := testView(t)
   410  		c := &InitCommand{
   411  			Meta: Meta{
   412  				testingOverrides: metaOverridesForProvider(testProvider()),
   413  				Ui:               ui,
   414  				View:             view,
   415  			},
   416  		}
   417  		args := []string{"-backend-config=", "-migrate-state"}
   418  		if code := c.Run(args); code != 0 {
   419  			t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
   420  		}
   421  
   422  		// Read our saved backend config and verify the backend config is empty
   423  		state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
   424  		if got, want := normalizeJSON(t, state.Backend.ConfigRaw), `{"path":null,"workspace_dir":null}`; got != want {
   425  			t.Errorf("wrong config\ngot:  %s\nwant: %s", got, want)
   426  		}
   427  	})
   428  
   429  	// simulate the local backend having a required field which is not
   430  	// specified in the override file
   431  	t.Run("required-argument", func(t *testing.T) {
   432  		c := &InitCommand{}
   433  		schema := &configschema.Block{
   434  			Attributes: map[string]*configschema.Attribute{
   435  				"path": {
   436  					Type:     cty.String,
   437  					Optional: true,
   438  				},
   439  				"workspace_dir": {
   440  					Type:     cty.String,
   441  					Required: true,
   442  				},
   443  			},
   444  		}
   445  		flagConfigExtra := newRawFlags("-backend-config")
   446  		flagConfigExtra.Set("input.config")
   447  		_, diags := c.backendConfigOverrideBody(flagConfigExtra, schema)
   448  		if len(diags) != 0 {
   449  			t.Errorf("expected no diags, got: %s", diags.Err())
   450  		}
   451  	})
   452  }
   453  
   454  func TestInit_backendConfigFilePowershellConfusion(t *testing.T) {
   455  	// Create a temporary working directory that is empty
   456  	td := tempDir(t)
   457  	testCopyDir(t, testFixturePath("init-backend-config-file"), td)
   458  	defer os.RemoveAll(td)
   459  	defer testChdir(t, td)()
   460  
   461  	ui := new(cli.MockUi)
   462  	view, _ := testView(t)
   463  	c := &InitCommand{
   464  		Meta: Meta{
   465  			testingOverrides: metaOverridesForProvider(testProvider()),
   466  			Ui:               ui,
   467  			View:             view,
   468  		},
   469  	}
   470  
   471  	// SUBTLE: when using -flag=value with Powershell, unquoted values are
   472  	// broken into separate arguments. This results in the init command
   473  	// interpreting the flags as an empty backend-config setting (which is
   474  	// semantically valid!) followed by a custom configuration path.
   475  	//
   476  	// Adding the "=" here forces this codepath to be checked, and it should
   477  	// result in an early exit with a diagnostic that the provided
   478  	// configuration file is not a diretory.
   479  	args := []string{"-backend-config=", "./input.config"}
   480  	if code := c.Run(args); code != 1 {
   481  		t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
   482  	}
   483  
   484  	output := ui.ErrorWriter.String()
   485  	if got, want := output, `Too many command line arguments`; !strings.Contains(got, want) {
   486  		t.Fatalf("wrong output\ngot:\n%s\n\nwant: message containing %q", got, want)
   487  	}
   488  }
   489  
   490  func TestInit_backendReconfigure(t *testing.T) {
   491  	// Create a temporary working directory that is empty
   492  	td := tempDir(t)
   493  	testCopyDir(t, testFixturePath("init-backend"), td)
   494  	defer os.RemoveAll(td)
   495  	defer testChdir(t, td)()
   496  
   497  	providerSource, close := newMockProviderSource(t, map[string][]string{
   498  		"hashicorp/test": {"1.2.3"},
   499  	})
   500  	defer close()
   501  
   502  	ui := new(cli.MockUi)
   503  	view, _ := testView(t)
   504  	c := &InitCommand{
   505  		Meta: Meta{
   506  			testingOverrides: metaOverridesForProvider(testProvider()),
   507  			ProviderSource:   providerSource,
   508  			Ui:               ui,
   509  			View:             view,
   510  		},
   511  	}
   512  
   513  	// create some state, so the backend has something to migrate.
   514  	f, err := os.Create("foo") // this is the path" in the backend config
   515  	if err != nil {
   516  		t.Fatalf("err: %s", err)
   517  	}
   518  	err = writeStateForTesting(testState(), f)
   519  	f.Close()
   520  	if err != nil {
   521  		t.Fatalf("err: %s", err)
   522  	}
   523  
   524  	args := []string{}
   525  	if code := c.Run(args); code != 0 {
   526  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
   527  	}
   528  
   529  	// now run init again, changing the path.
   530  	// The -reconfigure flag prevents init from migrating
   531  	// Without -reconfigure, the test fails since the backend asks for input on migrating state
   532  	args = []string{"-reconfigure", "-backend-config", "path=changed"}
   533  	if code := c.Run(args); code != 0 {
   534  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
   535  	}
   536  }
   537  
   538  func TestInit_backendConfigFileChange(t *testing.T) {
   539  	// Create a temporary working directory that is empty
   540  	td := tempDir(t)
   541  	testCopyDir(t, testFixturePath("init-backend-config-file-change"), td)
   542  	defer os.RemoveAll(td)
   543  	defer testChdir(t, td)()
   544  
   545  	ui := new(cli.MockUi)
   546  	view, _ := testView(t)
   547  	c := &InitCommand{
   548  		Meta: Meta{
   549  			testingOverrides: metaOverridesForProvider(testProvider()),
   550  			Ui:               ui,
   551  			View:             view,
   552  		},
   553  	}
   554  
   555  	args := []string{"-backend-config", "input.config", "-migrate-state"}
   556  	if code := c.Run(args); code != 0 {
   557  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
   558  	}
   559  
   560  	// Read our saved backend config and verify we have our settings
   561  	state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
   562  	if got, want := normalizeJSON(t, state.Backend.ConfigRaw), `{"path":"hello","workspace_dir":null}`; got != want {
   563  		t.Errorf("wrong config\ngot:  %s\nwant: %s", got, want)
   564  	}
   565  }
   566  
   567  func TestInit_backendMigrateWhileLocked(t *testing.T) {
   568  	// Create a temporary working directory that is empty
   569  	td := tempDir(t)
   570  	testCopyDir(t, testFixturePath("init-backend-migrate-while-locked"), td)
   571  	defer os.RemoveAll(td)
   572  	defer testChdir(t, td)()
   573  
   574  	providerSource, close := newMockProviderSource(t, map[string][]string{
   575  		"hashicorp/test": {"1.2.3"},
   576  	})
   577  	defer close()
   578  
   579  	ui := new(cli.MockUi)
   580  	view, _ := testView(t)
   581  	c := &InitCommand{
   582  		Meta: Meta{
   583  			testingOverrides: metaOverridesForProvider(testProvider()),
   584  			ProviderSource:   providerSource,
   585  			Ui:               ui,
   586  			View:             view,
   587  		},
   588  	}
   589  
   590  	// Create some state, so the backend has something to migrate from
   591  	f, err := os.Create("local-state.tfstate")
   592  	if err != nil {
   593  		t.Fatalf("err: %s", err)
   594  	}
   595  	err = writeStateForTesting(testState(), f)
   596  	f.Close()
   597  	if err != nil {
   598  		t.Fatalf("err: %s", err)
   599  	}
   600  
   601  	// Lock the source state
   602  	unlock, err := testLockState(testDataDir, "local-state.tfstate")
   603  	if err != nil {
   604  		t.Fatal(err)
   605  	}
   606  	defer unlock()
   607  
   608  	// Attempt to migrate
   609  	args := []string{"-backend-config", "input.config", "-migrate-state", "-force-copy"}
   610  	if code := c.Run(args); code == 0 {
   611  		t.Fatalf("expected nonzero exit code: %s", ui.OutputWriter.String())
   612  	}
   613  
   614  	// Disabling locking should work
   615  	args = []string{"-backend-config", "input.config", "-migrate-state", "-force-copy", "-lock=false"}
   616  	if code := c.Run(args); code != 0 {
   617  		t.Fatalf("expected zero exit code, got %d: %s", code, ui.ErrorWriter.String())
   618  	}
   619  }
   620  
   621  func TestInit_backendConfigFileChangeWithExistingState(t *testing.T) {
   622  	// Create a temporary working directory that is empty
   623  	td := tempDir(t)
   624  	testCopyDir(t, testFixturePath("init-backend-config-file-change-migrate-existing"), td)
   625  	defer os.RemoveAll(td)
   626  	defer testChdir(t, td)()
   627  
   628  	ui := new(cli.MockUi)
   629  	c := &InitCommand{
   630  		Meta: Meta{
   631  			testingOverrides: metaOverridesForProvider(testProvider()),
   632  			Ui:               ui,
   633  		},
   634  	}
   635  
   636  	oldState := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
   637  
   638  	// we deliberately do not provide the answer for backend-migrate-copy-to-empty to trigger error
   639  	args := []string{"-migrate-state", "-backend-config", "input.config", "-input=true"}
   640  	if code := c.Run(args); code == 0 {
   641  		t.Fatal("expected error")
   642  	}
   643  
   644  	// Read our backend config and verify new settings are not saved
   645  	state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
   646  	if got, want := normalizeJSON(t, state.Backend.ConfigRaw), `{"path":"local-state.tfstate"}`; got != want {
   647  		t.Errorf("wrong config\ngot:  %s\nwant: %s", got, want)
   648  	}
   649  
   650  	// without changing config, hash should not change
   651  	if oldState.Backend.Hash != state.Backend.Hash {
   652  		t.Errorf("backend hash should not have changed\ngot:  %d\nwant: %d", state.Backend.Hash, oldState.Backend.Hash)
   653  	}
   654  }
   655  
   656  func TestInit_backendConfigKV(t *testing.T) {
   657  	// Create a temporary working directory that is empty
   658  	td := tempDir(t)
   659  	testCopyDir(t, testFixturePath("init-backend-config-kv"), td)
   660  	defer os.RemoveAll(td)
   661  	defer testChdir(t, td)()
   662  
   663  	ui := new(cli.MockUi)
   664  	view, _ := testView(t)
   665  	c := &InitCommand{
   666  		Meta: Meta{
   667  			testingOverrides: metaOverridesForProvider(testProvider()),
   668  			Ui:               ui,
   669  			View:             view,
   670  		},
   671  	}
   672  
   673  	args := []string{"-backend-config", "path=hello"}
   674  	if code := c.Run(args); code != 0 {
   675  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
   676  	}
   677  
   678  	// Read our saved backend config and verify we have our settings
   679  	state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
   680  	if got, want := normalizeJSON(t, state.Backend.ConfigRaw), `{"path":"hello","workspace_dir":null}`; got != want {
   681  		t.Errorf("wrong config\ngot:  %s\nwant: %s", got, want)
   682  	}
   683  }
   684  
   685  func TestInit_backendConfigKVReInit(t *testing.T) {
   686  	// Create a temporary working directory that is empty
   687  	td := tempDir(t)
   688  	testCopyDir(t, testFixturePath("init-backend-config-kv"), td)
   689  	defer os.RemoveAll(td)
   690  	defer testChdir(t, td)()
   691  
   692  	ui := new(cli.MockUi)
   693  	view, _ := testView(t)
   694  	c := &InitCommand{
   695  		Meta: Meta{
   696  			testingOverrides: metaOverridesForProvider(testProvider()),
   697  			Ui:               ui,
   698  			View:             view,
   699  		},
   700  	}
   701  
   702  	args := []string{"-backend-config", "path=test"}
   703  	if code := c.Run(args); code != 0 {
   704  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
   705  	}
   706  
   707  	ui = new(cli.MockUi)
   708  	c = &InitCommand{
   709  		Meta: Meta{
   710  			testingOverrides: metaOverridesForProvider(testProvider()),
   711  			Ui:               ui,
   712  			View:             view,
   713  		},
   714  	}
   715  
   716  	// a second init should require no changes, nor should it change the backend.
   717  	args = []string{"-input=false"}
   718  	if code := c.Run(args); code != 0 {
   719  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
   720  	}
   721  
   722  	// make sure the backend is configured how we expect
   723  	configState := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
   724  	cfg := map[string]interface{}{}
   725  	if err := json.Unmarshal(configState.Backend.ConfigRaw, &cfg); err != nil {
   726  		t.Fatal(err)
   727  	}
   728  	if cfg["path"] != "test" {
   729  		t.Fatalf(`expected backend path="test", got path="%v"`, cfg["path"])
   730  	}
   731  
   732  	// override the -backend-config options by settings
   733  	args = []string{"-input=false", "-backend-config", "", "-migrate-state"}
   734  	if code := c.Run(args); code != 0 {
   735  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
   736  	}
   737  
   738  	// make sure the backend is configured how we expect
   739  	configState = testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
   740  	cfg = map[string]interface{}{}
   741  	if err := json.Unmarshal(configState.Backend.ConfigRaw, &cfg); err != nil {
   742  		t.Fatal(err)
   743  	}
   744  	if cfg["path"] != nil {
   745  		t.Fatalf(`expected backend path="<nil>", got path="%v"`, cfg["path"])
   746  	}
   747  }
   748  
   749  func TestInit_backendConfigKVReInitWithConfigDiff(t *testing.T) {
   750  	// Create a temporary working directory that is empty
   751  	td := tempDir(t)
   752  	testCopyDir(t, testFixturePath("init-backend"), td)
   753  	defer os.RemoveAll(td)
   754  	defer testChdir(t, td)()
   755  
   756  	ui := new(cli.MockUi)
   757  	view, _ := testView(t)
   758  	c := &InitCommand{
   759  		Meta: Meta{
   760  			testingOverrides: metaOverridesForProvider(testProvider()),
   761  			Ui:               ui,
   762  			View:             view,
   763  		},
   764  	}
   765  
   766  	args := []string{"-input=false"}
   767  	if code := c.Run(args); code != 0 {
   768  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
   769  	}
   770  
   771  	ui = new(cli.MockUi)
   772  	c = &InitCommand{
   773  		Meta: Meta{
   774  			testingOverrides: metaOverridesForProvider(testProvider()),
   775  			Ui:               ui,
   776  			View:             view,
   777  		},
   778  	}
   779  
   780  	// a second init with identical config should require no changes, nor
   781  	// should it change the backend.
   782  	args = []string{"-input=false", "-backend-config", "path=foo"}
   783  	if code := c.Run(args); code != 0 {
   784  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
   785  	}
   786  
   787  	// make sure the backend is configured how we expect
   788  	configState := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
   789  	cfg := map[string]interface{}{}
   790  	if err := json.Unmarshal(configState.Backend.ConfigRaw, &cfg); err != nil {
   791  		t.Fatal(err)
   792  	}
   793  	if cfg["path"] != "foo" {
   794  		t.Fatalf(`expected backend path="foo", got path="%v"`, cfg["foo"])
   795  	}
   796  }
   797  
   798  func TestInit_backendCli_no_config_block(t *testing.T) {
   799  	// Create a temporary working directory that is empty
   800  	td := tempDir(t)
   801  	testCopyDir(t, testFixturePath("init"), td)
   802  	defer os.RemoveAll(td)
   803  	defer testChdir(t, td)()
   804  
   805  	ui := new(cli.MockUi)
   806  	view, _ := testView(t)
   807  	c := &InitCommand{
   808  		Meta: Meta{
   809  			testingOverrides: metaOverridesForProvider(testProvider()),
   810  			Ui:               ui,
   811  			View:             view,
   812  		},
   813  	}
   814  
   815  	args := []string{"-backend-config", "path=test"}
   816  	if code := c.Run(args); code != 0 {
   817  		t.Fatalf("got exit status %d; want 0\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
   818  	}
   819  
   820  	errMsg := ui.ErrorWriter.String()
   821  	if !strings.Contains(errMsg, "Warning: Missing backend configuration") {
   822  		t.Fatal("expected missing backend block warning, got", errMsg)
   823  	}
   824  }
   825  
   826  func TestInit_backendReinitWithExtra(t *testing.T) {
   827  	td := tempDir(t)
   828  	testCopyDir(t, testFixturePath("init-backend-empty"), td)
   829  	defer os.RemoveAll(td)
   830  	defer testChdir(t, td)()
   831  
   832  	m := testMetaBackend(t, nil)
   833  	opts := &BackendOpts{
   834  		ConfigOverride: configs.SynthBody("synth", map[string]cty.Value{
   835  			"path": cty.StringVal("hello"),
   836  		}),
   837  		Init: true,
   838  	}
   839  
   840  	_, cHash, err := m.backendConfig(opts)
   841  	if err != nil {
   842  		t.Fatal(err)
   843  	}
   844  
   845  	ui := new(cli.MockUi)
   846  	view, _ := testView(t)
   847  	c := &InitCommand{
   848  		Meta: Meta{
   849  			testingOverrides: metaOverridesForProvider(testProvider()),
   850  			Ui:               ui,
   851  			View:             view,
   852  		},
   853  	}
   854  
   855  	args := []string{"-backend-config", "path=hello"}
   856  	if code := c.Run(args); code != 0 {
   857  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
   858  	}
   859  
   860  	// Read our saved backend config and verify we have our settings
   861  	state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
   862  	if got, want := normalizeJSON(t, state.Backend.ConfigRaw), `{"path":"hello","workspace_dir":null}`; got != want {
   863  		t.Errorf("wrong config\ngot:  %s\nwant: %s", got, want)
   864  	}
   865  
   866  	if state.Backend.Hash != uint64(cHash) {
   867  		t.Fatal("mismatched state and config backend hashes")
   868  	}
   869  
   870  	// init again and make sure nothing changes
   871  	if code := c.Run(args); code != 0 {
   872  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
   873  	}
   874  	state = testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
   875  	if got, want := normalizeJSON(t, state.Backend.ConfigRaw), `{"path":"hello","workspace_dir":null}`; got != want {
   876  		t.Errorf("wrong config\ngot:  %s\nwant: %s", got, want)
   877  	}
   878  	if state.Backend.Hash != uint64(cHash) {
   879  		t.Fatal("mismatched state and config backend hashes")
   880  	}
   881  }
   882  
   883  // move option from config to -backend-config args
   884  func TestInit_backendReinitConfigToExtra(t *testing.T) {
   885  	td := tempDir(t)
   886  	testCopyDir(t, testFixturePath("init-backend"), td)
   887  	defer os.RemoveAll(td)
   888  	defer testChdir(t, td)()
   889  
   890  	ui := new(cli.MockUi)
   891  	view, _ := testView(t)
   892  	c := &InitCommand{
   893  		Meta: Meta{
   894  			testingOverrides: metaOverridesForProvider(testProvider()),
   895  			Ui:               ui,
   896  			View:             view,
   897  		},
   898  	}
   899  
   900  	if code := c.Run([]string{"-input=false"}); code != 0 {
   901  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
   902  	}
   903  
   904  	// Read our saved backend config and verify we have our settings
   905  	state := testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
   906  	if got, want := normalizeJSON(t, state.Backend.ConfigRaw), `{"path":"foo","workspace_dir":null}`; got != want {
   907  		t.Errorf("wrong config\ngot:  %s\nwant: %s", got, want)
   908  	}
   909  
   910  	backendHash := state.Backend.Hash
   911  
   912  	// init again but remove the path option from the config
   913  	cfg := "terraform {\n  backend \"local\" {}\n}\n"
   914  	if err := ioutil.WriteFile("main.tf", []byte(cfg), 0644); err != nil {
   915  		t.Fatal(err)
   916  	}
   917  
   918  	// We need a fresh InitCommand here because the old one now has our configuration
   919  	// file cached inside it, so it won't re-read the modification we just made.
   920  	c = &InitCommand{
   921  		Meta: Meta{
   922  			testingOverrides: metaOverridesForProvider(testProvider()),
   923  			Ui:               ui,
   924  			View:             view,
   925  		},
   926  	}
   927  
   928  	args := []string{"-input=false", "-backend-config=path=foo"}
   929  	if code := c.Run(args); code != 0 {
   930  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
   931  	}
   932  	state = testDataStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
   933  	if got, want := normalizeJSON(t, state.Backend.ConfigRaw), `{"path":"foo","workspace_dir":null}`; got != want {
   934  		t.Errorf("wrong config after moving to arg\ngot:  %s\nwant: %s", got, want)
   935  	}
   936  
   937  	if state.Backend.Hash == backendHash {
   938  		t.Fatal("state.Backend.Hash was not updated")
   939  	}
   940  }
   941  
   942  func TestInit_backendCloudInvalidOptions(t *testing.T) {
   943  	// There are various "terraform init" options that are only for
   944  	// traditional backends and not applicable to Terraform Cloud mode.
   945  	// For those, we want to return an explicit error rather than
   946  	// just silently ignoring them, so that users will be aware that
   947  	// Cloud mode has more of an expected "happy path" than the
   948  	// less-vertically-integrated backends do, and to avoid these
   949  	// unapplicable options becoming compatibility constraints for
   950  	// future evolution of Cloud mode.
   951  
   952  	// We use the same starting fixture for all of these tests, but some
   953  	// of them will customize it a bit as part of their work.
   954  	setupTempDir := func(t *testing.T) func() {
   955  		t.Helper()
   956  		td := tempDir(t)
   957  		testCopyDir(t, testFixturePath("init-cloud-simple"), td)
   958  		unChdir := testChdir(t, td)
   959  		return func() {
   960  			unChdir()
   961  			os.RemoveAll(td)
   962  		}
   963  	}
   964  
   965  	// Some of the tests need a non-empty placeholder state file to work
   966  	// with.
   967  	fakeState := states.BuildState(func(cb *states.SyncState) {
   968  		// Having a root module output value should be enough for this
   969  		// state file to be considered "non-empty" and thus a candidate
   970  		// for migration.
   971  		cb.SetOutputValue(
   972  			addrs.OutputValue{Name: "a"}.Absolute(addrs.RootModuleInstance),
   973  			cty.True,
   974  			false,
   975  		)
   976  	})
   977  	fakeStateFile := &statefile.File{
   978  		Lineage:          "boop",
   979  		Serial:           4,
   980  		TerraformVersion: version.Must(version.NewVersion("1.0.0")),
   981  		State:            fakeState,
   982  	}
   983  	var fakeStateBuf bytes.Buffer
   984  	err := statefile.WriteForTest(fakeStateFile, &fakeStateBuf)
   985  	if err != nil {
   986  		t.Error(err)
   987  	}
   988  	fakeStateBytes := fakeStateBuf.Bytes()
   989  
   990  	t.Run("-backend-config", func(t *testing.T) {
   991  		defer setupTempDir(t)()
   992  
   993  		// We have -backend-config as a pragmatic way to dynamically set
   994  		// certain settings of backends that tend to vary depending on
   995  		// where Terraform is running, such as AWS authentication profiles
   996  		// that are naturally local only to the machine where Terraform is
   997  		// running. Those needs don't apply to Terraform Cloud, because
   998  		// the remote workspace encapsulates all of the details of how
   999  		// operations and state work in that case, and so the Cloud
  1000  		// configuration is only about which workspaces we'll be working
  1001  		// with.
  1002  		ui := cli.NewMockUi()
  1003  		view, _ := testView(t)
  1004  		c := &InitCommand{
  1005  			Meta: Meta{
  1006  				Ui:   ui,
  1007  				View: view,
  1008  			},
  1009  		}
  1010  		args := []string{"-backend-config=anything"}
  1011  		if code := c.Run(args); code == 0 {
  1012  			t.Fatalf("unexpected success\n%s", ui.OutputWriter.String())
  1013  		}
  1014  
  1015  		gotStderr := ui.ErrorWriter.String()
  1016  		wantStderr := `
  1017  Error: Invalid command-line option
  1018  
  1019  The -backend-config=... command line option is only for state backends, and
  1020  is not applicable to Terraform Cloud-based configurations.
  1021  
  1022  To change the set of workspaces associated with this configuration, edit the
  1023  Cloud configuration block in the root module.
  1024  
  1025  `
  1026  		if diff := cmp.Diff(wantStderr, gotStderr); diff != "" {
  1027  			t.Errorf("wrong error output\n%s", diff)
  1028  		}
  1029  	})
  1030  	t.Run("-reconfigure", func(t *testing.T) {
  1031  		defer setupTempDir(t)()
  1032  
  1033  		// The -reconfigure option was originally imagined as a way to force
  1034  		// skipping state migration when migrating between backends, but it
  1035  		// has a historical flaw that it doesn't work properly when the
  1036  		// initial situation is the implicit local backend with a state file
  1037  		// present. The Terraform Cloud migration path has some additional
  1038  		// steps to take care of more details automatically, and so
  1039  		// -reconfigure doesn't really make sense in that context, particularly
  1040  		// with its design bug with the handling of the implicit local backend.
  1041  		ui := cli.NewMockUi()
  1042  		view, _ := testView(t)
  1043  		c := &InitCommand{
  1044  			Meta: Meta{
  1045  				Ui:   ui,
  1046  				View: view,
  1047  			},
  1048  		}
  1049  		args := []string{"-reconfigure"}
  1050  		if code := c.Run(args); code == 0 {
  1051  			t.Fatalf("unexpected success\n%s", ui.OutputWriter.String())
  1052  		}
  1053  
  1054  		gotStderr := ui.ErrorWriter.String()
  1055  		wantStderr := `
  1056  Error: Invalid command-line option
  1057  
  1058  The -reconfigure option is for in-place reconfiguration of state backends
  1059  only, and is not needed when changing Terraform Cloud settings.
  1060  
  1061  When using Terraform Cloud, initialization automatically activates any new
  1062  Cloud configuration settings.
  1063  
  1064  `
  1065  		if diff := cmp.Diff(wantStderr, gotStderr); diff != "" {
  1066  			t.Errorf("wrong error output\n%s", diff)
  1067  		}
  1068  	})
  1069  	t.Run("-reconfigure when migrating in", func(t *testing.T) {
  1070  		defer setupTempDir(t)()
  1071  
  1072  		// We have a slightly different error message for the case where we
  1073  		// seem to be trying to migrate to Terraform Cloud with existing
  1074  		// state or explicit backend already present.
  1075  
  1076  		if err := os.WriteFile("terraform.tfstate", fakeStateBytes, 0644); err != nil {
  1077  			t.Fatal(err)
  1078  		}
  1079  
  1080  		ui := cli.NewMockUi()
  1081  		view, _ := testView(t)
  1082  		c := &InitCommand{
  1083  			Meta: Meta{
  1084  				Ui:   ui,
  1085  				View: view,
  1086  			},
  1087  		}
  1088  		args := []string{"-reconfigure"}
  1089  		if code := c.Run(args); code == 0 {
  1090  			t.Fatalf("unexpected success\n%s", ui.OutputWriter.String())
  1091  		}
  1092  
  1093  		gotStderr := ui.ErrorWriter.String()
  1094  		wantStderr := `
  1095  Error: Invalid command-line option
  1096  
  1097  The -reconfigure option is unsupported when migrating to Terraform Cloud,
  1098  because activating Terraform Cloud involves some additional steps.
  1099  
  1100  `
  1101  		if diff := cmp.Diff(wantStderr, gotStderr); diff != "" {
  1102  			t.Errorf("wrong error output\n%s", diff)
  1103  		}
  1104  	})
  1105  	t.Run("-migrate-state", func(t *testing.T) {
  1106  		defer setupTempDir(t)()
  1107  
  1108  		// In Cloud mode, migrating in or out always proposes migrating state
  1109  		// and changing configuration while staying in cloud mode never migrates
  1110  		// state, so this special option isn't relevant.
  1111  		ui := cli.NewMockUi()
  1112  		view, _ := testView(t)
  1113  		c := &InitCommand{
  1114  			Meta: Meta{
  1115  				Ui:   ui,
  1116  				View: view,
  1117  			},
  1118  		}
  1119  		args := []string{"-migrate-state"}
  1120  		if code := c.Run(args); code == 0 {
  1121  			t.Fatalf("unexpected success\n%s", ui.OutputWriter.String())
  1122  		}
  1123  
  1124  		gotStderr := ui.ErrorWriter.String()
  1125  		wantStderr := `
  1126  Error: Invalid command-line option
  1127  
  1128  The -migrate-state option is for migration between state backends only, and
  1129  is not applicable when using Terraform Cloud.
  1130  
  1131  State storage is handled automatically by Terraform Cloud and so the state
  1132  storage location is not configurable.
  1133  
  1134  `
  1135  		if diff := cmp.Diff(wantStderr, gotStderr); diff != "" {
  1136  			t.Errorf("wrong error output\n%s", diff)
  1137  		}
  1138  	})
  1139  	t.Run("-migrate-state when migrating in", func(t *testing.T) {
  1140  		defer setupTempDir(t)()
  1141  
  1142  		// We have a slightly different error message for the case where we
  1143  		// seem to be trying to migrate to Terraform Cloud with existing
  1144  		// state or explicit backend already present.
  1145  
  1146  		if err := os.WriteFile("terraform.tfstate", fakeStateBytes, 0644); err != nil {
  1147  			t.Fatal(err)
  1148  		}
  1149  
  1150  		ui := cli.NewMockUi()
  1151  		view, _ := testView(t)
  1152  		c := &InitCommand{
  1153  			Meta: Meta{
  1154  				Ui:   ui,
  1155  				View: view,
  1156  			},
  1157  		}
  1158  		args := []string{"-migrate-state"}
  1159  		if code := c.Run(args); code == 0 {
  1160  			t.Fatalf("unexpected success\n%s", ui.OutputWriter.String())
  1161  		}
  1162  
  1163  		gotStderr := ui.ErrorWriter.String()
  1164  		wantStderr := `
  1165  Error: Invalid command-line option
  1166  
  1167  The -migrate-state option is for migration between state backends only, and
  1168  is not applicable when using Terraform Cloud.
  1169  
  1170  Terraform Cloud migration has additional steps, configured by interactive
  1171  prompts.
  1172  
  1173  `
  1174  		if diff := cmp.Diff(wantStderr, gotStderr); diff != "" {
  1175  			t.Errorf("wrong error output\n%s", diff)
  1176  		}
  1177  	})
  1178  	t.Run("-force-copy", func(t *testing.T) {
  1179  		defer setupTempDir(t)()
  1180  
  1181  		// In Cloud mode, migrating in or out always proposes migrating state
  1182  		// and changing configuration while staying in cloud mode never migrates
  1183  		// state, so this special option isn't relevant.
  1184  		ui := cli.NewMockUi()
  1185  		view, _ := testView(t)
  1186  		c := &InitCommand{
  1187  			Meta: Meta{
  1188  				Ui:   ui,
  1189  				View: view,
  1190  			},
  1191  		}
  1192  		args := []string{"-force-copy"}
  1193  		if code := c.Run(args); code == 0 {
  1194  			t.Fatalf("unexpected success\n%s", ui.OutputWriter.String())
  1195  		}
  1196  
  1197  		gotStderr := ui.ErrorWriter.String()
  1198  		wantStderr := `
  1199  Error: Invalid command-line option
  1200  
  1201  The -force-copy option is for migration between state backends only, and is
  1202  not applicable when using Terraform Cloud.
  1203  
  1204  State storage is handled automatically by Terraform Cloud and so the state
  1205  storage location is not configurable.
  1206  
  1207  `
  1208  		if diff := cmp.Diff(wantStderr, gotStderr); diff != "" {
  1209  			t.Errorf("wrong error output\n%s", diff)
  1210  		}
  1211  	})
  1212  	t.Run("-force-copy when migrating in", func(t *testing.T) {
  1213  		defer setupTempDir(t)()
  1214  
  1215  		// We have a slightly different error message for the case where we
  1216  		// seem to be trying to migrate to Terraform Cloud with existing
  1217  		// state or explicit backend already present.
  1218  
  1219  		if err := os.WriteFile("terraform.tfstate", fakeStateBytes, 0644); err != nil {
  1220  			t.Fatal(err)
  1221  		}
  1222  
  1223  		ui := cli.NewMockUi()
  1224  		view, _ := testView(t)
  1225  		c := &InitCommand{
  1226  			Meta: Meta{
  1227  				Ui:   ui,
  1228  				View: view,
  1229  			},
  1230  		}
  1231  		args := []string{"-force-copy"}
  1232  		if code := c.Run(args); code == 0 {
  1233  			t.Fatalf("unexpected success\n%s", ui.OutputWriter.String())
  1234  		}
  1235  
  1236  		gotStderr := ui.ErrorWriter.String()
  1237  		wantStderr := `
  1238  Error: Invalid command-line option
  1239  
  1240  The -force-copy option is for migration between state backends only, and is
  1241  not applicable when using Terraform Cloud.
  1242  
  1243  Terraform Cloud migration has additional steps, configured by interactive
  1244  prompts.
  1245  
  1246  `
  1247  		if diff := cmp.Diff(wantStderr, gotStderr); diff != "" {
  1248  			t.Errorf("wrong error output\n%s", diff)
  1249  		}
  1250  	})
  1251  
  1252  }
  1253  
  1254  // make sure inputFalse stops execution on migrate
  1255  func TestInit_inputFalse(t *testing.T) {
  1256  	td := tempDir(t)
  1257  	testCopyDir(t, testFixturePath("init-backend"), td)
  1258  	defer os.RemoveAll(td)
  1259  	defer testChdir(t, td)()
  1260  
  1261  	ui := new(cli.MockUi)
  1262  	view, _ := testView(t)
  1263  	c := &InitCommand{
  1264  		Meta: Meta{
  1265  			testingOverrides: metaOverridesForProvider(testProvider()),
  1266  			Ui:               ui,
  1267  			View:             view,
  1268  		},
  1269  	}
  1270  
  1271  	args := []string{"-input=false", "-backend-config=path=foo"}
  1272  	if code := c.Run(args); code != 0 {
  1273  		t.Fatalf("bad: \n%s", ui.ErrorWriter)
  1274  	}
  1275  
  1276  	// write different states for foo and bar
  1277  	fooState := states.BuildState(func(s *states.SyncState) {
  1278  		s.SetOutputValue(
  1279  			addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance),
  1280  			cty.StringVal("foo"),
  1281  			false, // not sensitive
  1282  		)
  1283  	})
  1284  	if err := statemgr.NewFilesystem("foo").WriteState(fooState); err != nil {
  1285  		t.Fatal(err)
  1286  	}
  1287  	barState := states.BuildState(func(s *states.SyncState) {
  1288  		s.SetOutputValue(
  1289  			addrs.OutputValue{Name: "bar"}.Absolute(addrs.RootModuleInstance),
  1290  			cty.StringVal("bar"),
  1291  			false, // not sensitive
  1292  		)
  1293  	})
  1294  	if err := statemgr.NewFilesystem("bar").WriteState(barState); err != nil {
  1295  		t.Fatal(err)
  1296  	}
  1297  
  1298  	ui = new(cli.MockUi)
  1299  	c = &InitCommand{
  1300  		Meta: Meta{
  1301  			testingOverrides: metaOverridesForProvider(testProvider()),
  1302  			Ui:               ui,
  1303  			View:             view,
  1304  		},
  1305  	}
  1306  
  1307  	args = []string{"-input=false", "-backend-config=path=bar", "-migrate-state"}
  1308  	if code := c.Run(args); code == 0 {
  1309  		t.Fatal("init should have failed", ui.OutputWriter)
  1310  	}
  1311  
  1312  	errMsg := ui.ErrorWriter.String()
  1313  	if !strings.Contains(errMsg, "interactive input is disabled") {
  1314  		t.Fatal("expected input disabled error, got", errMsg)
  1315  	}
  1316  
  1317  	ui = new(cli.MockUi)
  1318  	c = &InitCommand{
  1319  		Meta: Meta{
  1320  			testingOverrides: metaOverridesForProvider(testProvider()),
  1321  			Ui:               ui,
  1322  			View:             view,
  1323  		},
  1324  	}
  1325  
  1326  	// A missing input=false should abort rather than loop infinitely
  1327  	args = []string{"-backend-config=path=baz"}
  1328  	if code := c.Run(args); code == 0 {
  1329  		t.Fatal("init should have failed", ui.OutputWriter)
  1330  	}
  1331  }
  1332  
  1333  func TestInit_getProvider(t *testing.T) {
  1334  	// Create a temporary working directory that is empty
  1335  	td := tempDir(t)
  1336  	testCopyDir(t, testFixturePath("init-get-providers"), td)
  1337  	defer os.RemoveAll(td)
  1338  	defer testChdir(t, td)()
  1339  
  1340  	overrides := metaOverridesForProvider(testProvider())
  1341  	ui := new(cli.MockUi)
  1342  	view, _ := testView(t)
  1343  	providerSource, close := newMockProviderSource(t, map[string][]string{
  1344  		// looking for an exact version
  1345  		"exact": {"1.2.3"},
  1346  		// config requires >= 2.3.3
  1347  		"greater-than": {"2.3.4", "2.3.3", "2.3.0"},
  1348  		// config specifies
  1349  		"between": {"3.4.5", "2.3.4", "1.2.3"},
  1350  	})
  1351  	defer close()
  1352  	m := Meta{
  1353  		testingOverrides: overrides,
  1354  		Ui:               ui,
  1355  		View:             view,
  1356  		ProviderSource:   providerSource,
  1357  	}
  1358  
  1359  	c := &InitCommand{
  1360  		Meta: m,
  1361  	}
  1362  
  1363  	args := []string{
  1364  		"-backend=false", // should be possible to install plugins without backend init
  1365  	}
  1366  	if code := c.Run(args); code != 0 {
  1367  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
  1368  	}
  1369  
  1370  	// check that we got the providers for our config
  1371  	exactPath := fmt.Sprintf(".terraform/providers/registry.terraform.io/hashicorp/exact/1.2.3/%s", getproviders.CurrentPlatform)
  1372  	if _, err := os.Stat(exactPath); os.IsNotExist(err) {
  1373  		t.Fatal("provider 'exact' not downloaded")
  1374  	}
  1375  	greaterThanPath := fmt.Sprintf(".terraform/providers/registry.terraform.io/hashicorp/greater-than/2.3.4/%s", getproviders.CurrentPlatform)
  1376  	if _, err := os.Stat(greaterThanPath); os.IsNotExist(err) {
  1377  		t.Fatal("provider 'greater-than' not downloaded")
  1378  	}
  1379  	betweenPath := fmt.Sprintf(".terraform/providers/registry.terraform.io/hashicorp/between/2.3.4/%s", getproviders.CurrentPlatform)
  1380  	if _, err := os.Stat(betweenPath); os.IsNotExist(err) {
  1381  		t.Fatal("provider 'between' not downloaded")
  1382  	}
  1383  
  1384  	t.Run("future-state", func(t *testing.T) {
  1385  		// getting providers should fail if a state from a newer version of
  1386  		// terraform exists, since InitCommand.getProviders needs to inspect that
  1387  		// state.
  1388  
  1389  		f, err := os.Create(DefaultStateFilename)
  1390  		if err != nil {
  1391  			t.Fatalf("err: %s", err)
  1392  		}
  1393  		defer f.Close()
  1394  
  1395  		// Construct a mock state file from the far future
  1396  		type FutureState struct {
  1397  			Version          uint                     `json:"version"`
  1398  			Lineage          string                   `json:"lineage"`
  1399  			TerraformVersion string                   `json:"terraform_version"`
  1400  			Outputs          map[string]interface{}   `json:"outputs"`
  1401  			Resources        []map[string]interface{} `json:"resources"`
  1402  		}
  1403  		fs := &FutureState{
  1404  			Version:          999,
  1405  			Lineage:          "123-456-789",
  1406  			TerraformVersion: "999.0.0",
  1407  			Outputs:          make(map[string]interface{}),
  1408  			Resources:        make([]map[string]interface{}, 0),
  1409  		}
  1410  		src, err := json.MarshalIndent(fs, "", "  ")
  1411  		if err != nil {
  1412  			t.Fatalf("failed to marshal future state: %s", err)
  1413  		}
  1414  		src = append(src, '\n')
  1415  		_, err = f.Write(src)
  1416  		if err != nil {
  1417  			t.Fatal(err)
  1418  		}
  1419  
  1420  		ui := new(cli.MockUi)
  1421  		view, _ := testView(t)
  1422  		m.Ui = ui
  1423  		m.View = view
  1424  		c := &InitCommand{
  1425  			Meta: m,
  1426  		}
  1427  
  1428  		if code := c.Run(nil); code == 0 {
  1429  			t.Fatal("expected error, got:", ui.OutputWriter)
  1430  		}
  1431  
  1432  		errMsg := ui.ErrorWriter.String()
  1433  		if !strings.Contains(errMsg, "Unsupported state file format") {
  1434  			t.Fatal("unexpected error:", errMsg)
  1435  		}
  1436  	})
  1437  }
  1438  
  1439  func TestInit_getProviderSource(t *testing.T) {
  1440  	// Create a temporary working directory that is empty
  1441  	td := tempDir(t)
  1442  	testCopyDir(t, testFixturePath("init-get-provider-source"), td)
  1443  	defer os.RemoveAll(td)
  1444  	defer testChdir(t, td)()
  1445  
  1446  	overrides := metaOverridesForProvider(testProvider())
  1447  	ui := new(cli.MockUi)
  1448  	view, _ := testView(t)
  1449  	providerSource, close := newMockProviderSource(t, map[string][]string{
  1450  		// looking for an exact version
  1451  		"acme/alpha": {"1.2.3"},
  1452  		// config doesn't specify versions for other providers
  1453  		"registry.example.com/acme/beta": {"1.0.0"},
  1454  		"gamma":                          {"2.0.0"},
  1455  	})
  1456  	defer close()
  1457  	m := Meta{
  1458  		testingOverrides: overrides,
  1459  		Ui:               ui,
  1460  		View:             view,
  1461  		ProviderSource:   providerSource,
  1462  	}
  1463  
  1464  	c := &InitCommand{
  1465  		Meta: m,
  1466  	}
  1467  
  1468  	args := []string{
  1469  		"-backend=false", // should be possible to install plugins without backend init
  1470  	}
  1471  	if code := c.Run(args); code != 0 {
  1472  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
  1473  	}
  1474  
  1475  	// check that we got the providers for our config
  1476  	exactPath := fmt.Sprintf(".terraform/providers/registry.terraform.io/acme/alpha/1.2.3/%s", getproviders.CurrentPlatform)
  1477  	if _, err := os.Stat(exactPath); os.IsNotExist(err) {
  1478  		t.Error("provider 'alpha' not downloaded")
  1479  	}
  1480  	greaterThanPath := fmt.Sprintf(".terraform/providers/registry.example.com/acme/beta/1.0.0/%s", getproviders.CurrentPlatform)
  1481  	if _, err := os.Stat(greaterThanPath); os.IsNotExist(err) {
  1482  		t.Error("provider 'beta' not downloaded")
  1483  	}
  1484  	betweenPath := fmt.Sprintf(".terraform/providers/registry.terraform.io/hashicorp/gamma/2.0.0/%s", getproviders.CurrentPlatform)
  1485  	if _, err := os.Stat(betweenPath); os.IsNotExist(err) {
  1486  		t.Error("provider 'gamma' not downloaded")
  1487  	}
  1488  }
  1489  
  1490  func TestInit_getProviderLegacyFromState(t *testing.T) {
  1491  	// Create a temporary working directory that is empty
  1492  	td := tempDir(t)
  1493  	testCopyDir(t, testFixturePath("init-get-provider-legacy-from-state"), td)
  1494  	defer os.RemoveAll(td)
  1495  	defer testChdir(t, td)()
  1496  
  1497  	overrides := metaOverridesForProvider(testProvider())
  1498  	ui := new(cli.MockUi)
  1499  	view, _ := testView(t)
  1500  	providerSource, close := newMockProviderSource(t, map[string][]string{
  1501  		"acme/alpha": {"1.2.3"},
  1502  	})
  1503  	defer close()
  1504  	m := Meta{
  1505  		testingOverrides: overrides,
  1506  		Ui:               ui,
  1507  		View:             view,
  1508  		ProviderSource:   providerSource,
  1509  	}
  1510  
  1511  	c := &InitCommand{
  1512  		Meta: m,
  1513  	}
  1514  
  1515  	if code := c.Run(nil); code != 1 {
  1516  		t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
  1517  	}
  1518  
  1519  	// Expect this diagnostic output
  1520  	wants := []string{
  1521  		"Invalid legacy provider address",
  1522  		"You must complete the Terraform 0.13 upgrade process",
  1523  	}
  1524  	got := ui.ErrorWriter.String()
  1525  	for _, want := range wants {
  1526  		if !strings.Contains(got, want) {
  1527  			t.Fatalf("expected output to contain %q, got:\n\n%s", want, got)
  1528  		}
  1529  	}
  1530  }
  1531  
  1532  func TestInit_getProviderInvalidPackage(t *testing.T) {
  1533  	// Create a temporary working directory that is empty
  1534  	td := tempDir(t)
  1535  	testCopyDir(t, testFixturePath("init-get-provider-invalid-package"), td)
  1536  	defer os.RemoveAll(td)
  1537  	defer testChdir(t, td)()
  1538  
  1539  	overrides := metaOverridesForProvider(testProvider())
  1540  	ui := new(cli.MockUi)
  1541  	view, _ := testView(t)
  1542  
  1543  	// create a provider source which allows installing an invalid package
  1544  	addr := addrs.MustParseProviderSourceString("invalid/package")
  1545  	version := getproviders.MustParseVersion("1.0.0")
  1546  	meta, close, err := getproviders.FakeInstallablePackageMeta(
  1547  		addr,
  1548  		version,
  1549  		getproviders.VersionList{getproviders.MustParseVersion("5.0")},
  1550  		getproviders.CurrentPlatform,
  1551  		"terraform-package", // should be "terraform-provider-package"
  1552  	)
  1553  	defer close()
  1554  	if err != nil {
  1555  		t.Fatalf("failed to prepare fake package for %s %s: %s", addr.ForDisplay(), version, err)
  1556  	}
  1557  	providerSource := getproviders.NewMockSource([]getproviders.PackageMeta{meta}, nil)
  1558  
  1559  	m := Meta{
  1560  		testingOverrides: overrides,
  1561  		Ui:               ui,
  1562  		View:             view,
  1563  		ProviderSource:   providerSource,
  1564  	}
  1565  
  1566  	c := &InitCommand{
  1567  		Meta: m,
  1568  	}
  1569  
  1570  	args := []string{
  1571  		"-backend=false", // should be possible to install plugins without backend init
  1572  	}
  1573  	if code := c.Run(args); code != 1 {
  1574  		t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
  1575  	}
  1576  
  1577  	// invalid provider should be installed
  1578  	packagePath := fmt.Sprintf(".terraform/providers/registry.terraform.io/invalid/package/1.0.0/%s/terraform-package", getproviders.CurrentPlatform)
  1579  	if _, err := os.Stat(packagePath); os.IsNotExist(err) {
  1580  		t.Fatal("provider 'invalid/package' not downloaded")
  1581  	}
  1582  
  1583  	wantErrors := []string{
  1584  		"Failed to install provider",
  1585  		"could not find executable file starting with terraform-provider-package",
  1586  	}
  1587  	got := ui.ErrorWriter.String()
  1588  	for _, wantError := range wantErrors {
  1589  		if !strings.Contains(got, wantError) {
  1590  			t.Fatalf("missing error:\nwant: %q\ngot:\n%s", wantError, got)
  1591  		}
  1592  	}
  1593  }
  1594  
  1595  func TestInit_getProviderDetectedLegacy(t *testing.T) {
  1596  	// Create a temporary working directory that is empty
  1597  	td := tempDir(t)
  1598  	testCopyDir(t, testFixturePath("init-get-provider-detected-legacy"), td)
  1599  	defer os.RemoveAll(td)
  1600  	defer testChdir(t, td)()
  1601  
  1602  	// We need to construct a multisource with a mock source and a registry
  1603  	// source: the mock source will return ErrRegistryProviderNotKnown for an
  1604  	// unknown provider, and the registry source will allow us to look up the
  1605  	// appropriate namespace if possible.
  1606  	providerSource, psClose := newMockProviderSource(t, map[string][]string{
  1607  		"hashicorp/foo":           {"1.2.3"},
  1608  		"terraform-providers/baz": {"2.3.4"}, // this will not be installed
  1609  	})
  1610  	defer psClose()
  1611  	registrySource, rsClose := testRegistrySource(t)
  1612  	defer rsClose()
  1613  	multiSource := getproviders.MultiSource{
  1614  		{Source: providerSource},
  1615  		{Source: registrySource},
  1616  	}
  1617  
  1618  	ui := new(cli.MockUi)
  1619  	view, _ := testView(t)
  1620  	m := Meta{
  1621  		Ui:             ui,
  1622  		View:           view,
  1623  		ProviderSource: multiSource,
  1624  	}
  1625  
  1626  	c := &InitCommand{
  1627  		Meta: m,
  1628  	}
  1629  
  1630  	args := []string{
  1631  		"-backend=false", // should be possible to install plugins without backend init
  1632  	}
  1633  	if code := c.Run(args); code == 0 {
  1634  		t.Fatalf("expected error, got output: \n%s", ui.OutputWriter.String())
  1635  	}
  1636  
  1637  	// foo should be installed
  1638  	fooPath := fmt.Sprintf(".terraform/providers/registry.terraform.io/hashicorp/foo/1.2.3/%s", getproviders.CurrentPlatform)
  1639  	if _, err := os.Stat(fooPath); os.IsNotExist(err) {
  1640  		t.Error("provider 'foo' not installed")
  1641  	}
  1642  	// baz should not be installed
  1643  	bazPath := fmt.Sprintf(".terraform/providers/registry.terraform.io/terraform-providers/baz/2.3.4/%s", getproviders.CurrentPlatform)
  1644  	if _, err := os.Stat(bazPath); !os.IsNotExist(err) {
  1645  		t.Error("provider 'baz' installed, but should not be")
  1646  	}
  1647  
  1648  	// error output is the main focus of this test
  1649  	errOutput := ui.ErrorWriter.String()
  1650  	errors := []string{
  1651  		"Failed to query available provider packages",
  1652  		"Could not retrieve the list of available versions",
  1653  		"registry.terraform.io/hashicorp/baz",
  1654  		"registry.terraform.io/hashicorp/frob",
  1655  	}
  1656  	for _, want := range errors {
  1657  		if !strings.Contains(errOutput, want) {
  1658  			t.Fatalf("expected error %q: %s", want, errOutput)
  1659  		}
  1660  	}
  1661  }
  1662  
  1663  func TestInit_providerSource(t *testing.T) {
  1664  	// Create a temporary working directory that is empty
  1665  	td := tempDir(t)
  1666  	testCopyDir(t, testFixturePath("init-required-providers"), td)
  1667  	defer os.RemoveAll(td)
  1668  	defer testChdir(t, td)()
  1669  
  1670  	providerSource, close := newMockProviderSource(t, map[string][]string{
  1671  		"test":      {"1.2.3", "1.2.4"},
  1672  		"test-beta": {"1.2.4"},
  1673  		"source":    {"1.2.2", "1.2.3", "1.2.1"},
  1674  	})
  1675  	defer close()
  1676  
  1677  	ui := new(cli.MockUi)
  1678  	view, _ := testView(t)
  1679  	m := Meta{
  1680  		testingOverrides: metaOverridesForProvider(testProvider()),
  1681  		Ui:               ui,
  1682  		View:             view,
  1683  		ProviderSource:   providerSource,
  1684  	}
  1685  
  1686  	c := &InitCommand{
  1687  		Meta: m,
  1688  	}
  1689  
  1690  	args := []string{}
  1691  
  1692  	if code := c.Run(args); code != 0 {
  1693  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
  1694  	}
  1695  	if strings.Contains(ui.OutputWriter.String(), "Terraform has initialized, but configuration upgrades may be needed") {
  1696  		t.Fatalf("unexpected \"configuration upgrade\" warning in output")
  1697  	}
  1698  
  1699  	cacheDir := m.providerLocalCacheDir()
  1700  	gotPackages := cacheDir.AllAvailablePackages()
  1701  	wantPackages := map[addrs.Provider][]providercache.CachedProvider{
  1702  		addrs.NewDefaultProvider("test"): {
  1703  			{
  1704  				Provider:   addrs.NewDefaultProvider("test"),
  1705  				Version:    getproviders.MustParseVersion("1.2.3"),
  1706  				PackageDir: expectedPackageInstallPath("test", "1.2.3", false),
  1707  			},
  1708  		},
  1709  		addrs.NewDefaultProvider("test-beta"): {
  1710  			{
  1711  				Provider:   addrs.NewDefaultProvider("test-beta"),
  1712  				Version:    getproviders.MustParseVersion("1.2.4"),
  1713  				PackageDir: expectedPackageInstallPath("test-beta", "1.2.4", false),
  1714  			},
  1715  		},
  1716  		addrs.NewDefaultProvider("source"): {
  1717  			{
  1718  				Provider:   addrs.NewDefaultProvider("source"),
  1719  				Version:    getproviders.MustParseVersion("1.2.3"),
  1720  				PackageDir: expectedPackageInstallPath("source", "1.2.3", false),
  1721  			},
  1722  		},
  1723  	}
  1724  	if diff := cmp.Diff(wantPackages, gotPackages); diff != "" {
  1725  		t.Errorf("wrong cache directory contents after upgrade\n%s", diff)
  1726  	}
  1727  
  1728  	locks, err := m.lockedDependencies()
  1729  	if err != nil {
  1730  		t.Fatalf("failed to get locked dependencies: %s", err)
  1731  	}
  1732  	gotProviderLocks := locks.AllProviders()
  1733  	wantProviderLocks := map[addrs.Provider]*depsfile.ProviderLock{
  1734  		addrs.NewDefaultProvider("test-beta"): depsfile.NewProviderLock(
  1735  			addrs.NewDefaultProvider("test-beta"),
  1736  			getproviders.MustParseVersion("1.2.4"),
  1737  			getproviders.MustParseVersionConstraints("= 1.2.4"),
  1738  			[]getproviders.Hash{
  1739  				getproviders.HashScheme1.New("see6W06w09Ea+AobFJ+mbvPTie6ASqZAAdlFZbs8BSM="),
  1740  			},
  1741  		),
  1742  		addrs.NewDefaultProvider("test"): depsfile.NewProviderLock(
  1743  			addrs.NewDefaultProvider("test"),
  1744  			getproviders.MustParseVersion("1.2.3"),
  1745  			getproviders.MustParseVersionConstraints("= 1.2.3"),
  1746  			[]getproviders.Hash{
  1747  				getproviders.HashScheme1.New("wlbEC2mChQZ2hhgUhl6SeVLPP7fMqOFUZAQhQ9GIIno="),
  1748  			},
  1749  		),
  1750  		addrs.NewDefaultProvider("source"): depsfile.NewProviderLock(
  1751  			addrs.NewDefaultProvider("source"),
  1752  			getproviders.MustParseVersion("1.2.3"),
  1753  			getproviders.MustParseVersionConstraints("= 1.2.3"),
  1754  			[]getproviders.Hash{
  1755  				getproviders.HashScheme1.New("myS3qb3px3tRBq1ZWRYJeUH+kySWpBc0Yy8rw6W7/p4="),
  1756  			},
  1757  		),
  1758  	}
  1759  	if diff := cmp.Diff(gotProviderLocks, wantProviderLocks, depsfile.ProviderLockComparer); diff != "" {
  1760  		t.Errorf("wrong version selections after upgrade\n%s", diff)
  1761  	}
  1762  
  1763  	outputStr := ui.OutputWriter.String()
  1764  	if want := "Installed hashicorp/test v1.2.3 (verified checksum)"; !strings.Contains(outputStr, want) {
  1765  		t.Fatalf("unexpected output: %s\nexpected to include %q", outputStr, want)
  1766  	}
  1767  }
  1768  
  1769  func TestInit_cancelModules(t *testing.T) {
  1770  	// This test runs `terraform init` as if SIGINT (or similar on other
  1771  	// platforms) were sent to it, testing that it is interruptible.
  1772  
  1773  	td := tempDir(t)
  1774  	testCopyDir(t, testFixturePath("init-registry-module"), td)
  1775  	defer os.RemoveAll(td)
  1776  	defer testChdir(t, td)()
  1777  
  1778  	// Our shutdown channel is pre-closed so init will exit as soon as it
  1779  	// starts a cancelable portion of the process.
  1780  	shutdownCh := make(chan struct{})
  1781  	close(shutdownCh)
  1782  
  1783  	ui := cli.NewMockUi()
  1784  	view, _ := testView(t)
  1785  	m := Meta{
  1786  		testingOverrides: metaOverridesForProvider(testProvider()),
  1787  		Ui:               ui,
  1788  		View:             view,
  1789  		ShutdownCh:       shutdownCh,
  1790  	}
  1791  
  1792  	c := &InitCommand{
  1793  		Meta: m,
  1794  	}
  1795  
  1796  	args := []string{}
  1797  
  1798  	if code := c.Run(args); code == 0 {
  1799  		t.Fatalf("succeeded; wanted error\n%s", ui.OutputWriter.String())
  1800  	}
  1801  
  1802  	if got, want := ui.ErrorWriter.String(), `Module installation was canceled by an interrupt signal`; !strings.Contains(got, want) {
  1803  		t.Fatalf("wrong error message\nshould contain: %s\ngot:\n%s", want, got)
  1804  	}
  1805  }
  1806  
  1807  func TestInit_cancelProviders(t *testing.T) {
  1808  	// This test runs `terraform init` as if SIGINT (or similar on other
  1809  	// platforms) were sent to it, testing that it is interruptible.
  1810  
  1811  	td := tempDir(t)
  1812  	testCopyDir(t, testFixturePath("init-required-providers"), td)
  1813  	defer os.RemoveAll(td)
  1814  	defer testChdir(t, td)()
  1815  
  1816  	// Use a provider source implementation which is designed to hang indefinitely,
  1817  	// to avoid a race between the closed shutdown channel and the provider source
  1818  	// operations.
  1819  	providerSource := &getproviders.HangingSource{}
  1820  
  1821  	// Our shutdown channel is pre-closed so init will exit as soon as it
  1822  	// starts a cancelable portion of the process.
  1823  	shutdownCh := make(chan struct{})
  1824  	close(shutdownCh)
  1825  
  1826  	ui := cli.NewMockUi()
  1827  	view, _ := testView(t)
  1828  	m := Meta{
  1829  		testingOverrides: metaOverridesForProvider(testProvider()),
  1830  		Ui:               ui,
  1831  		View:             view,
  1832  		ProviderSource:   providerSource,
  1833  		ShutdownCh:       shutdownCh,
  1834  	}
  1835  
  1836  	c := &InitCommand{
  1837  		Meta: m,
  1838  	}
  1839  
  1840  	args := []string{}
  1841  
  1842  	if code := c.Run(args); code == 0 {
  1843  		t.Fatalf("succeeded; wanted error\n%s", ui.OutputWriter.String())
  1844  	}
  1845  	// Currently the first operation that is cancelable is provider
  1846  	// installation, so our error message comes from there. If we
  1847  	// make the earlier steps cancelable in future then it'd be
  1848  	// expected for this particular message to change.
  1849  	if got, want := ui.ErrorWriter.String(), `Provider installation was canceled by an interrupt signal`; !strings.Contains(got, want) {
  1850  		t.Fatalf("wrong error message\nshould contain: %s\ngot:\n%s", want, got)
  1851  	}
  1852  }
  1853  
  1854  func TestInit_getUpgradePlugins(t *testing.T) {
  1855  	// Create a temporary working directory that is empty
  1856  	td := tempDir(t)
  1857  	testCopyDir(t, testFixturePath("init-get-providers"), td)
  1858  	defer os.RemoveAll(td)
  1859  	defer testChdir(t, td)()
  1860  
  1861  	providerSource, close := newMockProviderSource(t, map[string][]string{
  1862  		// looking for an exact version
  1863  		"exact": {"1.2.3"},
  1864  		// config requires >= 2.3.3
  1865  		"greater-than": {"2.3.4", "2.3.3", "2.3.0"},
  1866  		// config specifies > 1.0.0 , < 3.0.0
  1867  		"between": {"3.4.5", "2.3.4", "1.2.3"},
  1868  	})
  1869  	defer close()
  1870  
  1871  	ui := new(cli.MockUi)
  1872  	view, _ := testView(t)
  1873  	m := Meta{
  1874  		testingOverrides: metaOverridesForProvider(testProvider()),
  1875  		Ui:               ui,
  1876  		View:             view,
  1877  		ProviderSource:   providerSource,
  1878  	}
  1879  
  1880  	installFakeProviderPackages(t, &m, map[string][]string{
  1881  		"exact":        {"0.0.1"},
  1882  		"greater-than": {"2.3.3"},
  1883  	})
  1884  
  1885  	c := &InitCommand{
  1886  		Meta: m,
  1887  	}
  1888  
  1889  	args := []string{
  1890  		"-upgrade=true",
  1891  	}
  1892  	if code := c.Run(args); code != 0 {
  1893  		t.Fatalf("command did not complete successfully:\n%s", ui.ErrorWriter.String())
  1894  	}
  1895  
  1896  	cacheDir := m.providerLocalCacheDir()
  1897  	gotPackages := cacheDir.AllAvailablePackages()
  1898  	wantPackages := map[addrs.Provider][]providercache.CachedProvider{
  1899  		// "between" wasn't previously installed at all, so we installed
  1900  		// the newest available version that matched the version constraints.
  1901  		addrs.NewDefaultProvider("between"): {
  1902  			{
  1903  				Provider:   addrs.NewDefaultProvider("between"),
  1904  				Version:    getproviders.MustParseVersion("2.3.4"),
  1905  				PackageDir: expectedPackageInstallPath("between", "2.3.4", false),
  1906  			},
  1907  		},
  1908  		// The existing version of "exact" did not match the version constraints,
  1909  		// so we installed what the configuration selected as well.
  1910  		addrs.NewDefaultProvider("exact"): {
  1911  			{
  1912  				Provider:   addrs.NewDefaultProvider("exact"),
  1913  				Version:    getproviders.MustParseVersion("1.2.3"),
  1914  				PackageDir: expectedPackageInstallPath("exact", "1.2.3", false),
  1915  			},
  1916  			// Previous version is still there, but not selected
  1917  			{
  1918  				Provider:   addrs.NewDefaultProvider("exact"),
  1919  				Version:    getproviders.MustParseVersion("0.0.1"),
  1920  				PackageDir: expectedPackageInstallPath("exact", "0.0.1", false),
  1921  			},
  1922  		},
  1923  		// The existing version of "greater-than" _did_ match the constraints,
  1924  		// but a newer version was available and the user specified
  1925  		// -upgrade and so we upgraded it anyway.
  1926  		addrs.NewDefaultProvider("greater-than"): {
  1927  			{
  1928  				Provider:   addrs.NewDefaultProvider("greater-than"),
  1929  				Version:    getproviders.MustParseVersion("2.3.4"),
  1930  				PackageDir: expectedPackageInstallPath("greater-than", "2.3.4", false),
  1931  			},
  1932  			// Previous version is still there, but not selected
  1933  			{
  1934  				Provider:   addrs.NewDefaultProvider("greater-than"),
  1935  				Version:    getproviders.MustParseVersion("2.3.3"),
  1936  				PackageDir: expectedPackageInstallPath("greater-than", "2.3.3", false),
  1937  			},
  1938  		},
  1939  	}
  1940  	if diff := cmp.Diff(wantPackages, gotPackages); diff != "" {
  1941  		t.Errorf("wrong cache directory contents after upgrade\n%s", diff)
  1942  	}
  1943  
  1944  	locks, err := m.lockedDependencies()
  1945  	if err != nil {
  1946  		t.Fatalf("failed to get locked dependencies: %s", err)
  1947  	}
  1948  	gotProviderLocks := locks.AllProviders()
  1949  	wantProviderLocks := map[addrs.Provider]*depsfile.ProviderLock{
  1950  		addrs.NewDefaultProvider("between"): depsfile.NewProviderLock(
  1951  			addrs.NewDefaultProvider("between"),
  1952  			getproviders.MustParseVersion("2.3.4"),
  1953  			getproviders.MustParseVersionConstraints("> 1.0.0, < 3.0.0"),
  1954  			[]getproviders.Hash{
  1955  				getproviders.HashScheme1.New("JVqAvZz88A+hS2wHVtTWQkHaxoA/LrUAz0H3jPBWPIA="),
  1956  			},
  1957  		),
  1958  		addrs.NewDefaultProvider("exact"): depsfile.NewProviderLock(
  1959  			addrs.NewDefaultProvider("exact"),
  1960  			getproviders.MustParseVersion("1.2.3"),
  1961  			getproviders.MustParseVersionConstraints("= 1.2.3"),
  1962  			[]getproviders.Hash{
  1963  				getproviders.HashScheme1.New("H1TxWF8LyhBb6B4iUdKhLc/S9sC/jdcrCykpkbGcfbg="),
  1964  			},
  1965  		),
  1966  		addrs.NewDefaultProvider("greater-than"): depsfile.NewProviderLock(
  1967  			addrs.NewDefaultProvider("greater-than"),
  1968  			getproviders.MustParseVersion("2.3.4"),
  1969  			getproviders.MustParseVersionConstraints(">= 2.3.3"),
  1970  			[]getproviders.Hash{
  1971  				getproviders.HashScheme1.New("SJPpXx/yoFE/W+7eCipjJ+G21xbdnTBD7lWodZ8hWkU="),
  1972  			},
  1973  		),
  1974  	}
  1975  	if diff := cmp.Diff(gotProviderLocks, wantProviderLocks, depsfile.ProviderLockComparer); diff != "" {
  1976  		t.Errorf("wrong version selections after upgrade\n%s", diff)
  1977  	}
  1978  }
  1979  
  1980  func TestInit_getProviderMissing(t *testing.T) {
  1981  	// Create a temporary working directory that is empty
  1982  	td := tempDir(t)
  1983  	testCopyDir(t, testFixturePath("init-get-providers"), td)
  1984  	defer os.RemoveAll(td)
  1985  	defer testChdir(t, td)()
  1986  
  1987  	providerSource, close := newMockProviderSource(t, map[string][]string{
  1988  		// looking for exact version 1.2.3
  1989  		"exact": {"1.2.4"},
  1990  		// config requires >= 2.3.3
  1991  		"greater-than": {"2.3.4", "2.3.3", "2.3.0"},
  1992  		// config specifies
  1993  		"between": {"3.4.5", "2.3.4", "1.2.3"},
  1994  	})
  1995  	defer close()
  1996  
  1997  	ui := new(cli.MockUi)
  1998  	view, _ := testView(t)
  1999  	m := Meta{
  2000  		testingOverrides: metaOverridesForProvider(testProvider()),
  2001  		Ui:               ui,
  2002  		View:             view,
  2003  		ProviderSource:   providerSource,
  2004  	}
  2005  
  2006  	c := &InitCommand{
  2007  		Meta: m,
  2008  	}
  2009  
  2010  	args := []string{}
  2011  	if code := c.Run(args); code == 0 {
  2012  		t.Fatalf("expected error, got output: \n%s", ui.OutputWriter.String())
  2013  	}
  2014  
  2015  	if !strings.Contains(ui.ErrorWriter.String(), "no available releases match") {
  2016  		t.Fatalf("unexpected error output: %s", ui.ErrorWriter)
  2017  	}
  2018  }
  2019  
  2020  func TestInit_checkRequiredVersion(t *testing.T) {
  2021  	// Create a temporary working directory that is empty
  2022  	td := tempDir(t)
  2023  	testCopyDir(t, testFixturePath("init-check-required-version"), td)
  2024  	defer os.RemoveAll(td)
  2025  	defer testChdir(t, td)()
  2026  
  2027  	ui := cli.NewMockUi()
  2028  	view, _ := testView(t)
  2029  	c := &InitCommand{
  2030  		Meta: Meta{
  2031  			testingOverrides: metaOverridesForProvider(testProvider()),
  2032  			Ui:               ui,
  2033  			View:             view,
  2034  		},
  2035  	}
  2036  
  2037  	args := []string{}
  2038  	if code := c.Run(args); code != 1 {
  2039  		t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
  2040  	}
  2041  	errStr := ui.ErrorWriter.String()
  2042  	if !strings.Contains(errStr, `required_version = "~> 0.9.0"`) {
  2043  		t.Fatalf("output should point to unmet version constraint, but is:\n\n%s", errStr)
  2044  	}
  2045  	if strings.Contains(errStr, `required_version = ">= 0.13.0"`) {
  2046  		t.Fatalf("output should not point to met version constraint, but is:\n\n%s", errStr)
  2047  	}
  2048  }
  2049  
  2050  // Verify that init will error out with an invalid version constraint, even if
  2051  // there are other invalid configuration constructs.
  2052  func TestInit_checkRequiredVersionFirst(t *testing.T) {
  2053  	t.Run("root_module", func(t *testing.T) {
  2054  		td := t.TempDir()
  2055  		testCopyDir(t, testFixturePath("init-check-required-version-first"), td)
  2056  		defer testChdir(t, td)()
  2057  
  2058  		ui := cli.NewMockUi()
  2059  		view, _ := testView(t)
  2060  		c := &InitCommand{
  2061  			Meta: Meta{
  2062  				testingOverrides: metaOverridesForProvider(testProvider()),
  2063  				Ui:               ui,
  2064  				View:             view,
  2065  			},
  2066  		}
  2067  
  2068  		args := []string{}
  2069  		if code := c.Run(args); code != 1 {
  2070  			t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
  2071  		}
  2072  		errStr := ui.ErrorWriter.String()
  2073  		if !strings.Contains(errStr, `Unsupported Terraform Core version`) {
  2074  			t.Fatalf("output should point to unmet version constraint, but is:\n\n%s", errStr)
  2075  		}
  2076  	})
  2077  	t.Run("sub_module", func(t *testing.T) {
  2078  		td := t.TempDir()
  2079  		testCopyDir(t, testFixturePath("init-check-required-version-first-module"), td)
  2080  		defer testChdir(t, td)()
  2081  
  2082  		ui := cli.NewMockUi()
  2083  		view, _ := testView(t)
  2084  		c := &InitCommand{
  2085  			Meta: Meta{
  2086  				testingOverrides: metaOverridesForProvider(testProvider()),
  2087  				Ui:               ui,
  2088  				View:             view,
  2089  			},
  2090  		}
  2091  
  2092  		args := []string{}
  2093  		if code := c.Run(args); code != 1 {
  2094  			t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
  2095  		}
  2096  		errStr := ui.ErrorWriter.String()
  2097  		if !strings.Contains(errStr, `Unsupported Terraform Core version`) {
  2098  			t.Fatalf("output should point to unmet version constraint, but is:\n\n%s", errStr)
  2099  		}
  2100  	})
  2101  }
  2102  
  2103  func TestInit_providerLockFile(t *testing.T) {
  2104  	// Create a temporary working directory that is empty
  2105  	td := tempDir(t)
  2106  	testCopyDir(t, testFixturePath("init-provider-lock-file"), td)
  2107  	defer os.RemoveAll(td)
  2108  	defer testChdir(t, td)()
  2109  
  2110  	providerSource, close := newMockProviderSource(t, map[string][]string{
  2111  		"test": {"1.2.3"},
  2112  	})
  2113  	defer close()
  2114  
  2115  	ui := new(cli.MockUi)
  2116  	view, _ := testView(t)
  2117  	m := Meta{
  2118  		testingOverrides: metaOverridesForProvider(testProvider()),
  2119  		Ui:               ui,
  2120  		View:             view,
  2121  		ProviderSource:   providerSource,
  2122  	}
  2123  
  2124  	c := &InitCommand{
  2125  		Meta: m,
  2126  	}
  2127  
  2128  	args := []string{}
  2129  	if code := c.Run(args); code != 0 {
  2130  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
  2131  	}
  2132  
  2133  	lockFile := ".terraform.lock.hcl"
  2134  	buf, err := ioutil.ReadFile(lockFile)
  2135  	if err != nil {
  2136  		t.Fatalf("failed to read dependency lock file %s: %s", lockFile, err)
  2137  	}
  2138  	buf = bytes.TrimSpace(buf)
  2139  	// The hash in here is for the fake package that newMockProviderSource produces
  2140  	// (so it'll change if newMockProviderSource starts producing different contents)
  2141  	wantLockFile := strings.TrimSpace(`
  2142  # This file is maintained automatically by "terraform init".
  2143  # Manual edits may be lost in future updates.
  2144  
  2145  provider "registry.terraform.io/hashicorp/test" {
  2146    version     = "1.2.3"
  2147    constraints = "1.2.3"
  2148    hashes = [
  2149      "h1:wlbEC2mChQZ2hhgUhl6SeVLPP7fMqOFUZAQhQ9GIIno=",
  2150    ]
  2151  }
  2152  `)
  2153  	if diff := cmp.Diff(wantLockFile, string(buf)); diff != "" {
  2154  		t.Errorf("wrong dependency lock file contents\n%s", diff)
  2155  	}
  2156  
  2157  	// Make the local directory read-only, and verify that rerunning init
  2158  	// succeeds, to ensure that we don't try to rewrite an unchanged lock file
  2159  	os.Chmod(".", 0555)
  2160  	if code := c.Run(args); code != 0 {
  2161  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
  2162  	}
  2163  }
  2164  
  2165  func TestInit_providerLockFileReadonly(t *testing.T) {
  2166  	// The hash in here is for the fake package that newMockProviderSource produces
  2167  	// (so it'll change if newMockProviderSource starts producing different contents)
  2168  	inputLockFile := strings.TrimSpace(`
  2169  # This file is maintained automatically by "terraform init".
  2170  # Manual edits may be lost in future updates.
  2171  
  2172  provider "registry.terraform.io/hashicorp/test" {
  2173    version     = "1.2.3"
  2174    constraints = "1.2.3"
  2175    hashes = [
  2176      "zh:e919b507a91e23a00da5c2c4d0b64bcc7900b68d43b3951ac0f6e5d80387fbdc",
  2177    ]
  2178  }
  2179  `)
  2180  
  2181  	badLockFile := strings.TrimSpace(`
  2182  # This file is maintained automatically by "terraform init".
  2183  # Manual edits may be lost in future updates.
  2184  
  2185  provider "registry.terraform.io/hashicorp/test" {
  2186    version     = "1.2.3"
  2187    constraints = "1.2.3"
  2188    hashes = [
  2189      "zh:0000000000000000000000000000000000000000000000000000000000000000",
  2190    ]
  2191  }
  2192  `)
  2193  
  2194  	updatedLockFile := strings.TrimSpace(`
  2195  # This file is maintained automatically by "terraform init".
  2196  # Manual edits may be lost in future updates.
  2197  
  2198  provider "registry.terraform.io/hashicorp/test" {
  2199    version     = "1.2.3"
  2200    constraints = "1.2.3"
  2201    hashes = [
  2202      "h1:wlbEC2mChQZ2hhgUhl6SeVLPP7fMqOFUZAQhQ9GIIno=",
  2203      "zh:e919b507a91e23a00da5c2c4d0b64bcc7900b68d43b3951ac0f6e5d80387fbdc",
  2204    ]
  2205  }
  2206  `)
  2207  
  2208  	emptyUpdatedLockFile := strings.TrimSpace(`
  2209  # This file is maintained automatically by "terraform init".
  2210  # Manual edits may be lost in future updates.
  2211  `)
  2212  
  2213  	cases := []struct {
  2214  		desc      string
  2215  		fixture   string
  2216  		providers map[string][]string
  2217  		input     string
  2218  		args      []string
  2219  		ok        bool
  2220  		want      string
  2221  	}{
  2222  		{
  2223  			desc:      "default",
  2224  			fixture:   "init-provider-lock-file",
  2225  			providers: map[string][]string{"test": {"1.2.3"}},
  2226  			input:     inputLockFile,
  2227  			args:      []string{},
  2228  			ok:        true,
  2229  			want:      updatedLockFile,
  2230  		},
  2231  		{
  2232  			desc:      "unused provider",
  2233  			fixture:   "init-provider-now-unused",
  2234  			providers: map[string][]string{"test": {"1.2.3"}},
  2235  			input:     inputLockFile,
  2236  			args:      []string{},
  2237  			ok:        true,
  2238  			want:      emptyUpdatedLockFile,
  2239  		},
  2240  		{
  2241  			desc:      "readonly",
  2242  			fixture:   "init-provider-lock-file",
  2243  			providers: map[string][]string{"test": {"1.2.3"}},
  2244  			input:     inputLockFile,
  2245  			args:      []string{"-lockfile=readonly"},
  2246  			ok:        true,
  2247  			want:      inputLockFile,
  2248  		},
  2249  		{
  2250  			desc:      "unused provider readonly",
  2251  			fixture:   "init-provider-now-unused",
  2252  			providers: map[string][]string{"test": {"1.2.3"}},
  2253  			input:     inputLockFile,
  2254  			args:      []string{"-lockfile=readonly"},
  2255  			ok:        false,
  2256  			want:      inputLockFile,
  2257  		},
  2258  		{
  2259  			desc:      "conflict",
  2260  			fixture:   "init-provider-lock-file",
  2261  			providers: map[string][]string{"test": {"1.2.3"}},
  2262  			input:     inputLockFile,
  2263  			args:      []string{"-lockfile=readonly", "-upgrade"},
  2264  			ok:        false,
  2265  			want:      inputLockFile,
  2266  		},
  2267  		{
  2268  			desc:      "checksum mismatch",
  2269  			fixture:   "init-provider-lock-file",
  2270  			providers: map[string][]string{"test": {"1.2.3"}},
  2271  			input:     badLockFile,
  2272  			args:      []string{"-lockfile=readonly"},
  2273  			ok:        false,
  2274  			want:      badLockFile,
  2275  		},
  2276  		{
  2277  			desc:    "reject to change required provider dependences",
  2278  			fixture: "init-provider-lock-file-readonly-add",
  2279  			providers: map[string][]string{
  2280  				"test": {"1.2.3"},
  2281  				"foo":  {"1.0.0"},
  2282  			},
  2283  			input: inputLockFile,
  2284  			args:  []string{"-lockfile=readonly"},
  2285  			ok:    false,
  2286  			want:  inputLockFile,
  2287  		},
  2288  	}
  2289  
  2290  	for _, tc := range cases {
  2291  		t.Run(tc.desc, func(t *testing.T) {
  2292  			// Create a temporary working directory that is empty
  2293  			td := tempDir(t)
  2294  			testCopyDir(t, testFixturePath(tc.fixture), td)
  2295  			defer os.RemoveAll(td)
  2296  			defer testChdir(t, td)()
  2297  
  2298  			providerSource, close := newMockProviderSource(t, tc.providers)
  2299  			defer close()
  2300  
  2301  			ui := new(cli.MockUi)
  2302  			m := Meta{
  2303  				testingOverrides: metaOverridesForProvider(testProvider()),
  2304  				Ui:               ui,
  2305  				ProviderSource:   providerSource,
  2306  			}
  2307  
  2308  			c := &InitCommand{
  2309  				Meta: m,
  2310  			}
  2311  
  2312  			// write input lockfile
  2313  			lockFile := ".terraform.lock.hcl"
  2314  			if err := ioutil.WriteFile(lockFile, []byte(tc.input), 0644); err != nil {
  2315  				t.Fatalf("failed to write input lockfile: %s", err)
  2316  			}
  2317  
  2318  			code := c.Run(tc.args)
  2319  			if tc.ok && code != 0 {
  2320  				t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
  2321  			}
  2322  			if !tc.ok && code == 0 {
  2323  				t.Fatalf("expected error, got output: \n%s", ui.OutputWriter.String())
  2324  			}
  2325  
  2326  			buf, err := ioutil.ReadFile(lockFile)
  2327  			if err != nil {
  2328  				t.Fatalf("failed to read dependency lock file %s: %s", lockFile, err)
  2329  			}
  2330  			buf = bytes.TrimSpace(buf)
  2331  			if diff := cmp.Diff(tc.want, string(buf)); diff != "" {
  2332  				t.Errorf("wrong dependency lock file contents\n%s", diff)
  2333  			}
  2334  		})
  2335  	}
  2336  }
  2337  
  2338  func TestInit_pluginDirReset(t *testing.T) {
  2339  	td := testTempDir(t)
  2340  	defer os.RemoveAll(td)
  2341  	defer testChdir(t, td)()
  2342  
  2343  	// An empty provider source
  2344  	providerSource, close := newMockProviderSource(t, nil)
  2345  	defer close()
  2346  
  2347  	ui := new(cli.MockUi)
  2348  	view, _ := testView(t)
  2349  	c := &InitCommand{
  2350  		Meta: Meta{
  2351  			testingOverrides: metaOverridesForProvider(testProvider()),
  2352  			Ui:               ui,
  2353  			View:             view,
  2354  			ProviderSource:   providerSource,
  2355  		},
  2356  	}
  2357  
  2358  	// make our vendor paths
  2359  	pluginPath := []string{"a", "b", "c"}
  2360  	for _, p := range pluginPath {
  2361  		if err := os.MkdirAll(p, 0755); err != nil {
  2362  			t.Fatal(err)
  2363  		}
  2364  	}
  2365  
  2366  	// run once and save the -plugin-dir
  2367  	args := []string{"-plugin-dir", "a"}
  2368  	if code := c.Run(args); code != 0 {
  2369  		t.Fatalf("bad: \n%s", ui.ErrorWriter)
  2370  	}
  2371  
  2372  	pluginDirs, err := c.loadPluginPath()
  2373  	if err != nil {
  2374  		t.Fatal(err)
  2375  	}
  2376  
  2377  	if len(pluginDirs) != 1 || pluginDirs[0] != "a" {
  2378  		t.Fatalf(`expected plugin dir ["a"], got %q`, pluginDirs)
  2379  	}
  2380  
  2381  	ui = new(cli.MockUi)
  2382  	c = &InitCommand{
  2383  		Meta: Meta{
  2384  			testingOverrides: metaOverridesForProvider(testProvider()),
  2385  			Ui:               ui,
  2386  			View:             view,
  2387  			ProviderSource:   providerSource, // still empty
  2388  		},
  2389  	}
  2390  
  2391  	// make sure we remove the plugin-dir record
  2392  	args = []string{"-plugin-dir="}
  2393  	if code := c.Run(args); code != 0 {
  2394  		t.Fatalf("bad: \n%s", ui.ErrorWriter)
  2395  	}
  2396  
  2397  	pluginDirs, err = c.loadPluginPath()
  2398  	if err != nil {
  2399  		t.Fatal(err)
  2400  	}
  2401  
  2402  	if len(pluginDirs) != 0 {
  2403  		t.Fatalf("expected no plugin dirs got %q", pluginDirs)
  2404  	}
  2405  }
  2406  
  2407  // Test user-supplied -plugin-dir
  2408  func TestInit_pluginDirProviders(t *testing.T) {
  2409  	td := tempDir(t)
  2410  	testCopyDir(t, testFixturePath("init-get-providers"), td)
  2411  	defer os.RemoveAll(td)
  2412  	defer testChdir(t, td)()
  2413  
  2414  	// An empty provider source
  2415  	providerSource, close := newMockProviderSource(t, nil)
  2416  	defer close()
  2417  
  2418  	ui := new(cli.MockUi)
  2419  	view, _ := testView(t)
  2420  	m := Meta{
  2421  		testingOverrides: metaOverridesForProvider(testProvider()),
  2422  		Ui:               ui,
  2423  		View:             view,
  2424  		ProviderSource:   providerSource,
  2425  	}
  2426  
  2427  	c := &InitCommand{
  2428  		Meta: m,
  2429  	}
  2430  
  2431  	// make our vendor paths
  2432  	pluginPath := []string{"a", "b", "c"}
  2433  	for _, p := range pluginPath {
  2434  		if err := os.MkdirAll(p, 0755); err != nil {
  2435  			t.Fatal(err)
  2436  		}
  2437  	}
  2438  
  2439  	// We'll put some providers in our plugin dirs. To do this, we'll pretend
  2440  	// for a moment that they are provider cache directories just because that
  2441  	// allows us to lean on our existing test helper functions to do this.
  2442  	for i, def := range [][]string{
  2443  		{"exact", "1.2.3"},
  2444  		{"greater-than", "2.3.4"},
  2445  		{"between", "2.3.4"},
  2446  	} {
  2447  		name, version := def[0], def[1]
  2448  		dir := providercache.NewDir(pluginPath[i])
  2449  		installFakeProviderPackagesElsewhere(t, dir, map[string][]string{
  2450  			name: {version},
  2451  		})
  2452  	}
  2453  
  2454  	args := []string{
  2455  		"-plugin-dir", "a",
  2456  		"-plugin-dir", "b",
  2457  		"-plugin-dir", "c",
  2458  	}
  2459  	if code := c.Run(args); code != 0 {
  2460  		t.Fatalf("bad: \n%s", ui.ErrorWriter)
  2461  	}
  2462  
  2463  	locks, err := m.lockedDependencies()
  2464  	if err != nil {
  2465  		t.Fatalf("failed to get locked dependencies: %s", err)
  2466  	}
  2467  	gotProviderLocks := locks.AllProviders()
  2468  	wantProviderLocks := map[addrs.Provider]*depsfile.ProviderLock{
  2469  		addrs.NewDefaultProvider("between"): depsfile.NewProviderLock(
  2470  			addrs.NewDefaultProvider("between"),
  2471  			getproviders.MustParseVersion("2.3.4"),
  2472  			getproviders.MustParseVersionConstraints("> 1.0.0, < 3.0.0"),
  2473  			[]getproviders.Hash{
  2474  				getproviders.HashScheme1.New("JVqAvZz88A+hS2wHVtTWQkHaxoA/LrUAz0H3jPBWPIA="),
  2475  			},
  2476  		),
  2477  		addrs.NewDefaultProvider("exact"): depsfile.NewProviderLock(
  2478  			addrs.NewDefaultProvider("exact"),
  2479  			getproviders.MustParseVersion("1.2.3"),
  2480  			getproviders.MustParseVersionConstraints("= 1.2.3"),
  2481  			[]getproviders.Hash{
  2482  				getproviders.HashScheme1.New("H1TxWF8LyhBb6B4iUdKhLc/S9sC/jdcrCykpkbGcfbg="),
  2483  			},
  2484  		),
  2485  		addrs.NewDefaultProvider("greater-than"): depsfile.NewProviderLock(
  2486  			addrs.NewDefaultProvider("greater-than"),
  2487  			getproviders.MustParseVersion("2.3.4"),
  2488  			getproviders.MustParseVersionConstraints(">= 2.3.3"),
  2489  			[]getproviders.Hash{
  2490  				getproviders.HashScheme1.New("SJPpXx/yoFE/W+7eCipjJ+G21xbdnTBD7lWodZ8hWkU="),
  2491  			},
  2492  		),
  2493  	}
  2494  	if diff := cmp.Diff(gotProviderLocks, wantProviderLocks, depsfile.ProviderLockComparer); diff != "" {
  2495  		t.Errorf("wrong version selections after upgrade\n%s", diff)
  2496  	}
  2497  
  2498  	// -plugin-dir overrides the normal provider source, so it should not have
  2499  	// seen any calls at all.
  2500  	if calls := providerSource.CallLog(); len(calls) > 0 {
  2501  		t.Errorf("unexpected provider source calls (want none)\n%s", spew.Sdump(calls))
  2502  	}
  2503  }
  2504  
  2505  // Test user-supplied -plugin-dir doesn't allow auto-install
  2506  func TestInit_pluginDirProvidersDoesNotGet(t *testing.T) {
  2507  	td := tempDir(t)
  2508  	testCopyDir(t, testFixturePath("init-get-providers"), td)
  2509  	defer os.RemoveAll(td)
  2510  	defer testChdir(t, td)()
  2511  
  2512  	// Our provider source has a suitable package for "between" available,
  2513  	// but we should ignore it because -plugin-dir is set and thus this
  2514  	// source is temporarily overridden during install.
  2515  	providerSource, close := newMockProviderSource(t, map[string][]string{
  2516  		"between": {"2.3.4"},
  2517  	})
  2518  	defer close()
  2519  
  2520  	ui := cli.NewMockUi()
  2521  	view, _ := testView(t)
  2522  	m := Meta{
  2523  		testingOverrides: metaOverridesForProvider(testProvider()),
  2524  		Ui:               ui,
  2525  		View:             view,
  2526  		ProviderSource:   providerSource,
  2527  	}
  2528  
  2529  	c := &InitCommand{
  2530  		Meta: m,
  2531  	}
  2532  
  2533  	// make our vendor paths
  2534  	pluginPath := []string{"a", "b"}
  2535  	for _, p := range pluginPath {
  2536  		if err := os.MkdirAll(p, 0755); err != nil {
  2537  			t.Fatal(err)
  2538  		}
  2539  	}
  2540  
  2541  	// We'll put some providers in our plugin dirs. To do this, we'll pretend
  2542  	// for a moment that they are provider cache directories just because that
  2543  	// allows us to lean on our existing test helper functions to do this.
  2544  	for i, def := range [][]string{
  2545  		{"exact", "1.2.3"},
  2546  		{"greater-than", "2.3.4"},
  2547  	} {
  2548  		name, version := def[0], def[1]
  2549  		dir := providercache.NewDir(pluginPath[i])
  2550  		installFakeProviderPackagesElsewhere(t, dir, map[string][]string{
  2551  			name: {version},
  2552  		})
  2553  	}
  2554  
  2555  	args := []string{
  2556  		"-plugin-dir", "a",
  2557  		"-plugin-dir", "b",
  2558  	}
  2559  	if code := c.Run(args); code == 0 {
  2560  		// should have been an error
  2561  		t.Fatalf("succeeded; want error\nstdout:\n%s\nstderr\n%s", ui.OutputWriter, ui.ErrorWriter)
  2562  	}
  2563  
  2564  	// The error output should mention the "between" provider but should not
  2565  	// mention either the "exact" or "greater-than" provider, because the
  2566  	// latter two are available via the -plugin-dir directories.
  2567  	errStr := ui.ErrorWriter.String()
  2568  	if subStr := "hashicorp/between"; !strings.Contains(errStr, subStr) {
  2569  		t.Errorf("error output should mention the 'between' provider\nwant substr: %s\ngot:\n%s", subStr, errStr)
  2570  	}
  2571  	if subStr := "hashicorp/exact"; strings.Contains(errStr, subStr) {
  2572  		t.Errorf("error output should not mention the 'exact' provider\ndo not want substr: %s\ngot:\n%s", subStr, errStr)
  2573  	}
  2574  	if subStr := "hashicorp/greater-than"; strings.Contains(errStr, subStr) {
  2575  		t.Errorf("error output should not mention the 'greater-than' provider\ndo not want substr: %s\ngot:\n%s", subStr, errStr)
  2576  	}
  2577  
  2578  	if calls := providerSource.CallLog(); len(calls) > 0 {
  2579  		t.Errorf("unexpected provider source calls (want none)\n%s", spew.Sdump(calls))
  2580  	}
  2581  }
  2582  
  2583  // Verify that plugin-dir doesn't prevent discovery of src providers
  2584  func TestInit_pluginDirWithBuiltIn(t *testing.T) {
  2585  	td := tempDir(t)
  2586  	testCopyDir(t, testFixturePath("init-src"), td)
  2587  	defer os.RemoveAll(td)
  2588  	defer testChdir(t, td)()
  2589  
  2590  	// An empty provider source
  2591  	providerSource, close := newMockProviderSource(t, nil)
  2592  	defer close()
  2593  
  2594  	ui := cli.NewMockUi()
  2595  	view, _ := testView(t)
  2596  	m := Meta{
  2597  		testingOverrides: metaOverridesForProvider(testProvider()),
  2598  		Ui:               ui,
  2599  		View:             view,
  2600  		ProviderSource:   providerSource,
  2601  	}
  2602  
  2603  	c := &InitCommand{
  2604  		Meta: m,
  2605  	}
  2606  
  2607  	args := []string{"-plugin-dir", "./"}
  2608  	if code := c.Run(args); code != 0 {
  2609  		t.Fatalf("error: %s", ui.ErrorWriter)
  2610  	}
  2611  
  2612  	outputStr := ui.OutputWriter.String()
  2613  	if subStr := "terraform.io/builtin/terraform is built in to Terraform"; !strings.Contains(outputStr, subStr) {
  2614  		t.Errorf("output should mention the terraform provider\nwant substr: %s\ngot:\n%s", subStr, outputStr)
  2615  	}
  2616  }
  2617  
  2618  func TestInit_invalidBuiltInProviders(t *testing.T) {
  2619  	// This test fixture includes two invalid provider dependencies:
  2620  	// - an implied dependency on terraform.io/builtin/terraform with an
  2621  	//   explicit version number, which is not allowed because it's builtin.
  2622  	// - an explicit dependency on terraform.io/builtin/nonexist, which does
  2623  	//   not exist at all.
  2624  	td := tempDir(t)
  2625  	testCopyDir(t, testFixturePath("init-src-invalid"), td)
  2626  	defer os.RemoveAll(td)
  2627  	defer testChdir(t, td)()
  2628  
  2629  	// An empty provider source
  2630  	providerSource, close := newMockProviderSource(t, nil)
  2631  	defer close()
  2632  
  2633  	ui := cli.NewMockUi()
  2634  	view, _ := testView(t)
  2635  	m := Meta{
  2636  		testingOverrides: metaOverridesForProvider(testProvider()),
  2637  		Ui:               ui,
  2638  		View:             view,
  2639  		ProviderSource:   providerSource,
  2640  	}
  2641  
  2642  	c := &InitCommand{
  2643  		Meta: m,
  2644  	}
  2645  
  2646  	if code := c.Run(nil); code == 0 {
  2647  		t.Fatalf("succeeded, but was expecting error\nstdout:\n%s\nstderr:\n%s", ui.OutputWriter, ui.ErrorWriter)
  2648  	}
  2649  
  2650  	errStr := ui.ErrorWriter.String()
  2651  	if subStr := "Cannot use terraform.io/builtin/terraform: built-in"; !strings.Contains(errStr, subStr) {
  2652  		t.Errorf("error output should mention the terraform provider\nwant substr: %s\ngot:\n%s", subStr, errStr)
  2653  	}
  2654  	if subStr := "Cannot use terraform.io/builtin/nonexist: this Terraform release"; !strings.Contains(errStr, subStr) {
  2655  		t.Errorf("error output should mention the 'nonexist' provider\nwant substr: %s\ngot:\n%s", subStr, errStr)
  2656  	}
  2657  }
  2658  
  2659  // newMockProviderSource is a helper to succinctly construct a mock provider
  2660  // source that contains a set of packages matching the given provider versions
  2661  // that are available for installation (from temporary local files).
  2662  //
  2663  // The caller must call the returned close callback once the source is no
  2664  // longer needed, at which point it will clean up all of the temporary files
  2665  // and the packages in the source will no longer be available for installation.
  2666  //
  2667  // Provider addresses must be valid source strings, and passing only the
  2668  // provider name will be interpreted as a "default" provider under
  2669  // registry.terraform.io/hashicorp. If you need more control over the
  2670  // provider addresses, pass a full provider source string.
  2671  //
  2672  // This function also registers providers as belonging to the current platform,
  2673  // to ensure that they will be available to a provider installer operating in
  2674  // its default configuration.
  2675  //
  2676  // In case of any errors while constructing the source, this function will
  2677  // abort the current test using the given testing.T. Therefore a caller can
  2678  // assume that if this function returns then the result is valid and ready
  2679  // to use.
  2680  func newMockProviderSource(t *testing.T, availableProviderVersions map[string][]string) (source *getproviders.MockSource, close func()) {
  2681  	t.Helper()
  2682  	var packages []getproviders.PackageMeta
  2683  	var closes []func()
  2684  	close = func() {
  2685  		for _, f := range closes {
  2686  			f()
  2687  		}
  2688  	}
  2689  	for source, versions := range availableProviderVersions {
  2690  		addr := addrs.MustParseProviderSourceString(source)
  2691  		for _, versionStr := range versions {
  2692  			version, err := getproviders.ParseVersion(versionStr)
  2693  			if err != nil {
  2694  				close()
  2695  				t.Fatalf("failed to parse %q as a version number for %q: %s", versionStr, addr.ForDisplay(), err)
  2696  			}
  2697  			meta, close, err := getproviders.FakeInstallablePackageMeta(addr, version, getproviders.VersionList{getproviders.MustParseVersion("5.0")}, getproviders.CurrentPlatform, "")
  2698  			if err != nil {
  2699  				close()
  2700  				t.Fatalf("failed to prepare fake package for %s %s: %s", addr.ForDisplay(), versionStr, err)
  2701  			}
  2702  			closes = append(closes, close)
  2703  			packages = append(packages, meta)
  2704  		}
  2705  	}
  2706  
  2707  	return getproviders.NewMockSource(packages, nil), close
  2708  }
  2709  
  2710  // installFakeProviderPackages installs a fake package for the given provider
  2711  // names (interpreted as a "default" provider address) and versions into the
  2712  // local plugin cache for the given "meta".
  2713  //
  2714  // Any test using this must be using testChdir or some similar mechanism to
  2715  // make sure that it isn't writing directly into a test fixture or source
  2716  // directory within the codebase.
  2717  //
  2718  // If a requested package cannot be installed for some reason, this function
  2719  // will abort the test using the given testing.T. Therefore if this function
  2720  // returns the caller can assume that the requested providers have been
  2721  // installed.
  2722  func installFakeProviderPackages(t *testing.T, meta *Meta, providerVersions map[string][]string) {
  2723  	t.Helper()
  2724  
  2725  	cacheDir := meta.providerLocalCacheDir()
  2726  	installFakeProviderPackagesElsewhere(t, cacheDir, providerVersions)
  2727  }
  2728  
  2729  // installFakeProviderPackagesElsewhere is a variant of installFakeProviderPackages
  2730  // that will install packages into the given provider cache directory, rather
  2731  // than forcing the use of the local cache of the current "Meta".
  2732  func installFakeProviderPackagesElsewhere(t *testing.T, cacheDir *providercache.Dir, providerVersions map[string][]string) {
  2733  	t.Helper()
  2734  
  2735  	// It can be hard to spot the mistake of forgetting to run testChdir before
  2736  	// modifying the working directory, so we'll use a simple heuristic here
  2737  	// to try to detect that mistake and make a noisy error about it instead.
  2738  	wd, err := os.Getwd()
  2739  	if err == nil {
  2740  		wd = filepath.Clean(wd)
  2741  		// If the directory we're in is named "command" or if we're under a
  2742  		// directory named "testdata" then we'll assume a mistake and generate
  2743  		// an error. This will cause the test to fail but won't block it from
  2744  		// running.
  2745  		if filepath.Base(wd) == "command" || filepath.Base(wd) == "testdata" || strings.Contains(filepath.ToSlash(wd), "/testdata/") {
  2746  			t.Errorf("installFakeProviderPackage may be used only by tests that switch to a temporary working directory, e.g. using testChdir")
  2747  		}
  2748  	}
  2749  
  2750  	for name, versions := range providerVersions {
  2751  		addr := addrs.NewDefaultProvider(name)
  2752  		for _, versionStr := range versions {
  2753  			version, err := getproviders.ParseVersion(versionStr)
  2754  			if err != nil {
  2755  				t.Fatalf("failed to parse %q as a version number for %q: %s", versionStr, name, err)
  2756  			}
  2757  			meta, close, err := getproviders.FakeInstallablePackageMeta(addr, version, getproviders.VersionList{getproviders.MustParseVersion("5.0")}, getproviders.CurrentPlatform, "")
  2758  			// We're going to install all these fake packages before we return,
  2759  			// so we don't need to preserve them afterwards.
  2760  			defer close()
  2761  			if err != nil {
  2762  				t.Fatalf("failed to prepare fake package for %s %s: %s", name, versionStr, err)
  2763  			}
  2764  			_, err = cacheDir.InstallPackage(context.Background(), meta, nil)
  2765  			if err != nil {
  2766  				t.Fatalf("failed to install fake package for %s %s: %s", name, versionStr, err)
  2767  			}
  2768  		}
  2769  	}
  2770  }
  2771  
  2772  // expectedPackageInstallPath is a companion to installFakeProviderPackages
  2773  // that returns the path where the provider with the given name and version
  2774  // would be installed and, relatedly, where the installer will expect to
  2775  // find an already-installed version.
  2776  //
  2777  // Just as with installFakeProviderPackages, this function is a shortcut helper
  2778  // for "default-namespaced" providers as we commonly use in tests. If you need
  2779  // more control over the provider addresses, use functions of the underlying
  2780  // getproviders and providercache packages instead.
  2781  //
  2782  // The result always uses forward slashes, even on Windows, for consistency
  2783  // with how the getproviders and providercache packages build paths.
  2784  func expectedPackageInstallPath(name, version string, exe bool) string {
  2785  	platform := getproviders.CurrentPlatform
  2786  	baseDir := ".terraform/providers"
  2787  	if exe {
  2788  		p := fmt.Sprintf("registry.terraform.io/hashicorp/%s/%s/%s/terraform-provider-%s_%s", name, version, platform, name, version)
  2789  		if platform.OS == "windows" {
  2790  			p += ".exe"
  2791  		}
  2792  		return filepath.ToSlash(filepath.Join(baseDir, p))
  2793  	}
  2794  	return filepath.ToSlash(filepath.Join(
  2795  		baseDir, fmt.Sprintf("registry.terraform.io/hashicorp/%s/%s/%s", name, version, platform),
  2796  	))
  2797  }