mirror of
https://github.com/hashicorp/terraform.git
synced 2026-06-09 00:42:48 -04:00
Add more dynamic module sources tests
This commit is contained in:
parent
b6804e2edd
commit
45ba6796ba
40 changed files with 850 additions and 65 deletions
|
|
@ -4,6 +4,7 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
|
@ -11,11 +12,197 @@ import (
|
|||
"github.com/hashicorp/cli"
|
||||
)
|
||||
|
||||
func TestInit2_versionConstraintAdded(t *testing.T) {
|
||||
// This test is for what happens when there is a version constraint added
|
||||
// to a module that previously didn't have one.
|
||||
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: "Invalid 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",
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
view, done := testView(t)
|
||||
c := &InitCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
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",
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
view, done := testView(t)
|
||||
c := &InitCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
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_getFalseWithDynamicSource(t *testing.T) {
|
||||
td := t.TempDir()
|
||||
testCopyDir(t, testFixturePath(filepath.Join("dynamic-module-sources", "add-version-constraint")), td)
|
||||
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 := new(cli.MockUi)
|
||||
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 := new(cli.MockUi)
|
||||
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 := new(cli.MockUi)
|
||||
|
|
@ -28,74 +215,459 @@ func TestInit2_versionConstraintAdded(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
args := []string{"-get=false"}
|
||||
// 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())
|
||||
}
|
||||
got := testOutput.All()
|
||||
}
|
||||
|
||||
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 := new(cli.MockUi)
|
||||
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 := new(cli.MockUi)
|
||||
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 panics when the copied configuration
|
||||
// contains a dynamic module source (e.g. "./modules/${var.module_name}").
|
||||
t.Skip("skipping: -from-module panics 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 := new(cli.MockUi)
|
||||
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}
|
||||
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, close := newMockProviderSource(t, map[string][]string{
|
||||
"hashicorp/test": {"1.0.0"},
|
||||
})
|
||||
defer close()
|
||||
|
||||
args := []string{"-var", "module_name=example"}
|
||||
|
||||
initUi := new(cli.MockUi)
|
||||
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 := new(cli.MockUi)
|
||||
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, close := newMockProviderSource(t, map[string][]string{
|
||||
"hashicorp/test": {"1.0.0"},
|
||||
})
|
||||
defer close()
|
||||
args := []string{"-var", "module_name=example"}
|
||||
|
||||
initUi := new(cli.MockUi)
|
||||
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 := new(cli.MockUi)
|
||||
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, close := newMockProviderSource(t, map[string][]string{
|
||||
"hashicorp/test": {"1.0.0"},
|
||||
})
|
||||
defer close()
|
||||
args := []string{"-var", "module_name=example"}
|
||||
|
||||
initUi := new(cli.MockUi)
|
||||
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 := new(cli.MockUi)
|
||||
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, close := newMockProviderSource(t, map[string][]string{
|
||||
"hashicorp/test": {"1.0.0"},
|
||||
})
|
||||
defer close()
|
||||
|
||||
initUi := new(cli.MockUi)
|
||||
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 := new(cli.MockUi)
|
||||
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 := new(cli.MockUi)
|
||||
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, close := newMockProviderSource(t, map[string][]string{
|
||||
"hashicorp/test": {"1.0.0"},
|
||||
})
|
||||
defer close()
|
||||
|
||||
args := []string{"-var", "module_name=example"}
|
||||
|
||||
initUi := new(cli.MockUi)
|
||||
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 := new(cli.MockUi)
|
||||
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, close := newMockProviderSource(t, map[string][]string{
|
||||
"hashicorp/test": {"1.0.0"},
|
||||
})
|
||||
defer close()
|
||||
|
||||
args := []string{"-var", "module_name=example"}
|
||||
|
||||
initUi := new(cli.MockUi)
|
||||
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 := new(cli.MockUi)
|
||||
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 := new(cli.MockUi)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInit2_invalidRegistrySourceWithModule(t *testing.T) {
|
||||
td := t.TempDir()
|
||||
testCopyDir(t, testFixturePath(filepath.Join("dynamic-module-sources", "invalid-registry-source-with-module")), td)
|
||||
t.Chdir(td)
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
view, done := testView(t)
|
||||
c := &InitCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{}
|
||||
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())
|
||||
}
|
||||
got := testOutput.All()
|
||||
|
||||
want := "Invalid registry module source address"
|
||||
if !strings.Contains(got, want) {
|
||||
t.Fatalf("wrong error\ngot:\n%s\n\nwant: containing %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInit2_localSourceWithVersion(t *testing.T) {
|
||||
td := t.TempDir()
|
||||
testCopyDir(t, testFixturePath(filepath.Join("dynamic-module-sources", "local-source-with-version")), td)
|
||||
t.Chdir(td)
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
view, done := testView(t)
|
||||
c := &InitCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{}
|
||||
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())
|
||||
}
|
||||
got := testOutput.All()
|
||||
|
||||
want := "Invalid registry module source address"
|
||||
if !strings.Contains(got, want) {
|
||||
t.Fatalf("wrong error\ngot:\n%s\n\nwant: containing %q", got, want)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
internal/command/testdata/dynamic-module-sources/apply-plan-with-dynamic-source/main.tf
vendored
Normal file
9
internal/command/testdata/dynamic-module-sources/apply-plan-with-dynamic-source/main.tf
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
variable "module_name" {
|
||||
type = string
|
||||
const = true
|
||||
default = "example"
|
||||
}
|
||||
|
||||
module "example" {
|
||||
source = "./modules/${var.module_name}"
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
resource "test_instance" "example" {
|
||||
ami = "bar"
|
||||
}
|
||||
8
internal/command/testdata/dynamic-module-sources/apply-with-dynamic-source/main.tf
vendored
Normal file
8
internal/command/testdata/dynamic-module-sources/apply-with-dynamic-source/main.tf
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
variable "module_name" {
|
||||
type = string
|
||||
const = true
|
||||
}
|
||||
|
||||
module "example" {
|
||||
source = "./modules/${var.module_name}"
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
resource "test_instance" "example" {
|
||||
ami = "bar"
|
||||
}
|
||||
4
internal/command/testdata/dynamic-module-sources/count-in-module-source/main.tf
vendored
Normal file
4
internal/command/testdata/dynamic-module-sources/count-in-module-source/main.tf
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
module "example" {
|
||||
count = 2
|
||||
source = "./modules/${count.index}"
|
||||
}
|
||||
4
internal/command/testdata/dynamic-module-sources/each-in-module-source/main.tf
vendored
Normal file
4
internal/command/testdata/dynamic-module-sources/each-in-module-source/main.tf
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
module "example" {
|
||||
for_each = toset(["one", "two"])
|
||||
source = "./modules/${each.key}"
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
variable "module_name" {
|
||||
type = string
|
||||
const = true
|
||||
}
|
||||
|
||||
module "example" {
|
||||
source = "./modules/${var.module_name}"
|
||||
}
|
||||
8
internal/command/testdata/dynamic-module-sources/get-false-with-dynamic-source/main.tf
vendored
Normal file
8
internal/command/testdata/dynamic-module-sources/get-false-with-dynamic-source/main.tf
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
variable "module_name" {
|
||||
type = string
|
||||
const = true
|
||||
}
|
||||
|
||||
module "example" {
|
||||
source = "./modules/${var.module_name}"
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
|
||||
12
internal/command/testdata/dynamic-module-sources/local-source-with-local-value/main.tf
vendored
Normal file
12
internal/command/testdata/dynamic-module-sources/local-source-with-local-value/main.tf
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
variable "module_name" {
|
||||
type = string
|
||||
const = true
|
||||
}
|
||||
|
||||
locals {
|
||||
module_path = "./modules/${var.module_name}"
|
||||
}
|
||||
|
||||
module "example" {
|
||||
source = local.module_path
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
variable "module_name" {
|
||||
type = string
|
||||
}
|
||||
|
||||
module "example" {
|
||||
source = "./modules/${var.module_name}"
|
||||
}
|
||||
9
internal/command/testdata/dynamic-module-sources/local-source-with-variable-default/main.tf
vendored
Normal file
9
internal/command/testdata/dynamic-module-sources/local-source-with-variable-default/main.tf
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
variable "module_name" {
|
||||
type = string
|
||||
const = true
|
||||
default = "example"
|
||||
}
|
||||
|
||||
module "example" {
|
||||
source = "./modules/${var.module_name}"
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
|
||||
8
internal/command/testdata/dynamic-module-sources/local-source-with-variable/main.tf
vendored
Normal file
8
internal/command/testdata/dynamic-module-sources/local-source-with-variable/main.tf
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
variable "module_name" {
|
||||
type = string
|
||||
const = true
|
||||
}
|
||||
|
||||
module "example" {
|
||||
source = "./modules/${var.module_name}"
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
|
||||
8
internal/command/testdata/dynamic-module-sources/local-source-with-varsfile/main.tf
vendored
Normal file
8
internal/command/testdata/dynamic-module-sources/local-source-with-varsfile/main.tf
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
variable "module_name" {
|
||||
type = string
|
||||
const = true
|
||||
}
|
||||
|
||||
module "example" {
|
||||
source = "./modules/${var.module_name}"
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
|
||||
1
internal/command/testdata/dynamic-module-sources/local-source-with-varsfile/test.tfvars
vendored
Normal file
1
internal/command/testdata/dynamic-module-sources/local-source-with-varsfile/test.tfvars
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
module_name = "example"
|
||||
10
internal/command/testdata/dynamic-module-sources/module-with-count/main.tf
vendored
Normal file
10
internal/command/testdata/dynamic-module-sources/module-with-count/main.tf
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
variable "module_name" {
|
||||
type = string
|
||||
const = true
|
||||
}
|
||||
|
||||
module "example" {
|
||||
source = "./modules/${var.module_name}"
|
||||
count = 2
|
||||
number = count.index
|
||||
}
|
||||
7
internal/command/testdata/dynamic-module-sources/module-with-count/modules/example/main.tf
vendored
Normal file
7
internal/command/testdata/dynamic-module-sources/module-with-count/modules/example/main.tf
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
resource "test_instance" "example" {
|
||||
ami = "bar"
|
||||
}
|
||||
|
||||
variable "number" {
|
||||
type = number
|
||||
}
|
||||
10
internal/command/testdata/dynamic-module-sources/module-with-for-each/main.tf
vendored
Normal file
10
internal/command/testdata/dynamic-module-sources/module-with-for-each/main.tf
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
variable "module_name" {
|
||||
type = string
|
||||
const = true
|
||||
}
|
||||
|
||||
module "example" {
|
||||
source = "./modules/${var.module_name}"
|
||||
for_each = toset(["a", "b"])
|
||||
letter = each.value
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
resource "test_instance" "example" {
|
||||
ami = "bar"
|
||||
}
|
||||
|
||||
variable "letter" {
|
||||
type = string
|
||||
}
|
||||
9
internal/command/testdata/dynamic-module-sources/nested-module-with-variable-source/main.tf
vendored
Normal file
9
internal/command/testdata/dynamic-module-sources/nested-module-with-variable-source/main.tf
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
variable "child_name" {
|
||||
type = string
|
||||
const = true
|
||||
}
|
||||
|
||||
module "parent" {
|
||||
source = "./modules/parent"
|
||||
child_name = var.child_name
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
# Empty child module used by dynamic-module-sources tests
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
variable "child_name" {
|
||||
type = string
|
||||
const = true
|
||||
}
|
||||
|
||||
module "child" {
|
||||
source = "../${var.child_name}"
|
||||
}
|
||||
3
internal/command/testdata/dynamic-module-sources/path-attr-in-module-source/main.tf
vendored
Normal file
3
internal/command/testdata/dynamic-module-sources/path-attr-in-module-source/main.tf
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
module "example" {
|
||||
source = "${path.module}/modules/example"
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
|
||||
8
internal/command/testdata/dynamic-module-sources/plan-with-dynamic-source/main.tf
vendored
Normal file
8
internal/command/testdata/dynamic-module-sources/plan-with-dynamic-source/main.tf
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
variable "module_name" {
|
||||
type = string
|
||||
const = true
|
||||
}
|
||||
|
||||
module "example" {
|
||||
source = "./modules/${var.module_name}"
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
resource "test_instance" "example" {
|
||||
ami = "bar"
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"Modules": [
|
||||
{
|
||||
"Key": "",
|
||||
"Source": "",
|
||||
"Dir": ""
|
||||
},
|
||||
{
|
||||
"Key": "child",
|
||||
"Source": "hashicorp/module-installer-acctest/aws",
|
||||
"Version": "0.0.1",
|
||||
"Dir": ".terraform/modules/child"
|
||||
}
|
||||
]
|
||||
}
|
||||
15
internal/command/testdata/dynamic-module-sources/plan-with-version-mismatch/main.tf
vendored
Normal file
15
internal/command/testdata/dynamic-module-sources/plan-with-version-mismatch/main.tf
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# This fixture tests that plan detects a version mismatch when the dynamic
|
||||
# version constraint changes between init and plan.
|
||||
#
|
||||
# The pre-populated .terraform/modules/modules.json records version 0.0.1
|
||||
# but the configuration requires a version determined by the const variable.
|
||||
|
||||
variable "module_version" {
|
||||
type = string
|
||||
const = true
|
||||
}
|
||||
|
||||
module "child" {
|
||||
source = "hashicorp/module-installer-acctest/aws"
|
||||
version = var.module_version
|
||||
}
|
||||
7
internal/command/testdata/dynamic-module-sources/source-with-module-output-reference/main.tf
vendored
Normal file
7
internal/command/testdata/dynamic-module-sources/source-with-module-output-reference/main.tf
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
module "example" {
|
||||
source = "./modules/example"
|
||||
}
|
||||
|
||||
module "example2" {
|
||||
source = "./modules/${module.example.name}"
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
output "name" {
|
||||
value = "example"
|
||||
}
|
||||
5
internal/command/testdata/dynamic-module-sources/source-with-resource-reference/main.tf
vendored
Normal file
5
internal/command/testdata/dynamic-module-sources/source-with-resource-reference/main.tf
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
resource "test_instance" "example" {}
|
||||
|
||||
module "example" {
|
||||
source = "./modules/${test_instance.example.id}"
|
||||
}
|
||||
3
internal/command/testdata/dynamic-module-sources/terraform-attr-in-module-source/main.tf
vendored
Normal file
3
internal/command/testdata/dynamic-module-sources/terraform-attr-in-module-source/main.tf
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
module "example" {
|
||||
source = "./modules/${terraform.workspace}"
|
||||
}
|
||||
|
|
@ -167,7 +167,7 @@ func evalSource(sourceExpr hcl.Expression, hasVersion bool, ctx EvalContext) (ad
|
|||
|
||||
for _, ref := range refs {
|
||||
switch ref.Subject.(type) {
|
||||
case addrs.InputVariable, addrs.LocalValue:
|
||||
case addrs.InputVariable, addrs.LocalValue, addrs.PathAttr:
|
||||
// These are allowed
|
||||
default:
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
|
|
|
|||
Loading…
Reference in a new issue