report all errors from module validation
It can be tedious fixing a new module with many errors when Terraform only outputs the first random error it encounters. Accumulate all errors from validation, and format them for the user.
This commit is contained in:
parent
0fa3c71d09
commit
2a949093ed
|
@ -1 +1,2 @@
|
||||||
variable "memory" {}
|
variable "memory" {}
|
||||||
|
variable "feature" {}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 = `
|
||||||
|
|
Loading…
Reference in New Issue