diff --git a/dag/dag.go b/dag/dag.go new file mode 100644 index 000000000..e41de6053 --- /dev/null +++ b/dag/dag.go @@ -0,0 +1,14 @@ +package dag + +// AcyclicGraph is a specialization of Graph that cannot have cycles. With +// this property, we get the property of sane graph traversal. +type AcyclicGraph struct { + *Graph +} + +// WalkFunc is the callback used for walking the graph. +type WalkFunc func(Vertex) + +// Walk walks the graph, calling your callback as each node is visited. +func (g *AcyclicGraph) Walk(cb WalkFunc) { +} diff --git a/dag/graph.go b/dag/graph.go index b1cbd84d4..4d4450cc4 100644 --- a/dag/graph.go +++ b/dag/graph.go @@ -26,9 +26,6 @@ type NamedVertex interface { Name() string } -// WalkFunc is the callback used for walking the graph. -type WalkFunc func(Vertex) - // Vertices returns the list of all the vertices in the graph. func (g *Graph) Vertices() []Vertex { return g.vertices @@ -81,10 +78,6 @@ func (g *Graph) Connect(edge Edge) { s.Add(source) } -// Walk walks the graph, calling your callback as each node is visited. -func (g *Graph) Walk(cb WalkFunc) { -} - // String outputs some human-friendly output for the graph structure. func (g *Graph) String() string { var buf bytes.Buffer diff --git a/terraform/graph_config.go b/terraform/graph_config.go index ca02c697d..716241ca7 100644 --- a/terraform/graph_config.go +++ b/terraform/graph_config.go @@ -39,8 +39,12 @@ func Graph2(mod *module.Tree) (*dag.Graph, error) { } // Write all the modules out + children := mod.Children() for _, m := range config.Modules { - nodes = append(nodes, &GraphNodeConfigModule{Module: m}) + nodes = append(nodes, &GraphNodeConfigModule{ + Module: m, + Tree: children[m.Name], + }) } // Build the full map of the var names to the nodes. diff --git a/terraform/graph_config_node.go b/terraform/graph_config_node.go index cc0e43202..3fa479b8a 100644 --- a/terraform/graph_config_node.go +++ b/terraform/graph_config_node.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/hashicorp/terraform/config" + "github.com/hashicorp/terraform/config/module" "github.com/hashicorp/terraform/dag" ) @@ -27,6 +28,7 @@ type graphNodeConfig interface { // GraphNodeConfigModule represents a module within the configuration graph. type GraphNodeConfigModule struct { Module *config.Module + Tree *module.Tree } func (n *GraphNodeConfigModule) Name() string { diff --git a/terraform/semantics.go b/terraform/semantics.go index 51c17b68a..566a3c279 100644 --- a/terraform/semantics.go +++ b/terraform/semantics.go @@ -3,9 +3,70 @@ package terraform import ( "fmt" + "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform/config" + "github.com/hashicorp/terraform/dag" ) +// GraphSemanticChecker is the interface that semantic checks across +// the entire Terraform graph implement. +// +// The graph should NOT be modified by the semantic checker. +type GraphSemanticChecker interface { + Check(*dag.Graph) error +} + +// UnorderedSemanticCheckRunner is an implementation of GraphSemanticChecker +// that runs a list of SemanticCheckers against the vertices of the graph +// in no specified order. +type UnorderedSemanticCheckRunner struct { + Checks []SemanticChecker +} + +func (sc *UnorderedSemanticCheckRunner) Check(g *dag.Graph) error { + var err error + for _, v := range g.Vertices() { + for _, check := range sc.Checks { + if e := check.Check(g, v); e != nil { + err = multierror.Append(err, e) + } + } + } + + return err +} + +// SemanticChecker is the interface that semantic checks across the +// Terraform graph implement. Errors are accumulated. Even after an error +// is returned, child vertices in the graph will still be visited. +// +// The graph should NOT be modified by the semantic checker. +// +// The order in which vertices are visited is left unspecified, so the +// semantic checks should not rely on that. +type SemanticChecker interface { + Check(*dag.Graph, dag.Vertex) error +} + +// SemanticCheckModulesExist is an implementation of SemanticChecker that +// verifies that all the modules that are referenced in the graph exist. +type SemanticCheckModulesExist struct{} + +// TODO: test +func (*SemanticCheckModulesExist) Check(g *dag.Graph, v dag.Vertex) error { + mn, ok := v.(*GraphNodeConfigModule) + if !ok { + return nil + } + + if mn.Tree == nil { + return fmt.Errorf( + "module '%s' not found", mn.Module.Name) + } + + return nil +} + // smcUserVariables does all the semantic checks to verify that the // variables given satisfy the configuration itself. func smcUserVariables(c *config.Config, vs map[string]string) []error {