mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-06 14:13:16 -06:00
095b7e7831
Fixes #11038 This is a **short term fix**. Terraform core doesn't currently handle root modules named "root" well because the prefix `[]string{"root"}` has special meaning and Terraform core [currently] can't disambiguate between the root module and a module named "root" in the root module. This PR introduces a short term fix by simply disallowing root modules named "root". This shouldn't break any BC because since 0.8.0 this didn't work at all in many broken ways (including crashes). Longer term, this should be fixed by removing the special prefix at all and having empty paths be root. I started down this path but the core changes necessary are far too scary for a patch release. We can aim for 0.9.
433 lines
9.3 KiB
Go
433 lines
9.3 KiB
Go
package module
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/go-getter"
|
|
"github.com/hashicorp/terraform/config"
|
|
"github.com/hashicorp/terraform/helper/copy"
|
|
)
|
|
|
|
func TestTreeChild(t *testing.T) {
|
|
var nilTree *Tree
|
|
if nilTree.Child(nil) != nil {
|
|
t.Fatal("child should be nil")
|
|
}
|
|
|
|
storage := testStorage(t)
|
|
tree := NewTree("", testConfig(t, "child"))
|
|
if err := tree.Load(storage, GetModeGet); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Should be able to get the root child
|
|
if c := tree.Child([]string{}); c == nil {
|
|
t.Fatal("should not be nil")
|
|
} else if c.Name() != "root" {
|
|
t.Fatalf("bad: %#v", c.Name())
|
|
} else if !reflect.DeepEqual(c.Path(), []string(nil)) {
|
|
t.Fatalf("bad: %#v", c.Path())
|
|
}
|
|
|
|
// Should be able to get the root child
|
|
if c := tree.Child(nil); c == nil {
|
|
t.Fatal("should not be nil")
|
|
} else if c.Name() != "root" {
|
|
t.Fatalf("bad: %#v", c.Name())
|
|
} else if !reflect.DeepEqual(c.Path(), []string(nil)) {
|
|
t.Fatalf("bad: %#v", c.Path())
|
|
}
|
|
|
|
// Should be able to get the foo child
|
|
if c := tree.Child([]string{"foo"}); c == nil {
|
|
t.Fatal("should not be nil")
|
|
} else if c.Name() != "foo" {
|
|
t.Fatalf("bad: %#v", c.Name())
|
|
} else if !reflect.DeepEqual(c.Path(), []string{"foo"}) {
|
|
t.Fatalf("bad: %#v", c.Path())
|
|
}
|
|
|
|
// Should be able to get the nested child
|
|
if c := tree.Child([]string{"foo", "bar"}); c == nil {
|
|
t.Fatal("should not be nil")
|
|
} else if c.Name() != "bar" {
|
|
t.Fatalf("bad: %#v", c.Name())
|
|
} else if !reflect.DeepEqual(c.Path(), []string{"foo", "bar"}) {
|
|
t.Fatalf("bad: %#v", c.Path())
|
|
}
|
|
}
|
|
|
|
func TestTreeLoad(t *testing.T) {
|
|
storage := testStorage(t)
|
|
tree := NewTree("", testConfig(t, "basic"))
|
|
|
|
if tree.Loaded() {
|
|
t.Fatal("should not be loaded")
|
|
}
|
|
|
|
// This should error because we haven't gotten things yet
|
|
if err := tree.Load(storage, GetModeNone); err == nil {
|
|
t.Fatal("should error")
|
|
}
|
|
|
|
if tree.Loaded() {
|
|
t.Fatal("should not be loaded")
|
|
}
|
|
|
|
// This should get things
|
|
if err := tree.Load(storage, GetModeGet); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if !tree.Loaded() {
|
|
t.Fatal("should be loaded")
|
|
}
|
|
|
|
// This should no longer error
|
|
if err := tree.Load(storage, GetModeNone); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
actual := strings.TrimSpace(tree.String())
|
|
expected := strings.TrimSpace(treeLoadStr)
|
|
if actual != expected {
|
|
t.Fatalf("bad: \n\n%s", actual)
|
|
}
|
|
}
|
|
|
|
func TestTreeLoad_duplicate(t *testing.T) {
|
|
storage := testStorage(t)
|
|
tree := NewTree("", testConfig(t, "dup"))
|
|
|
|
if tree.Loaded() {
|
|
t.Fatal("should not be loaded")
|
|
}
|
|
|
|
// This should get things
|
|
if err := tree.Load(storage, GetModeGet); err == nil {
|
|
t.Fatalf("should error")
|
|
}
|
|
}
|
|
|
|
func TestTreeLoad_copyable(t *testing.T) {
|
|
dir := tempDir(t)
|
|
storage := &getter.FolderStorage{StorageDir: dir}
|
|
cfg := testConfig(t, "basic")
|
|
tree := NewTree("", cfg)
|
|
|
|
// This should get things
|
|
if err := tree.Load(storage, GetModeGet); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if !tree.Loaded() {
|
|
t.Fatal("should be loaded")
|
|
}
|
|
|
|
// This should no longer error
|
|
if err := tree.Load(storage, GetModeNone); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Now we copy the directory, this COPIES symlink values, and
|
|
// doesn't create symlinks themselves. That is important.
|
|
dir2 := tempDir(t)
|
|
os.RemoveAll(dir2)
|
|
defer os.RemoveAll(dir2)
|
|
if err := copy.CopyDir(dir, dir2); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Now copy the configuration
|
|
cfgDir := tempDir(t)
|
|
os.RemoveAll(cfgDir)
|
|
defer os.RemoveAll(cfgDir)
|
|
if err := copy.CopyDir(cfg.Dir, cfgDir); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
{
|
|
cfg, err := config.LoadDir(cfgDir)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
tree := NewTree("", cfg)
|
|
storage := &getter.FolderStorage{StorageDir: dir2}
|
|
|
|
// This should not error since we already got it!
|
|
if err := tree.Load(storage, GetModeNone); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if !tree.Loaded() {
|
|
t.Fatal("should be loaded")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTreeLoad_parentRef(t *testing.T) {
|
|
storage := testStorage(t)
|
|
tree := NewTree("", testConfig(t, "basic-parent"))
|
|
|
|
if tree.Loaded() {
|
|
t.Fatal("should not be loaded")
|
|
}
|
|
|
|
// This should error because we haven't gotten things yet
|
|
if err := tree.Load(storage, GetModeNone); err == nil {
|
|
t.Fatal("should error")
|
|
}
|
|
|
|
if tree.Loaded() {
|
|
t.Fatal("should not be loaded")
|
|
}
|
|
|
|
// This should get things
|
|
if err := tree.Load(storage, GetModeGet); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if !tree.Loaded() {
|
|
t.Fatal("should be loaded")
|
|
}
|
|
|
|
// This should no longer error
|
|
if err := tree.Load(storage, GetModeNone); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
actual := strings.TrimSpace(tree.String())
|
|
expected := strings.TrimSpace(treeLoadParentStr)
|
|
if actual != expected {
|
|
t.Fatalf("bad: \n\n%s", actual)
|
|
}
|
|
}
|
|
|
|
func TestTreeLoad_subdir(t *testing.T) {
|
|
storage := testStorage(t)
|
|
tree := NewTree("", testConfig(t, "basic-subdir"))
|
|
|
|
if tree.Loaded() {
|
|
t.Fatal("should not be loaded")
|
|
}
|
|
|
|
// This should error because we haven't gotten things yet
|
|
if err := tree.Load(storage, GetModeNone); err == nil {
|
|
t.Fatal("should error")
|
|
}
|
|
|
|
if tree.Loaded() {
|
|
t.Fatal("should not be loaded")
|
|
}
|
|
|
|
// This should get things
|
|
if err := tree.Load(storage, GetModeGet); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if !tree.Loaded() {
|
|
t.Fatal("should be loaded")
|
|
}
|
|
|
|
// This should no longer error
|
|
if err := tree.Load(storage, GetModeNone); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
actual := strings.TrimSpace(tree.String())
|
|
expected := strings.TrimSpace(treeLoadSubdirStr)
|
|
if actual != expected {
|
|
t.Fatalf("bad: \n\n%s", actual)
|
|
}
|
|
}
|
|
|
|
func TestTreeModules(t *testing.T) {
|
|
tree := NewTree("", testConfig(t, "basic"))
|
|
actual := tree.Modules()
|
|
|
|
expected := []*Module{
|
|
&Module{Name: "foo", Source: "./foo"},
|
|
}
|
|
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("bad: %#v", actual)
|
|
}
|
|
}
|
|
|
|
func TestTreeName(t *testing.T) {
|
|
tree := NewTree("", testConfig(t, "basic"))
|
|
actual := tree.Name()
|
|
|
|
if actual != RootName {
|
|
t.Fatalf("bad: %#v", actual)
|
|
}
|
|
}
|
|
|
|
// This is a table-driven test for tree validation. This is the preferred
|
|
// way to test Validate. Non table-driven tests exist historically but
|
|
// that style shouldn't be done anymore.
|
|
func TestTreeValidate_table(t *testing.T) {
|
|
cases := []struct {
|
|
Name string
|
|
Fixture string
|
|
Err string
|
|
}{
|
|
{
|
|
"provider alias in child",
|
|
"validate-alias-good",
|
|
"",
|
|
},
|
|
|
|
{
|
|
"undefined provider alias in child",
|
|
"validate-alias-bad",
|
|
"alias must be defined",
|
|
},
|
|
|
|
{
|
|
"root module named root",
|
|
"validate-module-root",
|
|
"cannot contain module",
|
|
},
|
|
|
|
{
|
|
"grandchild module named root",
|
|
"validate-module-root-grandchild",
|
|
"",
|
|
},
|
|
}
|
|
|
|
for i, tc := range cases {
|
|
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
|
|
tree := NewTree("", testConfig(t, tc.Fixture))
|
|
if err := tree.Load(testStorage(t), GetModeGet); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
err := tree.Validate()
|
|
if (err != nil) != (tc.Err != "") {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if err == nil {
|
|
return
|
|
}
|
|
if !strings.Contains(err.Error(), tc.Err) {
|
|
t.Fatalf("err should contain %q: %s", tc.Err, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTreeValidate_badChild(t *testing.T) {
|
|
tree := NewTree("", testConfig(t, "validate-child-bad"))
|
|
|
|
if err := tree.Load(testStorage(t), GetModeGet); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if err := tree.Validate(); err == nil {
|
|
t.Fatal("should error")
|
|
}
|
|
}
|
|
|
|
func TestTreeValidate_badChildOutput(t *testing.T) {
|
|
tree := NewTree("", testConfig(t, "validate-bad-output"))
|
|
|
|
if err := tree.Load(testStorage(t), GetModeGet); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if err := tree.Validate(); err == nil {
|
|
t.Fatal("should error")
|
|
}
|
|
}
|
|
|
|
func TestTreeValidate_badChildOutputToModule(t *testing.T) {
|
|
tree := NewTree("", testConfig(t, "validate-bad-output-to-module"))
|
|
|
|
if err := tree.Load(testStorage(t), GetModeGet); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if err := tree.Validate(); err == nil {
|
|
t.Fatal("should error")
|
|
}
|
|
}
|
|
|
|
func TestTreeValidate_badChildVar(t *testing.T) {
|
|
tree := NewTree("", testConfig(t, "validate-bad-var"))
|
|
|
|
if err := tree.Load(testStorage(t), GetModeGet); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if err := tree.Validate(); err == nil {
|
|
t.Fatal("should error")
|
|
}
|
|
}
|
|
|
|
func TestTreeValidate_badRoot(t *testing.T) {
|
|
tree := NewTree("", testConfig(t, "validate-root-bad"))
|
|
|
|
if err := tree.Load(testStorage(t), GetModeGet); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if err := tree.Validate(); err == nil {
|
|
t.Fatal("should error")
|
|
}
|
|
}
|
|
|
|
func TestTreeValidate_good(t *testing.T) {
|
|
tree := NewTree("", testConfig(t, "validate-child-good"))
|
|
|
|
if err := tree.Load(testStorage(t), GetModeGet); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if err := tree.Validate(); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
}
|
|
|
|
func TestTreeValidate_notLoaded(t *testing.T) {
|
|
tree := NewTree("", testConfig(t, "basic"))
|
|
|
|
if err := tree.Validate(); err == nil {
|
|
t.Fatal("should error")
|
|
}
|
|
}
|
|
|
|
func TestTreeValidate_requiredChildVar(t *testing.T) {
|
|
tree := NewTree("", testConfig(t, "validate-required-var"))
|
|
|
|
if err := tree.Load(testStorage(t), GetModeGet); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if err := tree.Validate(); err == nil {
|
|
t.Fatal("should error")
|
|
}
|
|
}
|
|
|
|
const treeLoadStr = `
|
|
root
|
|
foo (path: foo)
|
|
`
|
|
|
|
const treeLoadParentStr = `
|
|
root
|
|
a (path: a)
|
|
b (path: a, b)
|
|
`
|
|
const treeLoadSubdirStr = `
|
|
root
|
|
foo (path: foo)
|
|
bar (path: foo, bar)
|
|
`
|