terraform: start Graph2 to make logical config graph
This commit is contained in:
parent
8463c92102
commit
6b00633ed1
|
@ -0,0 +1,70 @@
|
||||||
|
package depgraph
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Graph is used to represent a dependency graph.
|
||||||
|
type Graph struct {
|
||||||
|
Nodes []Node
|
||||||
|
}
|
||||||
|
|
||||||
|
// Node is an element of the graph that has other dependencies.
|
||||||
|
type Node interface {
|
||||||
|
Deps() []Node
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamedNode is an optional interface implementation of a Node that
|
||||||
|
// can have a name. If this is implemented, this will be used for various
|
||||||
|
// output.
|
||||||
|
type NamedNode interface {
|
||||||
|
Node
|
||||||
|
Name() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Graph) String() string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Build the list of node names and a mapping so that we can more
|
||||||
|
// easily alphabetize the output to remain deterministic.
|
||||||
|
names := make([]string, 0, len(g.Nodes))
|
||||||
|
mapping := make(map[string]Node, len(g.Nodes))
|
||||||
|
for _, n := range g.Nodes {
|
||||||
|
name := nodeName(n)
|
||||||
|
names = append(names, name)
|
||||||
|
mapping[name] = n
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
|
||||||
|
// Write each node in order...
|
||||||
|
for _, name := range names {
|
||||||
|
n := mapping[name]
|
||||||
|
buf.WriteString(fmt.Sprintf("%s\n", name))
|
||||||
|
|
||||||
|
// Alphabetize dependencies
|
||||||
|
depsRaw := n.Deps()
|
||||||
|
deps := make([]string, 0, len(depsRaw))
|
||||||
|
for _, d := range depsRaw {
|
||||||
|
deps = append(deps, nodeName(d))
|
||||||
|
}
|
||||||
|
sort.Strings(deps)
|
||||||
|
|
||||||
|
// Write dependencies
|
||||||
|
for _, d := range deps {
|
||||||
|
buf.WriteString(fmt.Sprintf(" %s\n", d))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func nodeName(n Node) string {
|
||||||
|
switch v := n.(type) {
|
||||||
|
case NamedNode:
|
||||||
|
return v.Name()
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%s", v)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
"github.com/hashicorp/terraform/config/module"
|
||||||
|
"github.com/hashicorp/terraform/depgraph2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Graph takes a module tree and builds a logical graph of all the nodes
|
||||||
|
// in that module.
|
||||||
|
func Graph2(mod *module.Tree) (*depgraph.Graph, error) {
|
||||||
|
// A module is required and also must be completely loaded.
|
||||||
|
if mod == nil {
|
||||||
|
return nil, errors.New("module must not be nil")
|
||||||
|
}
|
||||||
|
if !mod.Loaded() {
|
||||||
|
return nil, errors.New("module must be loaded")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the configuration for this module
|
||||||
|
config := mod.Config()
|
||||||
|
|
||||||
|
// Create the node list we'll use for the graph
|
||||||
|
nodes := make([]graphNodeConfig, 0,
|
||||||
|
(len(config.Modules)+len(config.Resources))*2)
|
||||||
|
|
||||||
|
// Write all the resources out
|
||||||
|
for _, r := range config.Resources {
|
||||||
|
nodes = append(nodes, &GraphNodeConfigResource{
|
||||||
|
Resource: r,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write all the modules out
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
// Build the full map of the var names to the nodes.
|
||||||
|
fullMap := make(map[string]depgraph.Node)
|
||||||
|
for _, n := range nodes {
|
||||||
|
fullMap[n.VarName()] = n
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through all the nodes and build up the actual dependency map. We
|
||||||
|
// do this by getting the variables that each node depends on and then
|
||||||
|
// building the dep map based on the fullMap which contains the mapping
|
||||||
|
// of var names to the actual node with that name.
|
||||||
|
for _, n := range nodes {
|
||||||
|
m := make(map[string]depgraph.Node)
|
||||||
|
for _, v := range n.Variables() {
|
||||||
|
id := varNameForVar(v)
|
||||||
|
m[id] = fullMap[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
n.setDepMap(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the graph and return it
|
||||||
|
g := &depgraph.Graph{Nodes: make([]depgraph.Node, 0, len(nodes))}
|
||||||
|
for _, n := range nodes {
|
||||||
|
g.Nodes = append(g.Nodes, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
return g, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// graphNodeConfig is an interface that all graph nodes for the
|
||||||
|
// configuration graph need to implement in order to build the variable
|
||||||
|
// dependencies properly.
|
||||||
|
type graphNodeConfig interface {
|
||||||
|
depgraph.Node
|
||||||
|
|
||||||
|
// Variables returns the full list of variables that this node
|
||||||
|
// depends on.
|
||||||
|
Variables() map[string]config.InterpolatedVariable
|
||||||
|
|
||||||
|
// VarName returns the name that is used to identify a variable
|
||||||
|
// maps to this node. It should match the result of the
|
||||||
|
// `VarName` function.
|
||||||
|
VarName() string
|
||||||
|
|
||||||
|
// setDepMap sets the dependency map for this node. If the node is
|
||||||
|
// nil, then it wasn't found.
|
||||||
|
setDepMap(map[string]depgraph.Node)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphNodeConfigResource represents a resource within the configuration
|
||||||
|
// graph.
|
||||||
|
type GraphNodeConfigResource struct {
|
||||||
|
Resource *config.Resource
|
||||||
|
DepMap map[string]depgraph.Node
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *GraphNodeConfigResource) Deps() []depgraph.Node {
|
||||||
|
r := make([]depgraph.Node, 0, len(n.DepMap))
|
||||||
|
for _, v := range n.DepMap {
|
||||||
|
if v != nil {
|
||||||
|
r = append(r, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *GraphNodeConfigResource) Name() string {
|
||||||
|
return n.Resource.Id()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *GraphNodeConfigResource) Variables() map[string]config.InterpolatedVariable {
|
||||||
|
var m map[string]config.InterpolatedVariable
|
||||||
|
if n.Resource != nil {
|
||||||
|
m = make(map[string]config.InterpolatedVariable)
|
||||||
|
for k, v := range n.Resource.RawCount.Variables {
|
||||||
|
m[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range n.Resource.RawConfig.Variables {
|
||||||
|
m[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *GraphNodeConfigResource) VarName() string {
|
||||||
|
return n.Resource.Id()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *GraphNodeConfigResource) setDepMap(m map[string]depgraph.Node) {
|
||||||
|
n.DepMap = m
|
||||||
|
}
|
||||||
|
|
||||||
|
// varNameForVar returns the VarName value for an interpolated variable.
|
||||||
|
// This value is compared to the VarName() value for the nodes within the
|
||||||
|
// graph to build the graph edges.
|
||||||
|
func varNameForVar(raw config.InterpolatedVariable) string {
|
||||||
|
switch v := raw.(type) {
|
||||||
|
case *config.ModuleVariable:
|
||||||
|
return fmt.Sprintf("module.%s", v.Name)
|
||||||
|
case *config.ResourceVariable:
|
||||||
|
return v.ResourceId()
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config/module"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGraph_nilModule(t *testing.T) {
|
||||||
|
_, err := Graph2(nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGraph_unloadedModule(t *testing.T) {
|
||||||
|
mod, err := module.NewTreeModule(
|
||||||
|
"", filepath.Join(fixtureDir, "graph-basic"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := Graph2(mod); err == nil {
|
||||||
|
t.Fatal("should error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGraph(t *testing.T) {
|
||||||
|
g, err := Graph2(testModule(t, "graph-basic"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(g.String())
|
||||||
|
expected := strings.TrimSpace(testGraphBasicStr)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad:\n\n%s", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testGraphBasicStr = `
|
||||||
|
aws_instance.web
|
||||||
|
aws_security_group.firewall
|
||||||
|
aws_load_balancer.weblb
|
||||||
|
aws_instance.web
|
||||||
|
aws_security_group.firewall
|
||||||
|
openstack_floating_ip.random
|
||||||
|
`
|
Loading…
Reference in New Issue