diff --git a/terraform/context.go b/terraform/context.go index 0e36e83fd..7bbedb59a 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -1,6 +1,7 @@ package terraform import ( + "fmt" "sync" "github.com/hashicorp/terraform/config/module" @@ -62,6 +63,14 @@ func (c *Context2) GraphBuilder() GraphBuilder { // Validate validates the configuration and returns any warnings or errors. func (c *Context2) Validate() ([]string, []error) { + var warns []string + var errs []error + + // Validate the configuration itself + if err := c.module.Validate(); err != nil { + errs = append(errs, err) + } + evalCtx := c.evalContext() evalCtx.ComputeMissing = true @@ -71,14 +80,7 @@ func (c *Context2) Validate() ([]string, []error) { return nil, []error{err} } - // Valiate the graph - if err := graph.Validate(); err != nil { - return nil, []error{err} - } - // Walk the graph - var warns []string - var errs []error var lock sync.Mutex graph.Walk(func(v dag.Vertex) { ev, ok := v.(GraphNodeEvalable) @@ -86,7 +88,12 @@ func (c *Context2) Validate() ([]string, []error) { return } - _, err := Eval(ev.EvalTree(), evalCtx) + tree := ev.EvalTree() + if tree == nil { + panic(fmt.Sprintf("%s (%T): nil eval tree", dag.VertexName(v), v)) + } + + _, err := Eval(tree, evalCtx) if err == nil { return } diff --git a/terraform/context_test.go b/terraform/context_test.go index 938db75b1..659ce2a6d 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -1,6 +1,8 @@ package terraform import ( + "fmt" + "strings" "testing" ) @@ -19,7 +21,7 @@ func TestContext2Validate(t *testing.T) { t.Fatalf("bad: %#v", w) } if len(e) > 0 { - t.Fatalf("bad: %#v", e) + t.Fatalf("bad: %s", e) } } @@ -42,6 +44,70 @@ func TestContext2Validate_badVar(t *testing.T) { } } +func TestContext2Validate_orphans(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "validate-good") + state := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.web": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + }, + }, + }, + }, + }, + } + c := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: state, + }) + + p.ValidateResourceFn = func( + t string, c *ResourceConfig) ([]string, []error) { + return nil, c.CheckSet([]string{"foo"}) + } + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) > 0 { + t.Fatalf("bad: %#v", e) + } +} + +func TestContext2Validate_providerConfig_bad(t *testing.T) { + m := testModule(t, "validate-bad-pc") + p := testProvider("aws") + c := testContext2(t, &ContextOpts{ + Module: m, + 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: %s", e) + } + if !strings.Contains(fmt.Sprintf("%s", e), "bad") { + t.Fatalf("bad: %s", e) + } +} + /* func TestContextValidate_goodModule(t *testing.T) { p := testProvider("aws") @@ -188,46 +254,6 @@ func TestContextValidate_moduleProviderInherit(t *testing.T) { } } -func TestContextValidate_orphans(t *testing.T) { - p := testProvider("aws") - m := testModule(t, "validate-good") - state := &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: rootModulePath, - Resources: map[string]*ResourceState{ - "aws_instance.web": &ResourceState{ - Type: "aws_instance", - Primary: &InstanceState{ - ID: "bar", - }, - }, - }, - }, - }, - } - c := testContext(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - State: state, - }) - - p.ValidateResourceFn = func( - t string, c *ResourceConfig) ([]string, []error) { - return nil, c.CheckSet([]string{"foo"}) - } - - w, e := c.Validate() - if len(w) > 0 { - t.Fatalf("bad: %#v", w) - } - if len(e) > 0 { - t.Fatalf("bad: %#v", e) - } -} - func TestContextValidate_tainted(t *testing.T) { p := testProvider("aws") m := testModule(t, "validate-good") @@ -270,27 +296,6 @@ func TestContextValidate_tainted(t *testing.T) { } } -func TestContextValidate_providerConfig_bad(t *testing.T) { - m := testModule(t, "validate-bad-pc") - p := testProvider("aws") - c := testContext(t, &ContextOpts{ - Module: m, - 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_badEmpty(t *testing.T) { m := testModule(t, "validate-bad-pc-empty") p := testProvider("aws") diff --git a/terraform/eval_context_builtin.go b/terraform/eval_context_builtin.go index d74b490b7..cffc67908 100644 --- a/terraform/eval_context_builtin.go +++ b/terraform/eval_context_builtin.go @@ -30,7 +30,13 @@ func (ctx *BuiltinEvalContext) InitProvider(n string) (ResourceProvider, error) return nil, fmt.Errorf("Provider '%s' not found", n) } - return f() + p, err := f() + if err != nil { + return nil, err + } + + ctx.providers[n] = p + return p, nil } func (ctx *BuiltinEvalContext) Provider(n string) ResourceProvider { diff --git a/terraform/eval_validate.go b/terraform/eval_validate.go index 819dd8431..7a729f23e 100644 --- a/terraform/eval_validate.go +++ b/terraform/eval_validate.go @@ -1,8 +1,6 @@ package terraform import ( - "fmt" - "github.com/hashicorp/terraform/config" ) @@ -20,8 +18,9 @@ func (e *EvalValidateError) Error() string { // EvalValidateResource is an EvalNode implementation that validates // the configuration of a resource. type EvalValidateResource struct { - Provider EvalNode - Config *config.RawConfig + Provider EvalNode + Config *config.RawConfig + ProviderType string } func (n *EvalValidateResource) Args() ([]EvalNode, []EvalType) { @@ -31,8 +30,10 @@ func (n *EvalValidateResource) Args() ([]EvalNode, []EvalType) { func (n *EvalValidateResource) Eval( ctx EvalContext, args []interface{}) (interface{}, error) { + // TODO: test + //provider := args[0].(ResourceProvider) - return nil, fmt.Errorf("WHAT") + return nil, nil } func (n *EvalValidateResource) Type() EvalType { diff --git a/terraform/graph_builder.go b/terraform/graph_builder.go index 6274171a0..40baa5491 100644 --- a/terraform/graph_builder.go +++ b/terraform/graph_builder.go @@ -41,6 +41,11 @@ func (b *BuiltinGraphBuilder) Build(path []string) (*Graph, error) { } } + // Validate the graph structure + if err := g.Validate(); err != nil { + return nil, err + } + return g, nil } @@ -54,5 +59,6 @@ func (b *BuiltinGraphBuilder) Steps() []GraphTransformer { &MissingProviderTransformer{Providers: b.Providers}, &ProviderTransformer{}, &PruneProviderTransformer{}, + &RootTransformer{}, } } diff --git a/terraform/graph_config_node.go b/terraform/graph_config_node.go index 833f82171..887fc1682 100644 --- a/terraform/graph_config_node.go +++ b/terraform/graph_config_node.go @@ -123,7 +123,9 @@ func (n *GraphNodeConfigResource) Name() string { func (n *GraphNodeConfigResource) EvalTree() EvalNode { return &EvalValidateResource{ Provider: &EvalGetProvider{Name: n.ProvidedBy()}, - Config: n.Resource.RawConfig, + + Config: n.Resource.RawConfig, + ProviderType: n.ProvidedBy(), } } diff --git a/terraform/test-fixtures/transform-root-basic/main.tf b/terraform/test-fixtures/transform-root-basic/main.tf new file mode 100644 index 000000000..e4ff4b3e9 --- /dev/null +++ b/terraform/test-fixtures/transform-root-basic/main.tf @@ -0,0 +1,5 @@ +provider "aws" {} +resource "aws_instance" "foo" {} + +provider "do" {} +resource "do_droplet" "bar" {} diff --git a/terraform/transform_orphan.go b/terraform/transform_orphan.go index 5ce5a81fe..7b9e69411 100644 --- a/terraform/transform_orphan.go +++ b/terraform/transform_orphan.go @@ -119,10 +119,8 @@ func (n *graphNodeOrphanResource) ProvidedBy() string { } // GraphNodeEvalable impl. +/* func (n *graphNodeOrphanResource) EvalTree() EvalNode { - return nil - /* - TODO return &EvalSequence{ Nodes: []EvalNode{ &EvalRefresh{}, @@ -131,8 +129,8 @@ func (n *graphNodeOrphanResource) EvalTree() EvalNode { &EvalCommitState{}, }, } - */ } +*/ func (n *graphNodeOrphanResource) dependableName() string { return n.ResourceName diff --git a/terraform/transform_root.go b/terraform/transform_root.go new file mode 100644 index 000000000..be75e7302 --- /dev/null +++ b/terraform/transform_root.go @@ -0,0 +1,38 @@ +package terraform + +import ( + "github.com/hashicorp/terraform/dag" +) + +// RootTransformer is a GraphTransformer that adds a root to the graph. +type RootTransformer struct{} + +func (t *RootTransformer) Transform(g *Graph) error { + // If we already have a good root, we're done + if _, err := g.Root(); err == nil { + return nil + } + + // Add a root + var root graphNodeRoot + g.Add(root) + + // Connect the root to all the edges that need it + for _, v := range g.Vertices() { + if v == root { + continue + } + + if g.UpEdges(v).Len() == 0 { + g.Connect(dag.BasicEdge(root, v)) + } + } + + return nil +} + +type graphNodeRoot struct{} + +func (n graphNodeRoot) Name() string { + return "root" +} diff --git a/terraform/transform_root_test.go b/terraform/transform_root_test.go new file mode 100644 index 000000000..68f5c5114 --- /dev/null +++ b/terraform/transform_root_test.go @@ -0,0 +1,58 @@ +package terraform + +import ( + "strings" + "testing" +) + +func TestRootTransformer(t *testing.T) { + mod := testModule(t, "transform-root-basic") + + g := Graph{Path: RootModulePath} + { + tf := &ConfigTransformer{Module: mod} + if err := tf.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + } + + { + transform := &ProviderTransformer{} + if err := transform.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + } + + { + transform := &RootTransformer{} + if err := transform.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + } + + actual := strings.TrimSpace(g.String()) + expected := strings.TrimSpace(testTransformRootBasicStr) + if actual != expected { + t.Fatalf("bad:\n\n%s", actual) + } + + root, err := g.Root() + if err != nil { + t.Fatalf("err: %s", err) + } + if _, ok := root.(graphNodeRoot); !ok { + t.Fatalf("bad: %#v", root) + } +} + +const testTransformRootBasicStr = ` +aws_instance.foo + provider.aws +do_droplet.bar + provider.do +provider.aws +provider.do +root + aws_instance.foo + do_droplet.bar +`