terraform: start the transforms, adding orphans

This commit is contained in:
Mitchell Hashimoto 2015-01-26 20:17:52 -08:00
parent 21e4501edb
commit 6eb379fa75
8 changed files with 244 additions and 1 deletions

View File

@ -38,9 +38,10 @@ func (g *Graph) Edges() []Edge {
// Add adds a vertex to the graph. This is safe to call multiple time with
// the same Vertex.
func (g *Graph) Add(v Vertex) {
func (g *Graph) Add(v Vertex) Vertex {
g.once.Do(g.init)
g.vertices = append(g.vertices, v)
return v
}
// Connect adds an edge with the given source and target. This is safe to

View File

@ -79,6 +79,10 @@ type GraphNodeConfigResource struct {
Resource *config.Resource
}
func (n *GraphNodeConfigResource) DependableName() []string {
return []string{n.Resource.Id()}
}
func (n *GraphNodeConfigResource) Name() string {
return n.Resource.Id()
}

43
terraform/graph_deps.go Normal file
View File

@ -0,0 +1,43 @@
package terraform
import (
"github.com/hashicorp/terraform/dag"
)
// GraphNodeDependable is an interface which says that a node can be
// depended on (an edge can be placed between this node and another) according
// to the well-known name returned by DependableName.
//
// DependableName can return multiple names it is known by.
type GraphNodeDependable interface {
DependableName() []string
}
// GraphConnectDeps is a helper to connect a Vertex to the proper dependencies
// in the graph based only on the names expected by DependableName.
//
// This function will return the number of dependencies found and connected.
func GraphConnectDeps(g *dag.Graph, source dag.Vertex, targets []string) int {
count := 0
// This is reasonably horrible. In the future, we should optimize this
// through some kind of metadata on the graph that can store all of
// this information in a look-aside table.
for _, v := range g.Vertices() {
if dv, ok := v.(GraphNodeDependable); ok {
for _, n := range dv.DependableName() {
for _, n2 := range targets {
if n == n2 {
count++
g.Connect(dag.BasicEdge(source, v))
goto NEXT
}
}
}
}
NEXT:
}
return count
}

View File

@ -0,0 +1,43 @@
package terraform
import (
"strings"
"testing"
"github.com/hashicorp/terraform/dag"
)
func TestGraphConnectDeps(t *testing.T) {
var g dag.Graph
g.Add(&testGraphDependable{VertexName: "a", Mock: []string{"a"}})
b := g.Add(&testGraphDependable{VertexName: "b"})
if n := GraphConnectDeps(&g, b, []string{"a"}); n != 1 {
t.Fatalf("bad: %d", n)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testGraphConnectDepsStr)
if actual != expected {
t.Fatalf("bad: %s", actual)
}
}
type testGraphDependable struct {
VertexName string
Mock []string
}
func (v *testGraphDependable) Name() string {
return v.VertexName
}
func (v *testGraphDependable) DependableName() []string {
return v.Mock
}
const testGraphConnectDepsStr = `
a
b
a
`

View File

@ -0,0 +1 @@
resource "aws_instance" "web" {}

11
terraform/transform.go Normal file
View File

@ -0,0 +1,11 @@
package terraform
import (
"github.com/hashicorp/terraform/dag"
)
// GraphTransformer is the interface that transformers implement. This
// interface is only for transforms that need entire graph visibility.
type GraphTransformer interface {
Transform(*dag.Graph) error
}

View File

@ -0,0 +1,42 @@
package terraform
import (
"fmt"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/dag"
)
// OrphanTransformer is a GraphTransformer that adds orphans to the
// graph. This transformer adds both resource and module orphans.
type OrphanTransformer struct {
State *ModuleState
Config *config.Config
}
func (t *OrphanTransformer) Transform(g *dag.Graph) error {
// Get the orphans from our configuration. This will only get resources.
orphans := t.State.Orphans(t.Config)
if len(orphans) == 0 {
return nil
}
// Go over each orphan and add it to the graph.
for _, k := range orphans {
v := g.Add(&graphNodeOrphanResource{ResourceName: k})
GraphConnectDeps(g, v, t.State.Resources[k].Dependencies)
}
// TODO: modules
return nil
}
// graphNodeOrphan is the graph vertex representing an orphan resource..
type graphNodeOrphanResource struct {
ResourceName string
}
func (n *graphNodeOrphanResource) Name() string {
return fmt.Sprintf("%s (orphan)", n.ResourceName)
}

View File

@ -0,0 +1,98 @@
package terraform
import (
"strings"
"testing"
)
func TestOrphanTransformer(t *testing.T) {
mod := testModule(t, "transform-orphan-basic")
state := &ModuleState{
Path: rootModulePath,
Resources: map[string]*ResourceState{
"aws_instance.web": &ResourceState{
Type: "aws_instance",
Primary: &InstanceState{
ID: "foo",
},
},
// The orphan
"aws_instance.db": &ResourceState{
Type: "aws_instance",
Primary: &InstanceState{
ID: "foo",
},
},
},
}
g, err := Graph2(mod)
if err != nil {
t.Fatalf("err: %s", err)
}
transform := &OrphanTransformer{State: state, Config: mod.Config()}
if err := transform.Transform(g); err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformOrphanBasicStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestOrphanTransformer_resourceDepends(t *testing.T) {
mod := testModule(t, "transform-orphan-basic")
state := &ModuleState{
Path: rootModulePath,
Resources: map[string]*ResourceState{
"aws_instance.web": &ResourceState{
Type: "aws_instance",
Primary: &InstanceState{
ID: "foo",
},
},
// The orphan
"aws_instance.db": &ResourceState{
Type: "aws_instance",
Primary: &InstanceState{
ID: "foo",
},
Dependencies: []string{
"aws_instance.web",
},
},
},
}
g, err := Graph2(mod)
if err != nil {
t.Fatalf("err: %s", err)
}
transform := &OrphanTransformer{State: state, Config: mod.Config()}
if err := transform.Transform(g); err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformOrphanResourceDependsStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
const testTransformOrphanBasicStr = `
aws_instance.db (orphan)
aws_instance.web
`
const testTransformOrphanResourceDependsStr = `
aws_instance.db (orphan)
aws_instance.web
aws_instance.web
`