diff --git a/internal/command/init_test.go b/internal/command/init_test.go index a522e49912..0a10e5a871 100644 --- a/internal/command/init_test.go +++ b/internal/command/init_test.go @@ -59,6 +59,7 @@ func cleanString(s string) string { return s } +// Test that users can initialize an empty directory func TestInit_empty(t *testing.T) { // Create a temporary working directory that is empty td := t.TempDir() @@ -86,6 +87,7 @@ func TestInit_empty(t *testing.T) { } } +// Test that users can initialize a directory containing tests and no configuration func TestInit_only_test_files(t *testing.T) { // Create a temporary working directory that has only test files and no tf configuration td := t.TempDir() @@ -117,6 +119,8 @@ func TestInit_only_test_files(t *testing.T) { } } +// Test that the new init logic for the PSS project downloads all expected providers, +// without redundant download steps, and maintaining features like provider upgrade. func TestInit_two_step_provider_download(t *testing.T) { cases := map[string]struct { workDirPath string @@ -199,7 +203,6 @@ func TestInit_two_step_provider_download(t *testing.T) { for tn, tc := range cases { t.Run(tn, func(t *testing.T) { - // Create a temporary working directory no tf configuration but has state td := t.TempDir() testCopyDir(t, testFixturePath(tc.workDirPath), td) @@ -266,6 +269,7 @@ func TestInit_multipleArgs(t *testing.T) { } } +// Test that the -migrate-state and -json flags are mutually exclusive and an error is returned. func TestInit_migrateStateAndJSON(t *testing.T) { // Create a temporary working directory that is empty td := t.TempDir() @@ -296,6 +300,7 @@ func TestInit_migrateStateAndJSON(t *testing.T) { checkGoldenReference(t, testOutput, "init-migrate-state-with-json") } +// Test copying a source module using the -from-module=MODULE-SOURCE flag. func TestInit_fromModule_cwdDest(t *testing.T) { // Create a temporary working directory that is empty td := t.TempDir() @@ -591,6 +596,7 @@ func TestInit_backend_migration_stateMgr_error(t *testing.T) { } } +// Test removing a backend block, i.e. migration to an implied local backend func TestInit_backendUnset(t *testing.T) { // Create a temporary working directory that is empty td := t.TempDir() @@ -661,8 +667,11 @@ func TestInit_backendUnset(t *testing.T) { } } +// Test use of the -backend-config flag when initializing a backend, +// using a file path as the flag value. func TestInit_backendConfigFile(t *testing.T) { - // Create a temporary working directory that is empty + // The same working directory is used across test cases; after the first test case subsequent cases need to + // perform a state migration, unless they end early due to testing an error case. td := t.TempDir() testCopyDir(t, testFixturePath("init-backend-config-file"), td) t.Chdir(td) @@ -797,6 +806,7 @@ func TestInit_backendConfigFile(t *testing.T) { }) } +// Test use of the -backend-config flag in Powershell func TestInit_backendConfigFilePowershellConfusion(t *testing.T) { // Create a temporary working directory that is empty td := t.TempDir() @@ -833,6 +843,8 @@ func TestInit_backendConfigFilePowershellConfusion(t *testing.T) { } } +// Test that the -reconfigure flag allows users to initialize a backend +// independently of prior backend state files and avoiding state migrations. func TestInit_backendReconfigure(t *testing.T) { // Create a temporary working directory that is empty td := t.TempDir() @@ -998,6 +1010,8 @@ func TestInit_backendConfigFileChangeWithExistingState(t *testing.T) { } } +// Test use of the -backend-config flag when initializing a backend, +// using key-value pairs as the flag value. func TestInit_backendConfigKV(t *testing.T) { // Create a temporary working directory that is empty td := t.TempDir() @@ -1026,6 +1040,9 @@ func TestInit_backendConfigKV(t *testing.T) { } } +// Test that the -backend-config flag's values persist when subsequent init commands +// are run, and that subsequent init commands can use overwrite config if -backend-config +// is supplied with a different value. func TestInit_backendConfigKVReInit(t *testing.T) { // Create a temporary working directory that is empty td := t.TempDir() @@ -1578,7 +1595,6 @@ prompts. t.Errorf("wrong error output\n%s", diff) } }) - } // make sure inputFalse stops execution on migrate @@ -3269,6 +3285,7 @@ func TestInit_testsWithOverriddenInvalidRequiredProviders(t *testing.T) { } } +// Test that Terraform doesn't crash when required_providers is malformed func TestInit_testsWithInvalidRequiredProviders(t *testing.T) { td := t.TempDir() testCopyDir(t, testFixturePath("init-with-duplicates"), td) @@ -3299,6 +3316,7 @@ func TestInit_testsWithInvalidRequiredProviders(t *testing.T) { } } +// Test that modules referenced in test files are installed during init func TestInit_testsWithModule(t *testing.T) { // Create a temporary working directory that is empty td := t.TempDir() @@ -3858,6 +3876,94 @@ func TestInit_stateStore_configChanges(t *testing.T) { } }) + t.Run("the -backend=false flag makes Terraform ignore config and use only the the backend state file during initialization", func(t *testing.T) { + // Create a temporary working directory with state store configuration + // that doesn't match the backend state file + td := t.TempDir() + testCopyDir(t, testFixturePath("state-store-changed/store-config"), td) + t.Chdir(td) + + mockProvider := mockPluggableStateStorageProvider() + + // The previous init implied by this test scenario would have created this. + mockProvider.GetStatesResponse = &providers.GetStatesResponse{States: []string{"default"}} + mockProvider.MockStates = map[string]interface{}{"default": []byte(`{"version": 4,"terraform_version":"1.15.0","serial": 1,"lineage": "","outputs": {},"resources": [],"checks":[]}`)} + + mockProviderAddress := addrs.NewDefaultProvider("test") + providerSource, close := newMockProviderSource(t, map[string][]string{ + "hashicorp/test": {"1.2.3"}, // Matches provider version in backend state file fixture + }) + defer close() + + ui := new(cli.MockUi) + view, done := testView(t) + meta := Meta{ + Ui: ui, + View: view, + AllowExperimentalFeatures: true, + testingOverrides: &testingOverrides{ + Providers: map[addrs.Provider]providers.Factory{ + mockProviderAddress: providers.FactoryFixed(mockProvider), + }, + }, + ProviderSource: providerSource, + } + c := &InitCommand{ + Meta: meta, + } + + args := []string{ + "-enable-pluggable-state-storage-experiment=true", + "-backend=false", + } + code := c.Run(args) + testOutput := done(t) + if code != 0 { + t.Fatalf("expected code 0 exit code, got %d, output: \n%s", code, testOutput.All()) + } + + // Check output + output := testOutput.All() + expectedOutputs := []string{ + "Initializing the state store...", + "Terraform has been successfully initialized!", + } + for _, expected := range expectedOutputs { + if !strings.Contains(output, expected) { + t.Fatalf("expected output to include %q, but got':\n %s", expected, output) + } + } + + // Assert contents of the backend state file + statePath := filepath.Join(meta.DataDir(), DefaultStateFilename) + sMgr := &clistate.LocalState{Path: statePath} + if err := sMgr.RefreshState(); err != nil { + t.Fatal("Failed to load state:", err) + } + s := sMgr.State() + if s == nil { + t.Fatal("expected backend state file to be created, but there isn't one") + } + v1_2_3, _ := version.NewVersion("1.2.3") + expectedState := &workdir.StateStoreConfigState{ + Type: "test_store", + ConfigRaw: []byte("{\n \"value\": \"changed-value\"\n }"), + Hash: uint64(1157855489), // The new hash after reconfiguring; this doesn't match the backend state test fixture + Provider: &workdir.ProviderConfigState{ + Version: v1_2_3, + Source: &tfaddr.Provider{ + Hostname: tfaddr.DefaultProviderRegistryHost, + Namespace: "hashicorp", + Type: "test", + }, + ConfigRaw: []byte("{\n \"region\": null\n }"), + }, + } + if diff := cmp.Diff(s.StateStore, expectedState); diff != "" { + t.Fatalf("unexpected diff in backend state file's description of state store:\n%s", diff) + } + }) + t.Run("handling changed state store config is currently unimplemented", func(t *testing.T) { // Create a temporary working directory with state store configuration // that doesn't match the backend state file @@ -3905,7 +4011,6 @@ func TestInit_stateStore_configChanges(t *testing.T) { if !strings.Contains(output, expectedMsg) { t.Fatalf("expected output to include %q, but got':\n %s", expectedMsg, output) } - }) t.Run("handling changed state store provider config is currently unimplemented", func(t *testing.T) { @@ -4124,6 +4229,11 @@ func TestInit_stateStore_providerUpgrade(t *testing.T) { }) } +// Test migration from state_store to backend +// +// In this test the provider used for pluggable state storage is still present in +// the config's required_providers list, but this isn't necessary. +// See also TestInit_stateStore_unset_withoutProviderRequirements. func TestInit_stateStore_unset(t *testing.T) { // Create a temporary working directory that is empty td := t.TempDir() @@ -4225,6 +4335,11 @@ func TestInit_stateStore_unset(t *testing.T) { } } +// Test migration from state_store to backend +// +// Specifically, this test shows that when migrating away from a state_store (to implied backend) +// the required_provider entry used for pluggable state storage don't need to be present in the +// configuration during the migration process. func TestInit_stateStore_unset_withoutProviderRequirements(t *testing.T) { // Create a temporary working directory that is empty td := t.TempDir() @@ -4327,6 +4442,7 @@ func TestInit_stateStore_unset_withoutProviderRequirements(t *testing.T) { } } +// Test the ability to migrate from using `state_store` config to `backend` config func TestInit_stateStore_to_backend(t *testing.T) { // Create a temporary working directory that is empty td := t.TempDir() @@ -4504,7 +4620,7 @@ func TestInit_stateStore_to_backend(t *testing.T) { t.Fatal(err) } expectedOutputs := map[string]*states.OutputValue{ - "test": &states.OutputValue{ + "test": { Addr: addrs.AbsOutputValue{ OutputValue: addrs.OutputValue{ Name: "test",