Merge pull request #12383 from hashicorp/jbardin/multi-var-errs

report all errors from module validation
This commit is contained in:
James Bardin 2017-03-14 15:36:32 -04:00 committed by GitHub
commit 2e3579b058
3 changed files with 67 additions and 32 deletions

View File

@ -1 +1,2 @@
variable "memory" {} variable "memory" {}
variable "feature" {}

View File

@ -259,7 +259,7 @@ func (t *Tree) Validate() error {
} }
// If something goes wrong, here is our error template // 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". // 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 // We plan to fix this in the future but this bug was brought up in
@ -271,15 +271,14 @@ func (t *Tree) Validate() error {
// Validate our configuration first. // Validate our configuration first.
if err := t.config.Validate(); err != nil { if err := t.config.Validate(); err != nil {
newErr.Err = err newErr.Add(err)
return newErr
} }
// If we're the root, we do extra validation. This validation usually // If we're the root, we do extra validation. This validation usually
// requires the entire tree (since children don't have parent pointers). // requires the entire tree (since children don't have parent pointers).
if len(t.path) == 0 { if len(t.path) == 0 {
if err := t.validateProviderAlias(); err != nil { if err := t.validateProviderAlias(); err != nil {
return err newErr.Add(err)
} }
} }
@ -293,7 +292,7 @@ func (t *Tree) Validate() error {
continue continue
} }
verr, ok := err.(*TreeError) verr, ok := err.(*treeError)
if !ok { if !ok {
// Unknown error, just return... // Unknown error, just return...
return err return err
@ -301,7 +300,7 @@ func (t *Tree) Validate() error {
// Append ourselves to the error and then return // Append ourselves to the error and then return
verr.Name = append(verr.Name, t.Name()) verr.Name = append(verr.Name, t.Name())
return verr newErr.AddChild(verr)
} }
// Go over all the modules and verify that any parameters are valid // Go over all the modules and verify that any parameters are valid
@ -327,10 +326,9 @@ func (t *Tree) Validate() error {
// Compare to the keys in our raw config for the module // Compare to the keys in our raw config for the module
for k, _ := range m.RawConfig.Raw { for k, _ := range m.RawConfig.Raw {
if _, ok := varMap[k]; !ok { if _, ok := varMap[k]; !ok {
newErr.Err = fmt.Errorf( newErr.Add(fmt.Errorf(
"module %s: %s is not a valid parameter", "module %s: %s is not a valid parameter",
m.Name, k) m.Name, k))
return newErr
} }
// Remove the required // Remove the required
@ -339,10 +337,9 @@ func (t *Tree) Validate() error {
// If we have any required left over, they aren't set. // If we have any required left over, they aren't set.
for k, _ := range requiredMap { for k, _ := range requiredMap {
newErr.Err = fmt.Errorf( newErr.Add(fmt.Errorf(
"module %s: required variable %s not set", "module %s: required variable %q not set",
m.Name, k) m.Name, k))
return newErr
} }
} }
@ -369,33 +366,61 @@ func (t *Tree) Validate() error {
} }
} }
if !found { if !found {
newErr.Err = fmt.Errorf( newErr.Add(fmt.Errorf(
"%s: %s is not a valid output for module %s", "%s: %s is not a valid output for module %s",
source, mv.Field, mv.Name) source, mv.Field, mv.Name))
return newErr
} }
} }
} }
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)
}
func (e *treeError) ErrOrNil() error {
if len(e.Errs) > 0 || len(e.Children) > 0 {
return e
}
return nil return nil
} }
// TreeError is an error returned by Tree.Validate if an error occurs func (e *treeError) Error() string {
// with validation. name := strings.Join(e.Name, ".")
type TreeError struct { var out bytes.Buffer
Name []string fmt.Fprintf(&out, "module %s: ", name)
Err error
}
func (e *TreeError) Error() string { if len(e.Errs) == 1 {
// Build up the name // single like error
var buf bytes.Buffer out.WriteString(e.Errs[0].Error())
for _, n := range e.Name { } else {
buf.WriteString(n) // multi-line error
buf.WriteString(".") for _, err := range e.Errs {
fmt.Fprintf(&out, "\n %s", err)
}
} }
buf.Truncate(buf.Len() - 1)
// Format the value if len(e.Children) > 0 {
return fmt.Sprintf("module %s: %s", buf.String(), e.Err) // start the next error on a new line
out.WriteString("\n ")
}
for _, child := range e.Children {
out.WriteString(child.Error())
}
return out.String()
} }

View File

@ -410,9 +410,18 @@ func TestTreeValidate_requiredChildVar(t *testing.T) {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if err := tree.Validate(); err == nil { err := tree.Validate()
if err == nil {
t.Fatal("should error") 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 = ` const treeLoadStr = `