diff --git a/config/module/test-fixtures/validate-child-bad/child/main.tf b/config/module/test-fixtures/validate-child-bad/child/main.tf new file mode 100644 index 000000000..93b365403 --- /dev/null +++ b/config/module/test-fixtures/validate-child-bad/child/main.tf @@ -0,0 +1,3 @@ +# Duplicate resources +resource "aws_instance" "foo" {} +resource "aws_instance" "foo" {} diff --git a/config/module/test-fixtures/validate-child-bad/main.tf b/config/module/test-fixtures/validate-child-bad/main.tf new file mode 100644 index 000000000..813f7ef8e --- /dev/null +++ b/config/module/test-fixtures/validate-child-bad/main.tf @@ -0,0 +1,3 @@ +module "foo" { + source = "./child" +} diff --git a/config/module/test-fixtures/validate-child-good/child/main.tf b/config/module/test-fixtures/validate-child-good/child/main.tf new file mode 100644 index 000000000..dd0d95b59 --- /dev/null +++ b/config/module/test-fixtures/validate-child-good/child/main.tf @@ -0,0 +1 @@ +# Good diff --git a/config/module/test-fixtures/validate-child-good/main.tf b/config/module/test-fixtures/validate-child-good/main.tf new file mode 100644 index 000000000..0f6991c53 --- /dev/null +++ b/config/module/test-fixtures/validate-child-good/main.tf @@ -0,0 +1,3 @@ +module "child" { + source = "./child" +} diff --git a/config/module/test-fixtures/validate-root-bad/main.tf b/config/module/test-fixtures/validate-root-bad/main.tf new file mode 100644 index 000000000..93b365403 --- /dev/null +++ b/config/module/test-fixtures/validate-root-bad/main.tf @@ -0,0 +1,3 @@ +# Duplicate resources +resource "aws_instance" "foo" {} +resource "aws_instance" "foo" {} diff --git a/config/module/tree.go b/config/module/tree.go index d08b047bd..2ac3f8b18 100644 --- a/config/module/tree.go +++ b/config/module/tree.go @@ -67,6 +67,13 @@ func (t *Tree) Flatten() (*config.Config, error) { return nil, nil } +// Loaded says whether or not this tree has been loaded or not yet. +func (t *Tree) Loaded() bool { + t.lock.RLock() + defer t.lock.RUnlock() + return t.children != nil +} + // Modules returns the list of modules that this tree imports. // // This is only the imports of _this_ level of the tree. To retrieve the @@ -191,6 +198,58 @@ func (t *Tree) String() string { // // This will call the respective config.Config.Validate() functions as well // as verifying things such as parameters/outputs between the various modules. +// +// Load must be called prior to calling Validate or an error will be returned. func (t *Tree) Validate() error { + if !t.Loaded() { + return fmt.Errorf("tree must be loaded before calling Validate") + } + + // Validate our configuration first. + if err := t.config.Validate(); err != nil { + return &ValidateError{ + Name: []string{t.Name()}, + Err: err, + } + } + + // Validate all our children + for _, c := range t.Children() { + err := c.Validate() + if err == nil { + continue + } + + verr, ok := err.(*ValidateError) + if !ok { + // Unknown error, just return... + return err + } + + // Append ourselves to the error and then return + verr.Name = append(verr.Name, t.Name()) + return verr + } + return nil } + +// ValidateError is an error returned by Tree.Validate if an error occurs +// with validation. +type ValidateError struct { + Name []string + Err error +} + +func (e *ValidateError) Error() string { + // Build up the name + var buf bytes.Buffer + for _, n := range e.Name { + buf.WriteString(n) + buf.WriteString(".") + } + buf.Truncate(buf.Len()-1) + + // Format the value + return fmt.Sprintf("module %s: %s", buf.String(), e.Err) +} diff --git a/config/module/tree_test.go b/config/module/tree_test.go index fe9e09604..8795e16b9 100644 --- a/config/module/tree_test.go +++ b/config/module/tree_test.go @@ -6,20 +6,32 @@ import ( "testing" ) -func TestTree_Load(t *testing.T) { +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) @@ -32,7 +44,7 @@ func TestTree_Load(t *testing.T) { } } -func TestTree_Modules(t *testing.T) { +func TestTreeModules(t *testing.T) { tree := NewTree(testConfig(t, "basic")) actual := tree.Modules() @@ -45,7 +57,7 @@ func TestTree_Modules(t *testing.T) { } } -func TestTree_Name(t *testing.T) { +func TestTreeName(t *testing.T) { tree := NewTree(testConfig(t, "basic")) actual := tree.Name() @@ -54,6 +66,50 @@ func TestTree_Name(t *testing.T) { } } +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_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") + } +} + const treeLoadStr = ` foo