| // Copyright IBM Corp. 2014, 2026 | |
| // SPDX-License-Identifier: BUSL-1.1 | |
| package command | |
| import ( | |
| "os" | |
| "path/filepath" | |
| "strings" | |
| "testing" | |
| "github.com/hashicorp/terraform/internal/command/views" | |
| "github.com/hashicorp/terraform/internal/modsdir" | |
| "github.com/hashicorp/terraform/internal/terminal" | |
| ) | |
| func TestInit2_dynamicSourceErrors(t *testing.T) { | |
| tests := map[string]struct { | |
| fixture string | |
| args []string | |
| wantError string | |
| }{ | |
| "version constraint added to previously unversioned module": { | |
| fixture: "add-version-constraint", | |
| args: []string{"-get=false"}, | |
| wantError: "Module version requirements have changed", | |
| }, | |
| "invalid registry source with version argument": { | |
| fixture: "invalid-registry-source-with-module", | |
| wantError: "Invalid registry module source address", | |
| }, | |
| "local source with version argument": { | |
| fixture: "local-source-with-version", | |
| wantError: "Invalid registry module source address", | |
| }, | |
| "non-const variable in module source": { | |
| fixture: "local-source-with-non-const-variable", | |
| args: []string{"-var", "module_name=example"}, | |
| wantError: "Unknown module source", | |
| }, | |
| "resource reference in module source": { | |
| fixture: "source-with-resource-reference", | |
| wantError: "Invalid module source", | |
| }, | |
| "module output reference in module source": { | |
| fixture: "source-with-module-output-reference", | |
| wantError: "Invalid module source", | |
| }, | |
| "each.key in module source": { | |
| fixture: "each-in-module-source", | |
| wantError: "Invalid module source", | |
| }, | |
| "count.index in module source": { | |
| fixture: "count-in-module-source", | |
| wantError: "Invalid module source", | |
| }, | |
| "terraform.workspace in module source": { | |
| fixture: "terraform-attr-in-module-source", | |
| wantError: "Invalid module source", | |
| }, | |
| "required const variable not set": { | |
| fixture: "local-source-with-variable", | |
| wantError: "No value for required variable", | |
| }, | |
| "override default with nonexistent module": { | |
| fixture: "local-source-with-variable-default", | |
| args: []string{"-var", "module_name=nonexistent"}, | |
| wantError: "", // any error; the module directory doesn't exist | |
| }, | |
| "version mismatch with dynamic constraint": { | |
| fixture: "plan-with-version-mismatch", | |
| args: []string{"-get=false", "-var", "module_version=0.0.2"}, | |
| wantError: "Module version requirements have changed", | |
| }, | |
| "const variable with static validation check": { | |
| fixture: "const-var-source-with-validation", | |
| args: []string{"-var", "module_name=nonexistent"}, | |
| wantError: "The module_name variable must be set to \"example\"", | |
| }, | |
| // This error is in addition to the expected validation error in the test case above and is | |
| // a consequence of validation blocks having their own node in the graph, thus a validation | |
| // error doesn't prevent the module install node from executing. | |
| "const variable with static validation check - module install bug": { | |
| fixture: "const-var-source-with-validation", | |
| args: []string{"-var", "module_name=nonexistent"}, | |
| wantError: "Unable to evaluate directory symlink: lstat modules/nonexistent", | |
| }, | |
| // we can't determine if a provider function fails somewhere in the | |
| // evaluation chain during init, so we have to return a generic error | |
| // about unknown values. | |
| "provider function in module source": { | |
| fixture: "provider-function-in-source", | |
| wantError: "Only literal values and const variables can be evaluated during init.", | |
| }, | |
| "provider function in module version": { | |
| fixture: "provider-function-in-version", | |
| wantError: "Only literal values and const variables can be evaluated during init.", | |
| }, | |
| } | |
| for name, tc := range tests { | |
| t.Run(name, func(t *testing.T) { | |
| td := t.TempDir() | |
| testCopyDir(t, testFixturePath(filepath.Join("dynamic-module-sources", tc.fixture)), td) | |
| t.Chdir(td) | |
| providerSource := newMockProviderSource(t, map[string][]string{ | |
| "hashicorp/test": {"1.0.0"}, | |
| }) | |
| ui := testUiWrapped(t) | |
| view, done := testView(t) | |
| c := &InitCommand{ | |
| Meta: Meta{ | |
| testingOverrides: metaOverridesForProvider(testProvider()), | |
| Ui: ui, | |
| View: view, | |
| ProviderSource: providerSource, | |
| }, | |
| } | |
| code := c.Run(tc.args) | |
| testOutput := done(t) | |
| if code != 1 { | |
| t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, testOutput.Stderr(), testOutput.Stdout()) | |
| } | |
| if tc.wantError != "" { | |
| got := testOutput.All() | |
| if !strings.Contains(got, tc.wantError) { | |
| t.Fatalf("wrong error\ngot:\n%s\n\nwant: containing %q", got, tc.wantError) | |
| } | |
| } | |
| }) | |
| } | |
| } | |
| func TestInit2_dynamicSourceSuccess(t *testing.T) { | |
| tests := map[string]struct { | |
| fixture string | |
| args []string | |
| }{ | |
| "const variable via -var": { | |
| fixture: "local-source-with-variable", | |
| args: []string{"-var", "module_name=example"}, | |
| }, | |
| "const variable with default value": { | |
| fixture: "local-source-with-variable-default", | |
| }, | |
| "local value referencing const variable": { | |
| fixture: "local-source-with-local-value", | |
| args: []string{"-var", "module_name=example"}, | |
| }, | |
| "nested module with variable passed through parent": { | |
| fixture: "nested-module-with-variable-source", | |
| args: []string{"-var", "child_name=child"}, | |
| }, | |
| "const variable from tfvars file": { | |
| fixture: "local-source-with-varsfile", | |
| args: []string{"-var-file", "test.tfvars"}, | |
| }, | |
| "path.module in module source": { | |
| fixture: "path-attr-in-module-source", | |
| }, | |
| "const variable with static validation check": { | |
| fixture: "const-var-source-with-validation", | |
| args: []string{"-var", "module_name=example"}, | |
| }, | |
| "const variable with validation referencing non-const variable": { | |
| fixture: "const-var-with-validation-and-reference", | |
| // the non_const_var variable is not defined here since we are only testing init | |
| // so the value will always be unknown regardless of input. | |
| args: []string{"-var", "const_var=hello"}, | |
| }, | |
| // The validation returns unknown during init, and must wait until | |
| // a full validation is actually run. | |
| "provider function in const variable validation check": { | |
| fixture: "provider-function-in-const-validation", | |
| args: []string{"-var", "module_name=example"}, | |
| }, | |
| } | |
| for name, tc := range tests { | |
| t.Run(name, func(t *testing.T) { | |
| td := t.TempDir() | |
| testCopyDir(t, testFixturePath(filepath.Join("dynamic-module-sources", tc.fixture)), td) | |
| t.Chdir(td) | |
| providerSource := newMockProviderSource(t, map[string][]string{ | |
| "hashicorp/test": {"1.0.0"}, | |
| }) | |
| ui := testUiWrapped(t) | |
| view, done := testView(t) | |
| c := &InitCommand{ | |
| Meta: Meta{ | |
| testingOverrides: metaOverridesForProvider(testProvider()), | |
| Ui: ui, | |
| View: view, | |
| ProviderSource: providerSource, | |
| }, | |
| } | |
| code := c.Run(tc.args) | |
| testOutput := done(t) | |
| if code != 0 { | |
| t.Fatalf("got exit status %d; want 0\nstderr:\n%s\n\nstdout:\n%s", code, testOutput.Stderr(), testOutput.Stdout()) | |
| } | |
| }) | |
| } | |
| } | |
| func TestInit2_dynamicSourceInTestModule(t *testing.T) { | |
| tests := map[string]struct { | |
| fixture string | |
| args []string | |
| manifestKey string | |
| }{ | |
| "required variable": { | |
| fixture: "test-module-required-variable", | |
| args: []string{"-var", "module_src=../modules/simple"}, | |
| manifestKey: "test.main.dynamic_source.const_var_source", | |
| }, | |
| "module in same directory": { | |
| fixture: "test-module-same-directory", | |
| manifestKey: "test.main.dynamic_source.const_var_source", | |
| }, | |
| "local value": { | |
| fixture: "test-module-local-value", | |
| manifestKey: "test.main.dynamic_source.const_var_source", | |
| }, | |
| "nested test file": { | |
| fixture: "test-module-nested-test-file", | |
| manifestKey: "test.tests.dynamic.dynamic_source.const_var_source", | |
| }, | |
| } | |
| for name, tc := range tests { | |
| t.Run(name, func(t *testing.T) { | |
| td := t.TempDir() | |
| testCopyDir(t, testFixturePath(filepath.Join("dynamic-module-sources", tc.fixture)), td) | |
| t.Chdir(td) | |
| streams, done := terminal.StreamsForTesting(t) | |
| meta := Meta{ | |
| Ui: testUiWrapped(t), | |
| View: views.NewView(streams), | |
| Streams: streams, | |
| } | |
| init := &InitCommand{Meta: meta} | |
| if code := init.Run(tc.args); code != 0 { | |
| output := done(t) | |
| t.Fatalf("init failed with exit status %d\nstderr:\n%s\n\nstdout:\n%s", code, output.Stderr(), output.Stdout()) | |
| } | |
| done(t) | |
| manifest, err := modsdir.ReadManifestSnapshotForDir(filepath.Join(td, ".terraform", "modules")) | |
| if err != nil { | |
| t.Fatalf("failed to read module manifest: %s", err) | |
| } | |
| if _, exists := manifest[tc.manifestKey]; !exists { | |
| t.Errorf("module manifest does not contain %q", tc.manifestKey) | |
| } | |
| streams, done = terminal.StreamsForTesting(t) | |
| meta.Streams = streams | |
| meta.View = views.NewView(streams) | |
| test := &TestCommand{Meta: meta} | |
| code := test.Run(append(tc.args, "-no-color")) | |
| output := done(t) | |
| if code != 0 { | |
| t.Fatalf("test failed with exit status %d\nstderr:\n%s\n\nstdout:\n%s", code, output.Stderr(), output.Stdout()) | |
| } | |
| if !strings.Contains(output.Stdout(), "1 passed, 0 failed.") { | |
| t.Fatalf("expected test to pass, got:\n%s", output.Stdout()) | |
| } | |
| }) | |
| } | |
| } | |
| func TestInit2_getFalseWithDynamicSource(t *testing.T) { | |
| td := t.TempDir() | |
| testCopyDir(t, testFixturePath(filepath.Join("dynamic-module-sources", "get-false-with-dynamic-source")), td) | |
| t.Chdir(td) | |
| // First, run init normally to install the module | |
| ui := testUiWrapped(t) | |
| view, done := testView(t) | |
| c := &InitCommand{ | |
| Meta: Meta{ | |
| testingOverrides: metaOverridesForProvider(testProvider()), | |
| Ui: ui, | |
| View: view, | |
| }, | |
| } | |
| args := []string{"-var", "module_name=example"} | |
| code := c.Run(args) | |
| testOutput := done(t) | |
| if code != 0 { | |
| t.Fatalf("first init failed with exit status %d\nstderr:\n%s\n\nstdout:\n%s", code, testOutput.Stderr(), testOutput.Stdout()) | |
| } | |
| // Now run init with -get=false; should succeed since modules are already installed | |
| ui2 := testUiWrapped(t) | |
| view2, done2 := testView(t) | |
| c2 := &InitCommand{ | |
| Meta: Meta{ | |
| testingOverrides: metaOverridesForProvider(testProvider()), | |
| Ui: ui2, | |
| View: view2, | |
| }, | |
| } | |
| args2 := []string{"-get=false", "-var", "module_name=example"} | |
| code = c2.Run(args2) | |
| testOutput2 := done2(t) | |
| if code != 0 { | |
| t.Fatalf("init -get=false failed with exit status %d\nstderr:\n%s\n\nstdout:\n%s", code, testOutput2.Stderr(), testOutput2.Stdout()) | |
| } | |
| } | |
| func TestInit2_getFalseWithDynamicSourceNotInstalled(t *testing.T) { | |
| td := t.TempDir() | |
| testCopyDir(t, testFixturePath(filepath.Join("dynamic-module-sources", "get-false-with-dynamic-source")), td) | |
| t.Chdir(td) | |
| ui := testUiWrapped(t) | |
| view, done := testView(t) | |
| c := &InitCommand{ | |
| Meta: Meta{ | |
| testingOverrides: metaOverridesForProvider(testProvider()), | |
| Ui: ui, | |
| View: view, | |
| }, | |
| } | |
| // Run init with -get=false without having installed modules first | |
| args := []string{"-get=false", "-var", "module_name=example"} | |
| code := c.Run(args) | |
| testOutput := done(t) | |
| if code != 1 { | |
| t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, testOutput.Stderr(), testOutput.Stdout()) | |
| } | |
| } | |
| func TestInit2_reinitWithDifferentVariable(t *testing.T) { | |
| td := t.TempDir() | |
| testCopyDir(t, testFixturePath(filepath.Join("dynamic-module-sources", "local-source-with-variable-default")), td) | |
| t.Chdir(td) | |
| // First init with default variable (example) | |
| ui := testUiWrapped(t) | |
| view, done := testView(t) | |
| c := &InitCommand{ | |
| Meta: Meta{ | |
| testingOverrides: metaOverridesForProvider(testProvider()), | |
| Ui: ui, | |
| View: view, | |
| }, | |
| } | |
| code := c.Run([]string{}) | |
| testOutput := done(t) | |
| if code != 0 { | |
| t.Fatalf("first init failed with exit status %d\nstderr:\n%s\n\nstdout:\n%s", code, testOutput.Stderr(), testOutput.Stdout()) | |
| } | |
| // Re-init with different variable | |
| ui2 := testUiWrapped(t) | |
| view2, done2 := testView(t) | |
| c2 := &InitCommand{ | |
| Meta: Meta{ | |
| testingOverrides: metaOverridesForProvider(testProvider()), | |
| Ui: ui2, | |
| View: view2, | |
| }, | |
| } | |
| code = c2.Run([]string{"-var", "module_name=alternate"}) | |
| testOutput2 := done2(t) | |
| if code != 0 { | |
| t.Fatalf("second init failed with exit status %d\nstderr:\n%s\n\nstdout:\n%s", code, testOutput2.Stderr(), testOutput2.Stdout()) | |
| } | |
| } | |
| func TestInit2_fromModuleWithDynamicSource(t *testing.T) { | |
| // TODO: -from-module currently errors when the copied configuration | |
| // contains a dynamic module source (e.g. "./modules/${var.module_name}"). | |
| t.Skip("skipping: -from-module errors on dynamic module sources (see TODO in from_module.go)") | |
| // Create an empty target directory for -from-module to copy into | |
| td := t.TempDir() | |
| t.Chdir(td) | |
| ui := testUiWrapped(t) | |
| view, done := testView(t) | |
| c := &InitCommand{ | |
| Meta: Meta{ | |
| testingOverrides: metaOverridesForProvider(testProvider()), | |
| Ui: ui, | |
| View: view, | |
| }, | |
| } | |
| // Use -from-module to copy the source module (which has a dynamic source) | |
| // into the empty working directory. This should copy the files but the | |
| // nested dynamic module won't be resolved by -from-module itself. | |
| srcDir := testFixturePath(filepath.Join("dynamic-module-sources", "from-module-with-dynamic-source", "source-module")) | |
| args := []string{"-from-module=" + srcDir, "-var", "module_name=test"} | |
| code := c.Run(args) | |
| testOutput := done(t) | |
| // -from-module should succeed in copying. The dynamic module source | |
| // within the copied configuration won't be resolved yet — that requires | |
| // a separate init with the variable value. | |
| if code != 0 { | |
| t.Fatalf("init -from-module failed with exit status %d\nstderr:\n%s\n\nstdout:\n%s", code, testOutput.Stderr(), testOutput.Stdout()) | |
| } | |
| // Verify the main.tf was copied | |
| if _, err := os.Stat(filepath.Join(td, "main.tf")); os.IsNotExist(err) { | |
| t.Fatal("main.tf was not copied from the source module") | |
| } | |
| } | |
| func TestPlan_dynamicModuleSource(t *testing.T) { | |
| td := t.TempDir() | |
| testCopyDir(t, testFixturePath(filepath.Join("dynamic-module-sources", "plan-with-dynamic-source")), td) | |
| t.Chdir(td) | |
| p := planFixtureProvider() | |
| providerSource := newMockProviderSource(t, map[string][]string{ | |
| "hashicorp/test": {"1.0.0"}, | |
| }) | |
| args := []string{"-var", "module_name=example"} | |
| initUi := testUiWrapped(t) | |
| initView, initDone := testView(t) | |
| initCmd := &InitCommand{ | |
| Meta: Meta{ | |
| testingOverrides: metaOverridesForProvider(p), | |
| Ui: initUi, | |
| View: initView, | |
| ProviderSource: providerSource, | |
| }, | |
| } | |
| initCode := initCmd.Run(args) | |
| initOutput := initDone(t) | |
| if initCode != 0 { | |
| t.Fatalf("init failed with exit status %d\nstderr:\n%s\n\nstdout:\n%s", initCode, initOutput.Stderr(), initOutput.Stdout()) | |
| } | |
| // Now run plan | |
| planUi := testUiWrapped(t) | |
| planView, planDone := testView(t) | |
| planCmd := &PlanCommand{ | |
| Meta: Meta{ | |
| testingOverrides: metaOverridesForProvider(p), | |
| Ui: planUi, | |
| View: planView, | |
| }, | |
| } | |
| planCode := planCmd.Run(args) | |
| planOutput := planDone(t) | |
| if planCode != 0 { | |
| t.Fatalf("plan failed with exit status %d\nstderr:\n%s\n\nstdout:\n%s", planCode, planOutput.Stderr(), planOutput.Stdout()) | |
| } | |
| output := planOutput.Stdout() | |
| if !strings.Contains(output, "1 to add") { | |
| t.Fatalf("expected plan to show 1 resource to add, got:\n%s", output) | |
| } | |
| } | |
| func TestPlan_dynamicModuleSourceMismatch(t *testing.T) { | |
| td := t.TempDir() | |
| testCopyDir(t, testFixturePath(filepath.Join("dynamic-module-sources", "plan-with-dynamic-source")), td) | |
| t.Chdir(td) | |
| p := planFixtureProvider() | |
| providerSource := newMockProviderSource(t, map[string][]string{ | |
| "hashicorp/test": {"1.0.0"}, | |
| }) | |
| args := []string{"-var", "module_name=example"} | |
| initUi := testUiWrapped(t) | |
| initView, initDone := testView(t) | |
| initCmd := &InitCommand{ | |
| Meta: Meta{ | |
| testingOverrides: metaOverridesForProvider(p), | |
| Ui: initUi, | |
| View: initView, | |
| ProviderSource: providerSource, | |
| }, | |
| } | |
| initCode := initCmd.Run(args) | |
| initOutput := initDone(t) | |
| if initCode != 0 { | |
| t.Fatalf("init failed with exit status %d\nstderr:\n%s\n\nstdout:\n%s", initCode, initOutput.Stderr(), initOutput.Stdout()) | |
| } | |
| // Now run plan with a different variable value | |
| planUi := testUiWrapped(t) | |
| planView, planDone := testView(t) | |
| planCmd := &PlanCommand{ | |
| Meta: Meta{ | |
| testingOverrides: metaOverridesForProvider(p), | |
| Ui: planUi, | |
| View: planView, | |
| }, | |
| } | |
| planArgs := []string{"-var", "module_name=nonexistent"} | |
| code := planCmd.Run(planArgs) | |
| planOutput := planDone(t) | |
| if code == 0 { | |
| t.Fatalf("expected plan to fail, but got exit status 0\nstdout:\n%s", planOutput.Stdout()) | |
| } | |
| } | |
| func TestApply_dynamicModuleSource(t *testing.T) { | |
| td := t.TempDir() | |
| testCopyDir(t, testFixturePath(filepath.Join("dynamic-module-sources", "apply-with-dynamic-source")), td) | |
| t.Chdir(td) | |
| p := planFixtureProvider() | |
| providerSource := newMockProviderSource(t, map[string][]string{ | |
| "hashicorp/test": {"1.0.0"}, | |
| }) | |
| args := []string{"-var", "module_name=example"} | |
| initUi := testUiWrapped(t) | |
| initView, initDone := testView(t) | |
| initCmd := &InitCommand{ | |
| Meta: Meta{ | |
| testingOverrides: metaOverridesForProvider(p), | |
| Ui: initUi, | |
| View: initView, | |
| ProviderSource: providerSource, | |
| }, | |
| } | |
| initCode := initCmd.Run(args) | |
| initOutput := initDone(t) | |
| if initCode != 0 { | |
| t.Fatalf("init failed with exit status %d\nstderr:\n%s\n\nstdout:\n%s", initCode, initOutput.Stderr(), initOutput.Stdout()) | |
| } | |
| applyUi := testUiWrapped(t) | |
| applyView, applyDone := testView(t) | |
| applyCmd := &ApplyCommand{ | |
| Meta: Meta{ | |
| testingOverrides: metaOverridesForProvider(p), | |
| Ui: applyUi, | |
| View: applyView, | |
| }, | |
| } | |
| applyArgs := []string{"-auto-approve", "-var", "module_name=example"} | |
| code := applyCmd.Run(applyArgs) | |
| applyOutput := applyDone(t) | |
| if code != 0 { | |
| t.Fatalf("apply failed with exit status %d\nstderr:\n%s\n\nstdout:\n%s", code, applyOutput.Stderr(), applyOutput.Stdout()) | |
| } | |
| output := applyOutput.Stdout() | |
| if !strings.Contains(output, "Apply complete!") { | |
| t.Fatalf("expected apply to succeed, got:\n%s", output) | |
| } | |
| } | |
| func TestApply_dynamicModuleSourceWithDefaultPlanFile(t *testing.T) { | |
| td := t.TempDir() | |
| testCopyDir(t, testFixturePath(filepath.Join("dynamic-module-sources", "apply-plan-with-dynamic-source")), td) | |
| t.Chdir(td) | |
| p := planFixtureProvider() | |
| providerSource := newMockProviderSource(t, map[string][]string{ | |
| "hashicorp/test": {"1.0.0"}, | |
| }) | |
| initUi := testUiWrapped(t) | |
| initView, initDone := testView(t) | |
| initCmd := &InitCommand{ | |
| Meta: Meta{ | |
| testingOverrides: metaOverridesForProvider(p), | |
| Ui: initUi, | |
| View: initView, | |
| ProviderSource: providerSource, | |
| }, | |
| } | |
| initCode := initCmd.Run([]string{}) | |
| initOutput := initDone(t) | |
| if initCode != 0 { | |
| t.Fatalf("init failed with exit status %d\nstderr:\n%s\n\nstdout:\n%s", initCode, initOutput.Stderr(), initOutput.Stdout()) | |
| } | |
| // Run plan with -out | |
| planPath := filepath.Join(td, "saved.plan") | |
| planUi := testUiWrapped(t) | |
| planView, planDone := testView(t) | |
| planCmd := &PlanCommand{ | |
| Meta: Meta{ | |
| testingOverrides: metaOverridesForProvider(p), | |
| Ui: planUi, | |
| View: planView, | |
| }, | |
| } | |
| planArgs := []string{"-out", planPath} | |
| code := planCmd.Run(planArgs) | |
| planOutput := planDone(t) | |
| if code != 0 { | |
| t.Fatalf("plan failed with exit status %d\nstderr:\n%s\n\nstdout:\n%s", code, planOutput.Stderr(), planOutput.Stdout()) | |
| } | |
| // Verify the plan file was created | |
| if _, err := os.Stat(planPath); os.IsNotExist(err) { | |
| t.Fatalf("plan file was not created at %s", planPath) | |
| } | |
| // Apply the saved plan | |
| applyUi := testUiWrapped(t) | |
| applyView, applyDone := testView(t) | |
| applyCmd := &ApplyCommand{ | |
| Meta: Meta{ | |
| testingOverrides: metaOverridesForProvider(p), | |
| Ui: applyUi, | |
| View: applyView, | |
| }, | |
| } | |
| applyArgs := []string{planPath} | |
| code = applyCmd.Run(applyArgs) | |
| applyOutput := applyDone(t) | |
| if code != 0 { | |
| t.Fatalf("apply failed with exit status %d\nstderr:\n%s\n\nstdout:\n%s", code, applyOutput.Stderr(), applyOutput.Stdout()) | |
| } | |
| output := applyOutput.Stdout() | |
| if !strings.Contains(output, "Apply complete!") { | |
| t.Fatalf("expected apply to succeed, got:\n%s", output) | |
| } | |
| } | |
| func TestPlan_dynamicModuleSourceWithCount(t *testing.T) { | |
| td := t.TempDir() | |
| testCopyDir(t, testFixturePath(filepath.Join("dynamic-module-sources", "module-with-count")), td) | |
| t.Chdir(td) | |
| p := planFixtureProvider() | |
| providerSource := newMockProviderSource(t, map[string][]string{ | |
| "hashicorp/test": {"1.0.0"}, | |
| }) | |
| args := []string{"-var", "module_name=example"} | |
| initUi := testUiWrapped(t) | |
| initView, initDone := testView(t) | |
| initCmd := &InitCommand{ | |
| Meta: Meta{ | |
| testingOverrides: metaOverridesForProvider(p), | |
| Ui: initUi, | |
| View: initView, | |
| ProviderSource: providerSource, | |
| }, | |
| } | |
| initCode := initCmd.Run(args) | |
| initOutput := initDone(t) | |
| if initCode != 0 { | |
| t.Fatalf("init failed with exit status %d\nstderr:\n%s\n\nstdout:\n%s", initCode, initOutput.Stderr(), initOutput.Stdout()) | |
| } | |
| // Now run plan | |
| planUi := testUiWrapped(t) | |
| planView, planDone := testView(t) | |
| planCmd := &PlanCommand{ | |
| Meta: Meta{ | |
| testingOverrides: metaOverridesForProvider(p), | |
| Ui: planUi, | |
| View: planView, | |
| }, | |
| } | |
| planCode := planCmd.Run(args) | |
| planOutput := planDone(t) | |
| if planCode != 0 { | |
| t.Fatalf("plan failed with exit status %d\nstderr:\n%s\n\nstdout:\n%s", planCode, planOutput.Stderr(), planOutput.Stdout()) | |
| } | |
| output := planOutput.Stdout() | |
| if !strings.Contains(output, "2 to add") { | |
| t.Fatalf("expected plan to show 2 resources to add, got:\n%s", output) | |
| } | |
| } | |
| func TestPlan_dynamicModuleSourceWithForEach(t *testing.T) { | |
| td := t.TempDir() | |
| testCopyDir(t, testFixturePath(filepath.Join("dynamic-module-sources", "module-with-for-each")), td) | |
| t.Chdir(td) | |
| p := planFixtureProvider() | |
| providerSource := newMockProviderSource(t, map[string][]string{ | |
| "hashicorp/test": {"1.0.0"}, | |
| }) | |
| args := []string{"-var", "module_name=example"} | |
| initUi := testUiWrapped(t) | |
| initView, initDone := testView(t) | |
| initCmd := &InitCommand{ | |
| Meta: Meta{ | |
| testingOverrides: metaOverridesForProvider(p), | |
| Ui: initUi, | |
| View: initView, | |
| ProviderSource: providerSource, | |
| }, | |
| } | |
| initCode := initCmd.Run(args) | |
| initOutput := initDone(t) | |
| if initCode != 0 { | |
| t.Fatalf("init failed with exit status %d\nstderr:\n%s\n\nstdout:\n%s", initCode, initOutput.Stderr(), initOutput.Stdout()) | |
| } | |
| // Now run plan | |
| planUi := testUiWrapped(t) | |
| planView, planDone := testView(t) | |
| planCmd := &PlanCommand{ | |
| Meta: Meta{ | |
| testingOverrides: metaOverridesForProvider(p), | |
| Ui: planUi, | |
| View: planView, | |
| }, | |
| } | |
| planCode := planCmd.Run(args) | |
| planOutput := planDone(t) | |
| if planCode != 0 { | |
| t.Fatalf("plan failed with exit status %d\nstderr:\n%s\n\nstdout:\n%s", planCode, planOutput.Stderr(), planOutput.Stdout()) | |
| } | |
| output := planOutput.Stdout() | |
| if !strings.Contains(output, "2 to add") { | |
| t.Fatalf("expected plan to show 2 resources to add, got:\n%s", output) | |
| } | |
| } | |
| func TestPlan_dynamicModuleVersionMismatch(t *testing.T) { | |
| td := t.TempDir() | |
| testCopyDir(t, testFixturePath(filepath.Join("dynamic-module-sources", "plan-with-version-mismatch")), td) | |
| t.Chdir(td) | |
| p := planFixtureProvider() | |
| // Plan should fail because the installed module version (0.0.1 in | |
| // modules.json) doesn't satisfy the constraint we provide. | |
| planUi := testUiWrapped(t) | |
| planView, planDone := testView(t) | |
| planCmd := &PlanCommand{ | |
| Meta: Meta{ | |
| testingOverrides: metaOverridesForProvider(p), | |
| Ui: planUi, | |
| View: planView, | |
| }, | |
| } | |
| planArgs := []string{"-var", "module_version=0.0.2"} | |
| code := planCmd.Run(planArgs) | |
| planOutput := planDone(t) | |
| if code == 0 { | |
| t.Fatalf("expected plan to fail, but got exit status 0\nstdout:\n%s", planOutput.Stdout()) | |
| } | |
| got := planOutput.All() | |
| want := "Module version requirements have changed" | |
| if !strings.Contains(got, want) { | |
| t.Fatalf("wrong error\ngot:\n%s\n\nwant: containing %q", got, want) | |
| } | |
| } |