github.com/opentofu/opentofu@v1.7.1/internal/command/init_test.go (about)

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