diff --git a/terraform/context.go b/terraform/context.go index 0e7194e58c..c7cb86bfb7 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -204,12 +204,40 @@ func (c *Context) Validate() ([]string, []error) { rerr = multierror.ErrorAppend(rerr, errs...) } + // Validate the graph + g, err := c.graph() + if err != nil { + rerr = multierror.ErrorAppend(rerr, fmt.Errorf( + "Error creating graph: %s", err)) + } + + // Walk the graph and validate all the configs + var warns []string var errs []error + err = g.Walk(c.validateWalkFn(&warns, &errs)) + if err != nil { + rerr = multierror.ErrorAppend(rerr, fmt.Errorf( + "Error validating resources in graph: %s", err)) + } + if len(errs) > 0 { + rerr = multierror.ErrorAppend(rerr, errs...) + } + + errs = nil if rerr != nil && len(rerr.Errors) > 0 { errs = rerr.Errors } - return nil, errs + return warns, errs +} + +func (c *Context) graph() (*depgraph.Graph, error) { + return Graph(&GraphOpts{ + Config: c.config, + Diff: c.diff, + Providers: c.providers, + State: c.state, + }) } func (c *Context) acquireRun() chan<- struct{} { @@ -414,6 +442,37 @@ func (c *Context) refreshWalkFn(result *State) depgraph.WalkFunc { return c.genericWalkFn(c.variables, cb) } +func (c *Context) validateWalkFn(rws *[]string, res *[]error) depgraph.WalkFunc { + return func(n *depgraph.Noun) error { + // If it is the root node, ignore + if n.Name == GraphRootNode { + return nil + } + + switch rn := n.Meta.(type) { + case *GraphNodeResource: + case *GraphNodeResourceProvider: + rc := NewResourceConfig(rn.Config.RawConfig) + + for k, p := range rn.Providers { + log.Printf("[INFO] Validating provider: %s", k) + ws, es := p.Validate(rc) + for i, w := range ws { + ws[i] = fmt.Sprintf("Provider '%s' warning: %s", k, w) + } + for i, e := range es { + es[i] = fmt.Errorf("Provider '%s' error: %s", k, e) + } + + *rws = append(*rws, ws...) + *res = append(*res, es...) + } + } + + return nil + } +} + func (c *Context) genericWalkFn( invars map[string]string, cb genericWalkFunc) depgraph.WalkFunc { diff --git a/terraform/context_test.go b/terraform/context_test.go index 9cbb89cf8d..eec78017cc 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -37,6 +37,46 @@ func TestContextValidate_badVar(t *testing.T) { } } +func TestContextValidate_providerConfig_bad(t *testing.T) { + config := testConfig(t, "validate-bad-pc") + p := testProvider("aws") + c := testContext(t, &ContextOpts{ + Config: config, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + p.ValidateReturnErrors = []error{fmt.Errorf("bad")} + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) == 0 { + t.Fatalf("bad: %#v", e) + } +} + +func TestContextValidate_providerConfig_good(t *testing.T) { + config := testConfig(t, "validate-bad-pc") + p := testProvider("aws") + c := testContext(t, &ContextOpts{ + Config: config, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) > 0 { + t.Fatalf("bad: %#v", e) + } +} + func TestContextValidate_requiredVar(t *testing.T) { config := testConfig(t, "validate-required-var") c := testContext(t, &ContextOpts{ diff --git a/terraform/test-fixtures/validate-bad-pc/main.tf b/terraform/test-fixtures/validate-bad-pc/main.tf new file mode 100644 index 0000000000..2aada399b5 --- /dev/null +++ b/terraform/test-fixtures/validate-bad-pc/main.tf @@ -0,0 +1,5 @@ +provider "aws" { + foo = "bar" +} + +resource "aws_instance" "test" {}