terraform: validation in progress

This commit is contained in:
Mitchell Hashimoto 2015-02-04 20:02:18 -05:00
parent e86698c50d
commit c308405b53
10 changed files with 207 additions and 81 deletions

View File

@ -1,6 +1,7 @@
package terraform package terraform
import ( import (
"fmt"
"sync" "sync"
"github.com/hashicorp/terraform/config/module" "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. // Validate validates the configuration and returns any warnings or errors.
func (c *Context2) Validate() ([]string, []error) { 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 := c.evalContext()
evalCtx.ComputeMissing = true evalCtx.ComputeMissing = true
@ -71,14 +80,7 @@ func (c *Context2) Validate() ([]string, []error) {
return nil, []error{err} return nil, []error{err}
} }
// Valiate the graph
if err := graph.Validate(); err != nil {
return nil, []error{err}
}
// Walk the graph // Walk the graph
var warns []string
var errs []error
var lock sync.Mutex var lock sync.Mutex
graph.Walk(func(v dag.Vertex) { graph.Walk(func(v dag.Vertex) {
ev, ok := v.(GraphNodeEvalable) ev, ok := v.(GraphNodeEvalable)
@ -86,7 +88,12 @@ func (c *Context2) Validate() ([]string, []error) {
return 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 { if err == nil {
return return
} }

View File

@ -1,6 +1,8 @@
package terraform package terraform
import ( import (
"fmt"
"strings"
"testing" "testing"
) )
@ -19,7 +21,7 @@ func TestContext2Validate(t *testing.T) {
t.Fatalf("bad: %#v", w) t.Fatalf("bad: %#v", w)
} }
if len(e) > 0 { 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) { func TestContextValidate_goodModule(t *testing.T) {
p := testProvider("aws") 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) { func TestContextValidate_tainted(t *testing.T) {
p := testProvider("aws") p := testProvider("aws")
m := testModule(t, "validate-good") 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) { func TestContextValidate_providerConfig_badEmpty(t *testing.T) {
m := testModule(t, "validate-bad-pc-empty") m := testModule(t, "validate-bad-pc-empty")
p := testProvider("aws") p := testProvider("aws")

View File

@ -30,7 +30,13 @@ func (ctx *BuiltinEvalContext) InitProvider(n string) (ResourceProvider, error)
return nil, fmt.Errorf("Provider '%s' not found", n) 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 { func (ctx *BuiltinEvalContext) Provider(n string) ResourceProvider {

View File

@ -1,8 +1,6 @@
package terraform package terraform
import ( import (
"fmt"
"github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config"
) )
@ -22,6 +20,7 @@ func (e *EvalValidateError) Error() string {
type EvalValidateResource struct { type EvalValidateResource struct {
Provider EvalNode Provider EvalNode
Config *config.RawConfig Config *config.RawConfig
ProviderType string
} }
func (n *EvalValidateResource) Args() ([]EvalNode, []EvalType) { func (n *EvalValidateResource) Args() ([]EvalNode, []EvalType) {
@ -31,8 +30,10 @@ func (n *EvalValidateResource) Args() ([]EvalNode, []EvalType) {
func (n *EvalValidateResource) Eval( func (n *EvalValidateResource) Eval(
ctx EvalContext, args []interface{}) (interface{}, error) { ctx EvalContext, args []interface{}) (interface{}, error) {
// TODO: test
//provider := args[0].(ResourceProvider) //provider := args[0].(ResourceProvider)
return nil, fmt.Errorf("WHAT") return nil, nil
} }
func (n *EvalValidateResource) Type() EvalType { func (n *EvalValidateResource) Type() EvalType {

View File

@ -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 return g, nil
} }
@ -54,5 +59,6 @@ func (b *BuiltinGraphBuilder) Steps() []GraphTransformer {
&MissingProviderTransformer{Providers: b.Providers}, &MissingProviderTransformer{Providers: b.Providers},
&ProviderTransformer{}, &ProviderTransformer{},
&PruneProviderTransformer{}, &PruneProviderTransformer{},
&RootTransformer{},
} }
} }

View File

@ -123,7 +123,9 @@ func (n *GraphNodeConfigResource) Name() string {
func (n *GraphNodeConfigResource) EvalTree() EvalNode { func (n *GraphNodeConfigResource) EvalTree() EvalNode {
return &EvalValidateResource{ return &EvalValidateResource{
Provider: &EvalGetProvider{Name: n.ProvidedBy()}, Provider: &EvalGetProvider{Name: n.ProvidedBy()},
Config: n.Resource.RawConfig, Config: n.Resource.RawConfig,
ProviderType: n.ProvidedBy(),
} }
} }

View File

@ -0,0 +1,5 @@
provider "aws" {}
resource "aws_instance" "foo" {}
provider "do" {}
resource "do_droplet" "bar" {}

View File

@ -119,10 +119,8 @@ func (n *graphNodeOrphanResource) ProvidedBy() string {
} }
// GraphNodeEvalable impl. // GraphNodeEvalable impl.
func (n *graphNodeOrphanResource) EvalTree() EvalNode {
return nil
/* /*
TODO func (n *graphNodeOrphanResource) EvalTree() EvalNode {
return &EvalSequence{ return &EvalSequence{
Nodes: []EvalNode{ Nodes: []EvalNode{
&EvalRefresh{}, &EvalRefresh{},
@ -131,8 +129,8 @@ func (n *graphNodeOrphanResource) EvalTree() EvalNode {
&EvalCommitState{}, &EvalCommitState{},
}, },
} }
*/
} }
*/
func (n *graphNodeOrphanResource) dependableName() string { func (n *graphNodeOrphanResource) dependableName() string {
return n.ResourceName return n.ResourceName

View File

@ -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"
}

View File

@ -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
`