return tfdiags.Diagnostics from validation methods

Validation is the best time to return detailed diagnostics
to the user since we're much more likely to have source
location information, etc than we are in later operations.

This change doesn't actually add any detail to the messages
yet, but it changes the interface so that we can gradually
introduce more detailed diagnostics over time.

While here there are some minor adjustments to some of the
messages to improve their consistency with terminology we
use elsewhere.
This commit is contained in:
Martin Atkins 2017-11-21 15:08:00 -08:00
parent 9f1a773655
commit ba0514106a
17 changed files with 426 additions and 530 deletions

View File

@ -2,12 +2,13 @@ package local
import ( import (
"errors" "errors"
"fmt"
"log" "log"
"strings"
"github.com/hashicorp/terraform/command/format"
"github.com/hashicorp/terraform/tfdiags"
"github.com/hashicorp/errwrap" "github.com/hashicorp/errwrap"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
@ -91,27 +92,31 @@ func (b *Local) context(op *backend.Operation) (*terraform.Context, state.State,
// If validation is enabled, validate // If validation is enabled, validate
if b.OpValidation { if b.OpValidation {
// We ignore warnings here on purpose. We expect users to be listening diags := tfCtx.Validate()
// to the terraform.Hook called after a validation. if len(diags) > 0 {
ws, es := tfCtx.Validate() if diags.HasErrors() {
if len(ws) > 0 { // If there are warnings _and_ errors then we'll take this
// Log just in case the CLI isn't enabled // path and return them all together in this error.
log.Printf("[WARN] backend/local: %d warnings: %v", len(ws), ws) return nil, nil, diags.Err()
// If we have a CLI, output the warnings
if b.CLI != nil {
b.CLI.Warn(strings.TrimSpace(validateWarnHeader) + "\n")
for _, w := range ws {
b.CLI.Warn(fmt.Sprintf(" * %s", w))
}
// Make a newline before continuing
b.CLI.Output("")
} }
}
if len(es) > 0 { // For now we can't propagate warnings any further without
return nil, nil, multierror.Append(nil, es...) // printing them directly to the UI, so we'll need to
// format them here ourselves.
for _, diag := range diags {
if diag.Severity() != tfdiags.Warning {
continue
}
if b.CLI != nil {
b.CLI.Warn(format.Diagnostic(diag, b.Colorize(), 72))
} else {
desc := diag.Description()
log.Printf("[WARN] backend/local: %s", desc.Summary)
}
}
// Make a newline before continuing
b.CLI.Output("")
} }
} }
} }

View File

@ -7,7 +7,6 @@ import (
"runtime" "runtime"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli"
) )
// Set to true when we're testing // Set to true when we're testing
@ -82,39 +81,18 @@ func ModulePath(args []string) (string, error) {
return args[0], nil return args[0], nil
} }
func validateContext(ctx *terraform.Context, ui cli.Ui) bool { func (m *Meta) validateContext(ctx *terraform.Context) bool {
log.Println("[INFO] Validating the context...") log.Println("[INFO] Validating the context...")
ws, es := ctx.Validate() diags := ctx.Validate()
log.Printf("[INFO] Validation result: %d warnings, %d errors", len(ws), len(es)) log.Printf("[INFO] Validation result: %d diagnostics", len(diags))
if len(ws) > 0 || len(es) > 0 { if len(diags) > 0 {
ui.Output( m.Ui.Output(
"There are warnings and/or errors related to your configuration. Please\n" + "There are warnings and/or errors related to your configuration. Please\n" +
"fix these before continuing.\n") "fix these before continuing.\n")
if len(ws) > 0 { m.showDiagnostics(diags)
ui.Warn("Warnings:\n")
for _, w := range ws {
ui.Warn(fmt.Sprintf(" * %s", w))
}
if len(es) > 0 {
ui.Output("")
}
}
if len(es) > 0 {
ui.Error("Errors:\n")
for _, e := range es {
ui.Error(fmt.Sprintf(" * %s", e))
}
return false
} else {
ui.Warn(fmt.Sprintf("\n"+
"No errors found. Continuing with %d warning(s).\n", len(ws)))
return true
}
} }
return true return !diags.HasErrors()
} }

View File

@ -280,7 +280,8 @@ func (c *InitCommand) getProviders(path string, state *terraform.State, upgrade
return err return err
} }
if err := mod.Validate(); err != nil { if diags := mod.Validate(); diags.HasErrors() {
err := diags.Err()
c.Ui.Error(fmt.Sprintf("Error getting plugins: %s", err)) c.Ui.Error(fmt.Sprintf("Error getting plugins: %s", err))
return err return err
} }

View File

@ -53,10 +53,11 @@ func (c *ProvidersCommand) Run(args []string) int {
} }
// Validate the config (to ensure the version constraints are valid) // Validate the config (to ensure the version constraints are valid)
err = root.Validate() if diags := root.Validate(); len(diags) != 0 {
if err != nil { c.showDiagnostics(diags)
c.Ui.Error(err.Error()) if diags.HasErrors() {
return 1 return 1
}
} }
// Load the backend // Load the backend

View File

@ -100,10 +100,11 @@ func (c *ValidateCommand) validate(dir string, checkVars bool) int {
c.showDiagnostics(err) c.showDiagnostics(err)
return 1 return 1
} }
err = cfg.Validate() if diags := cfg.Validate(); len(diags) != 0 {
if err != nil { c.showDiagnostics(diags)
c.showDiagnostics(err) if diags.HasErrors() {
return 1 return 1
}
} }
if checkVars { if checkVars {
@ -122,7 +123,7 @@ func (c *ValidateCommand) validate(dir string, checkVars bool) int {
return 1 return 1
} }
if !validateContext(tfCtx, c.Ui) { if !c.validateContext(tfCtx) {
return 1 return 1
} }
} }

View File

@ -74,7 +74,7 @@ func TestValidateFailingCommandMissingVariable(t *testing.T) {
if code != 1 { if code != 1 {
t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String()) t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
} }
if !strings.HasSuffix(strings.TrimSpace(ui.ErrorWriter.String()), "config: unknown variable referenced: 'description'. define it with 'variable' blocks") { if !strings.HasSuffix(strings.TrimSpace(ui.ErrorWriter.String()), "config: unknown variable referenced: 'description'; define it with a 'variable' block") {
t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String()) t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
} }
} }
@ -84,7 +84,7 @@ func TestSameProviderMutipleTimesShouldFail(t *testing.T) {
if code != 1 { if code != 1 {
t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String()) t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
} }
if !strings.HasSuffix(strings.TrimSpace(ui.ErrorWriter.String()), "provider.aws: declared multiple times, you can only declare a provider once") { if !strings.HasSuffix(strings.TrimSpace(ui.ErrorWriter.String()), "provider.aws: multiple configurations present; only one configuration is allowed per provider") {
t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String()) t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
} }
} }
@ -94,7 +94,7 @@ func TestSameModuleMultipleTimesShouldFail(t *testing.T) {
if code != 1 { if code != 1 {
t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String()) t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
} }
if !strings.HasSuffix(strings.TrimSpace(ui.ErrorWriter.String()), "multi_module: module repeated multiple times") { if !strings.HasSuffix(strings.TrimSpace(ui.ErrorWriter.String()), "module \"multi_module\": module repeated multiple times") {
t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String()) t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
} }
} }
@ -114,7 +114,7 @@ func TestOutputWithoutValueShouldFail(t *testing.T) {
if code != 1 { if code != 1 {
t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String()) t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
} }
if !strings.HasSuffix(strings.TrimSpace(ui.ErrorWriter.String()), "output is missing required 'value' key") { if !strings.HasSuffix(strings.TrimSpace(ui.ErrorWriter.String()), "output \"myvalue\": missing required 'value' argument") {
t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String()) t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
} }
} }
@ -125,7 +125,7 @@ func TestModuleWithIncorrectNameShouldFail(t *testing.T) {
t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String()) t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
} }
if !strings.Contains(ui.ErrorWriter.String(), "module name can only contain letters, numbers, dashes, and underscores") { if !strings.Contains(ui.ErrorWriter.String(), "module name must be a letter or underscore followed by only letters, numbers, dashes, and underscores") {
t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String()) t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
} }
if !strings.Contains(ui.ErrorWriter.String(), "module source cannot contain interpolations") { if !strings.Contains(ui.ErrorWriter.String(), "module source cannot contain interpolations") {
@ -142,7 +142,7 @@ func TestWronglyUsedInterpolationShouldFail(t *testing.T) {
if !strings.Contains(ui.ErrorWriter.String(), "depends on value cannot contain interpolations") { if !strings.Contains(ui.ErrorWriter.String(), "depends on value cannot contain interpolations") {
t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String()) t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
} }
if !strings.Contains(ui.ErrorWriter.String(), "Variable 'vairable_with_interpolation': cannot contain interpolations") { if !strings.Contains(ui.ErrorWriter.String(), "variable \"vairable_with_interpolation\": default may not contain interpolations") {
t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String()) t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
} }
} }

View File

@ -8,10 +8,10 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/hil/ast" "github.com/hashicorp/hil/ast"
"github.com/hashicorp/terraform/helper/hilmapstructure" "github.com/hashicorp/terraform/helper/hilmapstructure"
"github.com/hashicorp/terraform/plugin/discovery" "github.com/hashicorp/terraform/plugin/discovery"
"github.com/hashicorp/terraform/tfdiags"
"github.com/mitchellh/reflectwalk" "github.com/mitchellh/reflectwalk"
) )
@ -278,30 +278,35 @@ func ResourceProviderFullName(resourceType, explicitProvider string) string {
} }
// Validate does some basic semantic checking of the configuration. // Validate does some basic semantic checking of the configuration.
func (c *Config) Validate() error { func (c *Config) Validate() tfdiags.Diagnostics {
if c == nil { if c == nil {
return nil return nil
} }
var errs []error var diags tfdiags.Diagnostics
for _, k := range c.unknownKeys { for _, k := range c.unknownKeys {
errs = append(errs, fmt.Errorf( diags = diags.Append(
"Unknown root level key: %s", k)) fmt.Errorf("Unknown root level key: %s", k),
)
} }
// Validate the Terraform config // Validate the Terraform config
if tf := c.Terraform; tf != nil { if tf := c.Terraform; tf != nil {
errs = append(errs, c.Terraform.Validate()...) errs := c.Terraform.Validate()
for _, err := range errs {
diags = diags.Append(err)
}
} }
vars := c.InterpolatedVariables() vars := c.InterpolatedVariables()
varMap := make(map[string]*Variable) varMap := make(map[string]*Variable)
for _, v := range c.Variables { for _, v := range c.Variables {
if _, ok := varMap[v.Name]; ok { if _, ok := varMap[v.Name]; ok {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"Variable '%s': duplicate found. Variable names must be unique.", "Variable '%s': duplicate found. Variable names must be unique.",
v.Name)) v.Name,
))
} }
varMap[v.Name] = v varMap[v.Name] = v
@ -309,17 +314,19 @@ func (c *Config) Validate() error {
for k, _ := range varMap { for k, _ := range varMap {
if !NameRegexp.MatchString(k) { if !NameRegexp.MatchString(k) {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"variable %q: variable name must match regular expresion %s", "variable %q: variable name must match regular expression %s",
k, NameRegexp)) k, NameRegexp,
))
} }
} }
for _, v := range c.Variables { for _, v := range c.Variables {
if v.Type() == VariableTypeUnknown { if v.Type() == VariableTypeUnknown {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"Variable '%s': must be a string or a map", "Variable '%s': must be a string or a map",
v.Name)) v.Name,
))
continue continue
} }
@ -340,9 +347,10 @@ func (c *Config) Validate() error {
if v.Default != nil { if v.Default != nil {
if err := reflectwalk.Walk(v.Default, w); err == nil { if err := reflectwalk.Walk(v.Default, w); err == nil {
if interp { if interp {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"Variable '%s': cannot contain interpolations", "variable %q: default may not contain interpolations",
v.Name)) v.Name,
))
} }
} }
} }
@ -358,10 +366,11 @@ func (c *Config) Validate() error {
} }
if _, ok := varMap[uv.Name]; !ok { if _, ok := varMap[uv.Name]; !ok {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: unknown variable referenced: '%s'. define it with 'variable' blocks", "%s: unknown variable referenced: '%s'; define it with a 'variable' block",
source, source,
uv.Name)) uv.Name,
))
} }
} }
} }
@ -372,17 +381,19 @@ func (c *Config) Validate() error {
switch v := rawV.(type) { switch v := rawV.(type) {
case *CountVariable: case *CountVariable:
if v.Type == CountValueInvalid { if v.Type == CountValueInvalid {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: invalid count variable: %s", "%s: invalid count variable: %s",
source, source,
v.FullKey())) v.FullKey(),
))
} }
case *PathVariable: case *PathVariable:
if v.Type == PathValueInvalid { if v.Type == PathValueInvalid {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: invalid path variable: %s", "%s: invalid path variable: %s",
source, source,
v.FullKey())) v.FullKey(),
))
} }
} }
} }
@ -394,16 +405,17 @@ func (c *Config) Validate() error {
for _, p := range c.ProviderConfigs { for _, p := range c.ProviderConfigs {
name := p.FullName() name := p.FullName()
if _, ok := providerSet[name]; ok { if _, ok := providerSet[name]; ok {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"provider.%s: declared multiple times, you can only declare a provider once", "provider.%s: multiple configurations present; only one configuration is allowed per provider",
name)) name,
))
continue continue
} }
if p.Version != "" { if p.Version != "" {
_, err := discovery.ConstraintStr(p.Version).Parse() _, err := discovery.ConstraintStr(p.Version).Parse()
if err != nil { if err != nil {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"provider.%s: invalid version constraint %q: %s", "provider.%s: invalid version constraint %q: %s",
name, p.Version, err, name, p.Version, err,
)) ))
@ -422,9 +434,10 @@ func (c *Config) Validate() error {
if _, ok := dupped[m.Id()]; !ok { if _, ok := dupped[m.Id()]; !ok {
dupped[m.Id()] = struct{}{} dupped[m.Id()] = struct{}{}
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: module repeated multiple times", "module %q: module repeated multiple times",
m.Id())) m.Id(),
))
} }
// Already seen this module, just skip it // Already seen this module, just skip it
@ -438,21 +451,23 @@ func (c *Config) Validate() error {
"root": m.Source, "root": m.Source,
}) })
if err != nil { if err != nil {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: module source error: %s", "module %q: module source error: %s",
m.Id(), err)) m.Id(), err,
))
} else if len(rc.Interpolations) > 0 { } else if len(rc.Interpolations) > 0 {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: module source cannot contain interpolations", "module %q: module source cannot contain interpolations",
m.Id())) m.Id(),
))
} }
// Check that the name matches our regexp // Check that the name matches our regexp
if !NameRegexp.Match([]byte(m.Name)) { if !NameRegexp.Match([]byte(m.Name)) {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: module name can only contain letters, numbers, "+ "module %q: module name must be a letter or underscore followed by only letters, numbers, dashes, and underscores",
"dashes, and underscores", m.Id(),
m.Id())) ))
} }
// Check that the configuration can all be strings, lists or maps // Check that the configuration can all be strings, lists or maps
@ -476,36 +491,44 @@ func (c *Config) Validate() error {
continue continue
} }
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: variable %s must be a string, list or map value", "module %q: argument %s must have a string, list, or map value",
m.Id(), k)) m.Id(), k,
))
} }
// Check for invalid count variables // Check for invalid count variables
for _, v := range m.RawConfig.Variables { for _, v := range m.RawConfig.Variables {
switch v.(type) { switch v.(type) {
case *CountVariable: case *CountVariable:
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: count variables are only valid within resources", m.Name)) "module %q: count variables are only valid within resources",
m.Name,
))
case *SelfVariable: case *SelfVariable:
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: self variables are only valid within resources", m.Name)) "module %q: self variables are only valid within resources",
m.Name,
))
} }
} }
// Update the raw configuration to only contain the string values // Update the raw configuration to only contain the string values
m.RawConfig, err = NewRawConfig(raw) m.RawConfig, err = NewRawConfig(raw)
if err != nil { if err != nil {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: can't initialize configuration: %s", "%s: can't initialize configuration: %s",
m.Id(), err)) m.Id(), err,
))
} }
// check that all named providers actually exist // check that all named providers actually exist
for _, p := range m.Providers { for _, p := range m.Providers {
if !providerSet[p] { if !providerSet[p] {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"provider %q named in module %q does not exist", p, m.Name)) "module %q: cannot pass non-existent provider %q",
m.Name, p,
))
} }
} }
@ -522,10 +545,10 @@ func (c *Config) Validate() error {
} }
if _, ok := modules[mv.Name]; !ok { if _, ok := modules[mv.Name]; !ok {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: unknown module referenced: %s", "%s: unknown module referenced: %s",
source, source, mv.Name,
mv.Name)) ))
} }
} }
} }
@ -538,9 +561,10 @@ func (c *Config) Validate() error {
if _, ok := dupped[r.Id()]; !ok { if _, ok := dupped[r.Id()]; !ok {
dupped[r.Id()] = struct{}{} dupped[r.Id()] = struct{}{}
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: resource repeated multiple times", "%s: resource repeated multiple times",
r.Id())) r.Id(),
))
} }
} }
@ -554,15 +578,15 @@ func (c *Config) Validate() error {
for _, v := range r.RawCount.Variables { for _, v := range r.RawCount.Variables {
switch v.(type) { switch v.(type) {
case *CountVariable: case *CountVariable:
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: resource count can't reference count variable: %s", "%s: resource count can't reference count variable: %s",
n, n, v.FullKey(),
v.FullKey())) ))
case *SimpleVariable: case *SimpleVariable:
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: resource count can't reference variable: %s", "%s: resource count can't reference variable: %s",
n, n, v.FullKey(),
v.FullKey())) ))
// Good // Good
case *ModuleVariable: case *ModuleVariable:
@ -572,21 +596,24 @@ func (c *Config) Validate() error {
case *LocalVariable: case *LocalVariable:
default: default:
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"Internal error. Unknown type in count var in %s: %T", "Internal error. Unknown type in count var in %s: %T",
n, v)) n, v,
))
} }
} }
if !r.RawCount.couldBeInteger() { if !r.RawCount.couldBeInteger() {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: resource count must be an integer", "%s: resource count must be an integer", n,
n)) ))
} }
r.RawCount.init() r.RawCount.init()
// Validate DependsOn // Validate DependsOn
errs = append(errs, c.validateDependsOn(n, r.DependsOn, resources, modules)...) for _, err := range c.validateDependsOn(n, r.DependsOn, resources, modules) {
diags = diags.Append(err)
}
// Verify provisioners // Verify provisioners
for _, p := range r.Provisioners { for _, p := range r.Provisioners {
@ -600,9 +627,10 @@ func (c *Config) Validate() error {
} }
if rv.Multi && rv.Index == -1 && rv.Type == r.Type && rv.Name == r.Name { if rv.Multi && rv.Index == -1 && rv.Type == r.Type && rv.Name == r.Name {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: connection info cannot contain splat variable "+ "%s: connection info cannot contain splat variable referencing itself",
"referencing itself", n)) n,
))
break break
} }
} }
@ -614,9 +642,10 @@ func (c *Config) Validate() error {
} }
if rv.Multi && rv.Index == -1 && rv.Type == r.Type && rv.Name == r.Name { if rv.Multi && rv.Index == -1 && rv.Type == r.Type && rv.Name == r.Name {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: connection info cannot contain splat variable "+ "%s: connection info cannot contain splat variable referencing itself",
"referencing itself", n)) n,
))
break break
} }
} }
@ -624,21 +653,24 @@ func (c *Config) Validate() error {
// Check for invalid when/onFailure values, though this should be // Check for invalid when/onFailure values, though this should be
// picked up by the loader we check here just in case. // picked up by the loader we check here just in case.
if p.When == ProvisionerWhenInvalid { if p.When == ProvisionerWhenInvalid {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: provisioner 'when' value is invalid", n)) "%s: provisioner 'when' value is invalid", n,
))
} }
if p.OnFailure == ProvisionerOnFailureInvalid { if p.OnFailure == ProvisionerOnFailureInvalid {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: provisioner 'on_failure' value is invalid", n)) "%s: provisioner 'on_failure' value is invalid", n,
))
} }
} }
// Verify ignore_changes contains valid entries // Verify ignore_changes contains valid entries
for _, v := range r.Lifecycle.IgnoreChanges { for _, v := range r.Lifecycle.IgnoreChanges {
if strings.Contains(v, "*") && v != "*" { if strings.Contains(v, "*") && v != "*" {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: ignore_changes does not support using a partial string "+ "%s: ignore_changes does not support using a partial string together with a wildcard: %s",
"together with a wildcard: %s", n, v)) n, v,
))
} }
} }
@ -647,21 +679,24 @@ func (c *Config) Validate() error {
"root": r.Lifecycle.IgnoreChanges, "root": r.Lifecycle.IgnoreChanges,
}) })
if err != nil { if err != nil {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: lifecycle ignore_changes error: %s", "%s: lifecycle ignore_changes error: %s",
n, err)) n, err,
))
} else if len(rc.Interpolations) > 0 { } else if len(rc.Interpolations) > 0 {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: lifecycle ignore_changes cannot contain interpolations", "%s: lifecycle ignore_changes cannot contain interpolations",
n)) n,
))
} }
// If it is a data source then it can't have provisioners // If it is a data source then it can't have provisioners
if r.Mode == DataResourceMode { if r.Mode == DataResourceMode {
if _, ok := r.RawConfig.Raw["provisioner"]; ok { if _, ok := r.RawConfig.Raw["provisioner"]; ok {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: data sources cannot have provisioners", "%s: data sources cannot have provisioners",
n)) n,
))
} }
} }
} }
@ -675,11 +710,12 @@ func (c *Config) Validate() error {
id := rv.ResourceId() id := rv.ResourceId()
if _, ok := resources[id]; !ok { if _, ok := resources[id]; !ok {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: unknown resource '%s' referenced in variable %s", "%s: unknown resource '%s' referenced in variable %s",
source, source,
id, id,
rv.FullKey())) rv.FullKey(),
))
continue continue
} }
} }
@ -690,7 +726,7 @@ func (c *Config) Validate() error {
found := make(map[string]struct{}) found := make(map[string]struct{})
for _, l := range c.Locals { for _, l := range c.Locals {
if _, ok := found[l.Name]; ok { if _, ok := found[l.Name]; ok {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: duplicate local. local value names must be unique", "%s: duplicate local. local value names must be unique",
l.Name, l.Name,
)) ))
@ -700,7 +736,7 @@ func (c *Config) Validate() error {
for _, v := range l.RawConfig.Variables { for _, v := range l.RawConfig.Variables {
if _, ok := v.(*CountVariable); ok { if _, ok := v.(*CountVariable); ok {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"local %s: count variables are only valid within resources", l.Name, "local %s: count variables are only valid within resources", l.Name,
)) ))
} }
@ -714,9 +750,10 @@ func (c *Config) Validate() error {
for _, o := range c.Outputs { for _, o := range c.Outputs {
// Verify the output is new // Verify the output is new
if _, ok := found[o.Name]; ok { if _, ok := found[o.Name]; ok {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: duplicate output. output names must be unique.", "output %q: an output of this name was already defined",
o.Name)) o.Name,
))
continue continue
} }
found[o.Name] = struct{}{} found[o.Name] = struct{}{}
@ -736,9 +773,10 @@ func (c *Config) Validate() error {
continue continue
} }
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: value for 'sensitive' must be boolean", "output %q: value for 'sensitive' must be boolean",
o.Name)) o.Name,
))
continue continue
} }
if k == "description" { if k == "description" {
@ -747,29 +785,35 @@ func (c *Config) Validate() error {
continue continue
} }
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: value for 'description' must be string", "output %q: value for 'description' must be string",
o.Name)) o.Name,
))
continue continue
} }
invalidKeys = append(invalidKeys, k) invalidKeys = append(invalidKeys, k)
} }
if len(invalidKeys) > 0 { if len(invalidKeys) > 0 {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: output has invalid keys: %s", "output %q: invalid keys: %s",
o.Name, strings.Join(invalidKeys, ", "))) o.Name, strings.Join(invalidKeys, ", "),
))
} }
if !valueKeyFound { if !valueKeyFound {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: output is missing required 'value' key", o.Name)) "output %q: missing required 'value' argument", o.Name,
))
} }
for _, v := range o.RawConfig.Variables { for _, v := range o.RawConfig.Variables {
if _, ok := v.(*CountVariable); ok { if _, ok := v.(*CountVariable); ok {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: count variables are only valid within resources", o.Name)) "output %q: count variables are only valid within resources",
o.Name,
))
} }
} }
} }
} }
@ -783,17 +827,15 @@ func (c *Config) Validate() error {
for _, v := range rc.Variables { for _, v := range rc.Variables {
if _, ok := v.(*SelfVariable); ok { if _, ok := v.(*SelfVariable); ok {
errs = append(errs, fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: cannot contain self-reference %s", source, v.FullKey())) "%s: cannot contain self-reference %s",
source, v.FullKey(),
))
} }
} }
} }
if len(errs) > 0 { return diags
return &multierror.Error{Errors: errs}
}
return nil
} }
// InterpolatedVariables is a helper that returns a mapping of all the interpolated // InterpolatedVariables is a helper that returns a mapping of all the interpolated

View File

@ -223,20 +223,21 @@ func TestConfigValidate_table(t *testing.T) {
"invalid provider name in module block", "invalid provider name in module block",
"validate-missing-provider", "validate-missing-provider",
true, true,
"does not exist", "cannot pass non-existent provider",
}, },
} }
for i, tc := range cases { for i, tc := range cases {
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
c := testConfig(t, tc.Fixture) c := testConfig(t, tc.Fixture)
err := c.Validate() diags := c.Validate()
if (err != nil) != tc.Err { if diags.HasErrors() != tc.Err {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", diags.Err().Error())
} }
if err != nil { if diags.HasErrors() {
if tc.ErrString != "" && !strings.Contains(err.Error(), tc.ErrString) { gotErr := diags.Err().Error()
t.Fatalf("expected err to contain: %s\n\ngot: %s", tc.ErrString, err) if tc.ErrString != "" && !strings.Contains(gotErr, tc.ErrString) {
t.Fatalf("expected err to contain: %s\n\ngot: %s", tc.ErrString, gotErr)
} }
return return
@ -291,15 +292,16 @@ func TestConfigValidate_countInt_HCL2(t *testing.T) {
func TestConfigValidate_countBadContext(t *testing.T) { func TestConfigValidate_countBadContext(t *testing.T) {
c := testConfig(t, "validate-count-bad-context") c := testConfig(t, "validate-count-bad-context")
err := c.Validate() diags := c.Validate()
expected := []string{ expected := []string{
"no_count_in_output: count variables are only valid within resources", "output \"no_count_in_output\": count variables are only valid within resources",
"no_count_in_module: count variables are only valid within resources", "module \"no_count_in_module\": count variables are only valid within resources",
} }
for _, exp := range expected { for _, exp := range expected {
if !strings.Contains(err.Error(), exp) { errStr := diags.Err().Error()
t.Fatalf("expected: %q,\nto contain: %q", err, exp) if !strings.Contains(errStr, exp) {
t.Errorf("expected: %q,\nto contain: %q", errStr, exp)
} }
} }
} }

View File

@ -9,6 +9,8 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/hashicorp/terraform/tfdiags"
getter "github.com/hashicorp/go-getter" getter "github.com/hashicorp/go-getter"
"github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config"
) )
@ -383,32 +385,35 @@ func (t *Tree) String() string {
// as verifying things such as parameters/outputs between the various modules. // 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. // Load must be called prior to calling Validate or an error will be returned.
func (t *Tree) Validate() error { func (t *Tree) Validate() tfdiags.Diagnostics {
if !t.Loaded() { var diags tfdiags.Diagnostics
return fmt.Errorf("tree must be loaded before calling Validate")
}
// If something goes wrong, here is our error template if !t.Loaded() {
newErr := &treeError{Name: []string{t.Name()}} diags = diags.Append(fmt.Errorf(
"tree must be loaded before calling Validate",
))
return diags
}
// 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
// the middle of a release and we don't want to introduce wide-sweeping // the middle of a release and we don't want to introduce wide-sweeping
// changes at that time. // changes at that time.
if len(t.path) == 1 && t.name == "root" { if len(t.path) == 1 && t.name == "root" {
return fmt.Errorf("root module cannot contain module named 'root'") diags = diags.Append(fmt.Errorf(
"root module cannot contain module named 'root'",
))
return diags
} }
// Validate our configuration first. // Validate our configuration first.
if err := t.config.Validate(); err != nil { diags = diags.Append(t.config.Validate())
newErr.Add(err)
}
// 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 {
newErr.Add(err) diags = diags.Append(err)
} }
} }
@ -417,20 +422,11 @@ func (t *Tree) Validate() error {
// Validate all our children // Validate all our children
for _, c := range children { for _, c := range children {
err := c.Validate() childDiags := c.Validate()
if err == nil { diags = diags.Append(childDiags)
if diags.HasErrors() {
continue continue
} }
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())
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
@ -456,9 +452,10 @@ 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.Add(fmt.Errorf( diags = diags.Append(fmt.Errorf(
"module %s: %s is not a valid parameter", "module %q: %q is not a valid argument",
m.Name, k)) m.Name, k,
))
} }
// Remove the required // Remove the required
@ -467,9 +464,10 @@ 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.Add(fmt.Errorf( diags = diags.Append(fmt.Errorf(
"module %s: required variable %q not set", "module %q: missing required argument %q",
m.Name, k)) m.Name, k,
))
} }
} }
@ -484,9 +482,10 @@ func (t *Tree) Validate() error {
tree, ok := children[mv.Name] tree, ok := children[mv.Name]
if !ok { if !ok {
newErr.Add(fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: undefined module referenced %s", "%s: reference to undefined module %q",
source, mv.Name)) source, mv.Name,
))
continue continue
} }
@ -498,14 +497,15 @@ func (t *Tree) Validate() error {
} }
} }
if !found { if !found {
newErr.Add(fmt.Errorf( diags = diags.Append(fmt.Errorf(
"%s: %s is not a valid output for module %s", "%s: %q is not a valid output for module %q",
source, mv.Field, mv.Name)) source, mv.Field, mv.Name,
))
} }
} }
} }
return newErr.ErrOrNil() return diags
} }
// versionedPathKey returns a path string with every levels full name, version // versionedPathKey returns a path string with every levels full name, version

View File

@ -404,15 +404,15 @@ func TestTreeValidate_table(t *testing.T) {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
err := tree.Validate() diags := tree.Validate()
if (err != nil) != (tc.Err != "") { if (diags.HasErrors()) != (tc.Err != "") {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", diags.Err())
} }
if err == nil { if len(diags) == 0 {
return return
} }
if !strings.Contains(err.Error(), tc.Err) { if !strings.Contains(diags.Err().Error(), tc.Err) {
t.Fatalf("err should contain %q: %s", tc.Err, err) t.Fatalf("err should contain %q: %s", tc.Err, diags.Err().Error())
} }
}) })
} }
@ -519,13 +519,13 @@ func TestTreeValidate_requiredChildVar(t *testing.T) {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
err := tree.Validate() diags := tree.Validate()
if err == nil { if !diags.HasErrors() {
t.Fatal("should error") t.Fatal("should error")
} }
// ensure both variables are mentioned in the output // ensure both variables are mentioned in the output
errMsg := err.Error() errMsg := diags.Err().Error()
for _, v := range []string{"feature", "memory"} { for _, v := range []string{"feature", "memory"} {
if !strings.Contains(errMsg, v) { if !strings.Contains(errMsg, v) {
t.Fatalf("no mention of missing variable %q", v) t.Fatalf("no mention of missing variable %q", v)

View File

@ -15,6 +15,7 @@ import (
"testing" "testing"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
"github.com/hashicorp/logutils" "github.com/hashicorp/logutils"
"github.com/hashicorp/terraform/config/module" "github.com/hashicorp/terraform/config/module"
@ -662,18 +663,12 @@ func testIDOnlyRefresh(c TestCase, opts terraform.ContextOpts, step TestStep, r
if err != nil { if err != nil {
return err return err
} }
if ws, es := ctx.Validate(); len(ws) > 0 || len(es) > 0 { if diags := ctx.Validate(); len(diags) > 0 {
if len(es) > 0 { if diags.HasErrors() {
estrs := make([]string, len(es)) return errwrap.Wrapf("config is invalid: {{err}}", diags.Err())
for i, e := range es {
estrs[i] = e.Error()
}
return fmt.Errorf(
"Configuration is invalid.\n\nWarnings: %#v\n\nErrors: %#v",
ws, estrs)
} }
log.Printf("[WARN] Config warnings: %#v", ws) log.Printf("[WARN] Config warnings:\n%s", diags.Err().Error())
} }
// Refresh! // Refresh!

View File

@ -5,6 +5,7 @@ import (
"log" "log"
"strings" "strings"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
@ -33,17 +34,12 @@ func testStep(
if err != nil { if err != nil {
return state, fmt.Errorf("Error initializing context: %s", err) return state, fmt.Errorf("Error initializing context: %s", err)
} }
if ws, es := ctx.Validate(); len(ws) > 0 || len(es) > 0 { if diags := ctx.Validate(); len(diags) > 0 {
if len(es) > 0 { if diags.HasErrors() {
estrs := make([]string, len(es)) return nil, errwrap.Wrapf("config is invalid: {{err}}", diags.Err())
for i, e := range es {
estrs[i] = e.Error()
}
return state, fmt.Errorf(
"Configuration is invalid.\n\nWarnings: %#v\n\nErrors: %#v",
ws, estrs)
} }
log.Printf("[WARN] Config warnings: %#v", ws)
log.Printf("[WARN] Config warnings:\n%s", diags)
} }
// Refresh! // Refresh!

View File

@ -8,6 +8,8 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/hashicorp/terraform/tfdiags"
"github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
"github.com/hashicorp/hcl" "github.com/hashicorp/hcl"
"github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config"
@ -671,29 +673,27 @@ func (c *Context) Stop() {
} }
// Validate validates the configuration and returns any warnings or errors. // Validate validates the configuration and returns any warnings or errors.
func (c *Context) Validate() ([]string, []error) { func (c *Context) Validate() tfdiags.Diagnostics {
defer c.acquireRun("validate")() defer c.acquireRun("validate")()
var errs error var diags tfdiags.Diagnostics
// Validate the configuration itself // Validate the configuration itself
if err := c.module.Validate(); err != nil { diags = diags.Append(c.module.Validate())
errs = multierror.Append(errs, err)
}
// This only needs to be done for the root module, since inter-module // This only needs to be done for the root module, since inter-module
// variables are validated in the module tree. // variables are validated in the module tree.
if config := c.module.Config(); config != nil { if config := c.module.Config(); config != nil {
// Validate the user variables // Validate the user variables
if err := smcUserVariables(config, c.variables); len(err) > 0 { for _, err := range smcUserVariables(config, c.variables) {
errs = multierror.Append(errs, err...) diags = diags.Append(err)
} }
} }
// If we have errors at this point, the graphing has no chance, // If we have errors at this point, the graphing has no chance,
// so just bail early. // so just bail early.
if errs != nil { if diags.HasErrors() {
return nil, []error{errs} return diags
} }
// Build the graph so we can walk it and run Validate on nodes. // Build the graph so we can walk it and run Validate on nodes.
@ -702,24 +702,29 @@ func (c *Context) Validate() ([]string, []error) {
// graph again later after Planning. // graph again later after Planning.
graph, err := c.Graph(GraphTypeValidate, nil) graph, err := c.Graph(GraphTypeValidate, nil)
if err != nil { if err != nil {
return nil, []error{err} diags = diags.Append(err)
return diags
} }
// Walk // Walk
walker, err := c.walk(graph, walkValidate) walker, err := c.walk(graph, walkValidate)
if err != nil { if err != nil {
return nil, multierror.Append(errs, err).Errors diags = diags.Append(err)
} }
// Return the result
rerrs := multierror.Append(errs, walker.ValidationErrors...)
sort.Strings(walker.ValidationWarnings) sort.Strings(walker.ValidationWarnings)
sort.Slice(rerrs.Errors, func(i, j int) bool { sort.Slice(walker.ValidationErrors, func(i, j int) bool {
return rerrs.Errors[i].Error() < rerrs.Errors[j].Error() return walker.ValidationErrors[i].Error() < walker.ValidationErrors[j].Error()
}) })
return walker.ValidationWarnings, rerrs.Errors for _, warn := range walker.ValidationWarnings {
diags = diags.Append(tfdiags.SimpleWarning(warn))
}
for _, err := range walker.ValidationErrors {
diags = diags.Append(err)
}
return diags
} }
// Module returns the module tree associated with this context. // Module returns the module tree associated with this context.

View File

@ -7966,12 +7966,9 @@ func TestContext2Apply_vars(t *testing.T) {
}, },
}) })
w, e := ctx.Validate() diags := ctx.Validate()
if len(w) > 0 { if len(diags) != 0 {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) > 0 {
t.Fatalf("bad: %s", e)
} }
if _, err := ctx.Plan(); err != nil { if _, err := ctx.Plan(); err != nil {
@ -8009,12 +8006,9 @@ func TestContext2Apply_varsEnv(t *testing.T) {
), ),
}) })
w, e := ctx.Validate() diags := ctx.Validate()
if len(w) > 0 { if len(diags) != 0 {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) > 0 {
t.Fatalf("bad: %s", e)
} }
if _, err := ctx.Plan(); err != nil { if _, err := ctx.Plan(); err != nil {

View File

@ -3499,12 +3499,9 @@ func TestContext2Plan_resourceNestedCount(t *testing.T) {
State: s, State: s,
}) })
w, e := ctx.Validate() diags := ctx.Validate()
if len(w) > 0 { if len(diags) != 0 {
t.Fatalf("warnings generated on validate: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) > 0 {
t.Fatalf("errors generated on validate: %#v", e)
} }
_, err := ctx.Refresh() _, err := ctx.Refresh()
@ -3582,12 +3579,9 @@ output "out" {
}) })
// if this ever fails to pass validate, add a resource to reference in the config // if this ever fails to pass validate, add a resource to reference in the config
w, e := ctx.Validate() diags := ctx.Validate()
if len(w) > 0 { if len(diags) != 0 {
t.Fatalf("warnings generated on validate: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) > 0 {
t.Fatalf("errors generated on validate: %v", e)
} }
_, err := ctx.Refresh() _, err := ctx.Refresh()
@ -3630,12 +3624,9 @@ resource "aws_instance" "foo" {
}) })
// if this ever fails to pass validate, add a resource to reference in the config // if this ever fails to pass validate, add a resource to reference in the config
w, e := ctx.Validate() diags := ctx.Validate()
if len(w) > 0 { if len(diags) != 0 {
t.Fatalf("warnings generated on validate: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) > 0 {
t.Fatalf("errors generated on validate: %v", e)
} }
_, err := ctx.Refresh() _, err := ctx.Refresh()

View File

@ -1042,12 +1042,9 @@ func TestContext2Validate(t *testing.T) {
), ),
}) })
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if len(diags) != 0 {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) > 0 {
t.Fatalf("bad: %s", e)
} }
} }

View File

@ -18,12 +18,9 @@ func TestContext2Validate_badCount(t *testing.T) {
), ),
}) })
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if !diags.HasErrors() {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) == 0 {
t.Fatalf("bad: %#v", e)
} }
} }
@ -39,12 +36,9 @@ func TestContext2Validate_badVar(t *testing.T) {
), ),
}) })
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if !diags.HasErrors() {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) == 0 {
t.Fatalf("bad: %#v", e)
} }
} }
@ -63,12 +57,9 @@ func TestContext2Validate_varMapOverrideOld(t *testing.T) {
}, },
}) })
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if !diags.HasErrors() {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) == 0 {
t.Fatalf("bad: %s", e)
} }
} }
@ -78,12 +69,9 @@ func TestContext2Validate_varNoDefaultExplicitType(t *testing.T) {
Module: m, Module: m,
}) })
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if !diags.HasErrors() {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) == 0 {
t.Fatalf("bad: %#v", e)
} }
} }
@ -112,14 +100,9 @@ func TestContext2Validate_computedVar(t *testing.T) {
return fmt.Errorf("Configure should not be called for provider") return fmt.Errorf("Configure should not be called for provider")
} }
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if diags.HasErrors() {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) > 0 {
for _, err := range e {
t.Errorf("bad: %s", err)
}
} }
} }
@ -138,12 +121,9 @@ func TestContext2Validate_countComputed(t *testing.T) {
), ),
}) })
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if diags.HasErrors() {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) > 0 {
t.Fatalf("bad: %s", e)
} }
} }
@ -159,12 +139,9 @@ func TestContext2Validate_countNegative(t *testing.T) {
), ),
}) })
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if !diags.HasErrors() {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) == 0 {
t.Fatalf("bad: %#v", e)
} }
} }
@ -180,12 +157,9 @@ func TestContext2Validate_countVariable(t *testing.T) {
), ),
}) })
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if diags.HasErrors() {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) > 0 {
t.Fatalf("bad: %s", e)
} }
} }
@ -201,12 +175,9 @@ func TestContext2Validate_countVariableNoDefault(t *testing.T) {
), ),
}) })
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if !diags.HasErrors() {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) != 1 {
t.Fatalf("bad: %s", e)
} }
} }
@ -224,12 +195,9 @@ func TestContext2Validate_cycle(t *testing.T) {
), ),
}) })
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if !diags.HasErrors() {
t.Fatalf("expected no warns, got: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) != 1 {
t.Fatalf("expected 1 err, got: %s", e)
} }
} }
*/ */
@ -246,12 +214,9 @@ func TestContext2Validate_moduleBadOutput(t *testing.T) {
), ),
}) })
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if !diags.HasErrors() {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) == 0 {
t.Fatalf("bad: %s", e)
} }
} }
@ -267,12 +232,9 @@ func TestContext2Validate_moduleGood(t *testing.T) {
), ),
}) })
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if diags.HasErrors() {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) > 0 {
t.Fatalf("bad: %#v", e)
} }
} }
@ -290,12 +252,9 @@ func TestContext2Validate_moduleBadResource(t *testing.T) {
p.ValidateResourceReturnErrors = []error{fmt.Errorf("bad")} p.ValidateResourceReturnErrors = []error{fmt.Errorf("bad")}
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if !diags.HasErrors() {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) == 0 {
t.Fatalf("bad: %#v", e)
} }
} }
@ -311,13 +270,9 @@ func TestContext2Validate_moduleDepsShouldNotCycle(t *testing.T) {
), ),
}) })
w, e := ctx.Validate() diags := ctx.Validate()
if diags.HasErrors() {
if len(w) > 0 { t.Fatalf("bad: %#v", diags)
t.Fatalf("expected no warnings, got: %s", w)
}
if len(e) > 0 {
t.Fatalf("expected no errors, got: %s", e)
} }
} }
@ -360,12 +315,9 @@ func TestContext2Validate_moduleProviderInheritOrphan(t *testing.T) {
return nil, nil return nil, nil
} }
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if diags.HasErrors() {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) > 0 {
t.Fatalf("bad: %s", e)
} }
} }
@ -388,12 +340,9 @@ func TestContext2Validate_moduleProviderVar(t *testing.T) {
return nil, c.CheckSet([]string{"foo"}) return nil, c.CheckSet([]string{"foo"})
} }
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if diags.HasErrors() {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) > 0 {
t.Fatalf("bad: %s", e)
} }
} }
@ -413,12 +362,9 @@ func TestContext2Validate_moduleProviderInheritUnused(t *testing.T) {
return nil, c.CheckSet([]string{"foo"}) return nil, c.CheckSet([]string{"foo"})
} }
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if diags.HasErrors() {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) > 0 {
t.Fatalf("bad: %s", e)
} }
} }
@ -455,12 +401,9 @@ func TestContext2Validate_orphans(t *testing.T) {
return nil, c.CheckSet([]string{"foo"}) return nil, c.CheckSet([]string{"foo"})
} }
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if diags.HasErrors() {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) > 0 {
t.Fatalf("bad: %s", e)
} }
} }
@ -478,15 +421,12 @@ func TestContext2Validate_providerConfig_bad(t *testing.T) {
p.ValidateReturnErrors = []error{fmt.Errorf("bad")} p.ValidateReturnErrors = []error{fmt.Errorf("bad")}
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if len(diags) != 1 {
t.Fatalf("bad: %#v", w) t.Fatalf("wrong number of diagnostics %d; want %d", len(diags), 1)
} }
if len(e) == 0 { if !strings.Contains(diags.Err().Error(), "bad") {
t.Fatalf("bad: %s", e) t.Fatalf("bad: %s", diags.Err().Error())
}
if !strings.Contains(fmt.Sprintf("%s", e), "bad") {
t.Fatalf("bad: %s", e)
} }
} }
@ -504,12 +444,9 @@ func TestContext2Validate_providerConfig_badEmpty(t *testing.T) {
p.ValidateReturnErrors = []error{fmt.Errorf("bad")} p.ValidateReturnErrors = []error{fmt.Errorf("bad")}
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if !diags.HasErrors() {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) == 0 {
t.Fatalf("bad: %#v", e)
} }
} }
@ -525,12 +462,9 @@ func TestContext2Validate_providerConfig_good(t *testing.T) {
), ),
}) })
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if diags.HasErrors() {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) > 0 {
t.Fatalf("bad: %#v", e)
} }
} }
@ -552,12 +486,9 @@ func TestContext2Validate_provisionerConfig_bad(t *testing.T) {
pr.ValidateReturnErrors = []error{fmt.Errorf("bad")} pr.ValidateReturnErrors = []error{fmt.Errorf("bad")}
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if !diags.HasErrors() {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) == 0 {
t.Fatalf("bad: %#v", e)
} }
} }
@ -583,12 +514,9 @@ func TestContext2Validate_provisionerConfig_good(t *testing.T) {
}, },
}) })
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if diags.HasErrors() {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) > 0 {
t.Fatalf("bad: %#v", e)
} }
} }
@ -604,12 +532,9 @@ func TestContext2Validate_requiredVar(t *testing.T) {
), ),
}) })
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if !diags.HasErrors() {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) == 0 {
t.Fatalf("bad: %s", e)
} }
} }
@ -627,12 +552,9 @@ func TestContext2Validate_resourceConfig_bad(t *testing.T) {
p.ValidateResourceReturnErrors = []error{fmt.Errorf("bad")} p.ValidateResourceReturnErrors = []error{fmt.Errorf("bad")}
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if !diags.HasErrors() {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) == 0 {
t.Fatalf("bad: %s", e)
} }
} }
@ -648,12 +570,9 @@ func TestContext2Validate_resourceConfig_good(t *testing.T) {
), ),
}) })
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if diags.HasErrors() {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) > 0 {
t.Fatalf("bad: %#v", e)
} }
} }
@ -669,12 +588,9 @@ func TestContext2Validate_resourceNameSymbol(t *testing.T) {
), ),
}) })
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if !diags.HasErrors() {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) == 0 {
t.Fatalf("bad: %s", e)
} }
} }
@ -690,12 +606,9 @@ func TestContext2Validate_selfRef(t *testing.T) {
), ),
}) })
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if !diags.HasErrors() {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) == 0 {
t.Fatalf("bad: %s", e)
} }
} }
@ -711,12 +624,9 @@ func TestContext2Validate_selfRefMulti(t *testing.T) {
), ),
}) })
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if !diags.HasErrors() {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) == 0 {
t.Fatalf("bad: %#v", e)
} }
} }
@ -732,12 +642,9 @@ func TestContext2Validate_selfRefMultiAll(t *testing.T) {
), ),
}) })
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if !diags.HasErrors() {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) == 0 {
t.Fatalf("bad: %#v", e)
} }
} }
@ -775,12 +682,9 @@ func TestContext2Validate_tainted(t *testing.T) {
return nil, c.CheckSet([]string{"foo"}) return nil, c.CheckSet([]string{"foo"})
} }
w, e := c.Validate() diags := c.Validate()
if len(w) > 0 { if diags.HasErrors() {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", diags)
}
if len(e) > 0 {
t.Fatalf("bad: %#v", e)
} }
} }
@ -815,16 +719,9 @@ func TestContext2Validate_targetedDestroy(t *testing.T) {
Destroy: true, Destroy: true,
}) })
w, e := ctx.Validate() diags := ctx.Validate()
if len(w) > 0 { if diags.HasErrors() {
warnStr := "" t.Fatalf("bad: %#v", diags)
for _, v := range w {
warnStr = warnStr + " " + v
}
t.Fatalf("bad: %s", warnStr)
}
if len(e) > 0 {
t.Fatalf("bad: %s", e)
} }
} }
@ -875,12 +772,9 @@ func TestContext2Validate_interpolateVar(t *testing.T) {
UIInput: input, UIInput: input,
}) })
w, e := ctx.Validate() diags := ctx.Validate()
if w != nil { if diags.HasErrors() {
t.Log("warnings:", w) t.Fatalf("bad: %#v", diags)
}
if e != nil {
t.Fatal("err:", e)
} }
} }
@ -904,12 +798,9 @@ func TestContext2Validate_interpolateComputedModuleVarDef(t *testing.T) {
UIInput: input, UIInput: input,
}) })
w, e := ctx.Validate() diags := ctx.Validate()
if w != nil { if diags.HasErrors() {
t.Log("warnings:", w) t.Fatalf("bad: %#v", diags)
}
if e != nil {
t.Fatal("err:", e)
} }
} }
@ -932,12 +823,9 @@ func TestContext2Validate_interpolateMap(t *testing.T) {
UIInput: input, UIInput: input,
}) })
w, e := ctx.Validate() diags := ctx.Validate()
if w != nil { if diags.HasErrors() {
t.Log("warnings:", w) t.Fatalf("bad: %#v", diags)
}
if e != nil {
t.Fatal("err:", e)
} }
} }