Skip to content

Commit

Permalink
report all errors from module validation
Browse files Browse the repository at this point in the history
It can be tedious fixing a new module with many errors when Terraform
only outputs the first random error it encounters.

Accumulated all errors from validation, and format them for the user.
  • Loading branch information
jbardin committed Mar 2, 2017
1 parent 0fa3c71 commit 964b118
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 32 deletions.
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
variable "memory" {}
variable "feature" {}
87 changes: 56 additions & 31 deletions config/module/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ func (t *Tree) Validate() error {
}

// If something goes wrong, here is our error template
newErr := &TreeError{Name: []string{t.Name()}}
newErr := &treeError{Name: []string{t.Name()}}

// Terraform core does not handle root module children named "root".
// We plan to fix this in the future but this bug was brought up in
Expand All @@ -271,15 +271,14 @@ func (t *Tree) Validate() error {

// Validate our configuration first.
if err := t.config.Validate(); err != nil {
newErr.Err = err
return newErr
newErr.Add(err)
}

// If we're the root, we do extra validation. This validation usually
// requires the entire tree (since children don't have parent pointers).
if len(t.path) == 0 {
if err := t.validateProviderAlias(); err != nil {
return err
newErr.Add(err)
}
}

Expand All @@ -293,15 +292,15 @@ func (t *Tree) Validate() error {
continue
}

verr, ok := err.(*TreeError)
verr, ok := err.(*treeError)
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
newErr.AddChild(verr)
}

// Go over all the modules and verify that any parameters are valid
Expand All @@ -327,10 +326,9 @@ func (t *Tree) Validate() error {
// Compare to the keys in our raw config for the module
for k, _ := range m.RawConfig.Raw {
if _, ok := varMap[k]; !ok {
newErr.Err = fmt.Errorf(
newErr.Add(fmt.Errorf(
"module %s: %s is not a valid parameter",
m.Name, k)
return newErr
m.Name, k))
}

// Remove the required
Expand All @@ -339,10 +337,9 @@ func (t *Tree) Validate() error {

// If we have any required left over, they aren't set.
for k, _ := range requiredMap {
newErr.Err = fmt.Errorf(
"module %s: required variable %s not set",
m.Name, k)
return newErr
newErr.Add(fmt.Errorf(
"module %s: required variable %q not set",
m.Name, k))
}
}

Expand All @@ -369,33 +366,61 @@ func (t *Tree) Validate() error {
}
}
if !found {
newErr.Err = fmt.Errorf(
newErr.Add(fmt.Errorf(
"%s: %s is not a valid output for module %s",
source, mv.Field, mv.Name)
return newErr
source, mv.Field, mv.Name))
}
}
}

return nil
return newErr.ErrOrNil()
}

// treeError is an error use by Tree.Validate to accumulates all
// validation errors.
type treeError struct {
Name []string
Errs []error
Children []*treeError
}

func (e *treeError) Add(err error) {
e.Errs = append(e.Errs, err)
}

func (e *treeError) AddChild(err *treeError) {
e.Children = append(e.Children, err)
}

// TreeError is an error returned by Tree.Validate if an error occurs
// with validation.
type TreeError struct {
Name []string
Err error
func (e *treeError) ErrOrNil() error {
if len(e.Errs) > 0 || len(e.Children) > 0 {
return e
}
return nil
}

func (e *TreeError) Error() string {
// Build up the name
var buf bytes.Buffer
for _, n := range e.Name {
buf.WriteString(n)
buf.WriteString(".")
func (e *treeError) Error() string {
name := strings.Join(e.Name, ".")
var out bytes.Buffer
fmt.Fprintf(&out, "module %s: ", name)

if len(e.Errs) == 1 {
// single like error
out.WriteString(e.Errs[0].Error())
} else {
// multi-line error
for _, err := range e.Errs {
fmt.Fprintf(&out, "\n %s", err)
}
}

if len(e.Children) > 0 {
// start the next error on a new line
out.WriteString("\n ")
}
for _, child := range e.Children {
out.WriteString(child.Error())
}
buf.Truncate(buf.Len() - 1)

// Format the value
return fmt.Sprintf("module %s: %s", buf.String(), e.Err)
return out.String()
}
11 changes: 10 additions & 1 deletion config/module/tree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,9 +410,18 @@ func TestTreeValidate_requiredChildVar(t *testing.T) {
t.Fatalf("err: %s", err)
}

if err := tree.Validate(); err == nil {
err := tree.Validate()
if err == nil {
t.Fatal("should error")
}

// ensure both variables are mentioned in the output
errMsg := err.Error()
for _, v := range []string{"feature", "memory"} {
if !strings.Contains(errMsg, v) {
t.Fatalf("no mention of missing variable %q", v)
}
}
}

const treeLoadStr = `
Expand Down

0 comments on commit 964b118

Please sign in to comment.