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