terraform: start the transforms, adding orphans
This commit is contained in:
parent
21e4501edb
commit
6eb379fa75
|
@ -38,9 +38,10 @@ func (g *Graph) Edges() []Edge {
|
||||||
|
|
||||||
// Add adds a vertex to the graph. This is safe to call multiple time with
|
// Add adds a vertex to the graph. This is safe to call multiple time with
|
||||||
// the same Vertex.
|
// the same Vertex.
|
||||||
func (g *Graph) Add(v Vertex) {
|
func (g *Graph) Add(v Vertex) Vertex {
|
||||||
g.once.Do(g.init)
|
g.once.Do(g.init)
|
||||||
g.vertices = append(g.vertices, v)
|
g.vertices = append(g.vertices, v)
|
||||||
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect adds an edge with the given source and target. This is safe to
|
// Connect adds an edge with the given source and target. This is safe to
|
||||||
|
|
|
@ -79,6 +79,10 @@ type GraphNodeConfigResource struct {
|
||||||
Resource *config.Resource
|
Resource *config.Resource
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *GraphNodeConfigResource) DependableName() []string {
|
||||||
|
return []string{n.Resource.Id()}
|
||||||
|
}
|
||||||
|
|
||||||
func (n *GraphNodeConfigResource) Name() string {
|
func (n *GraphNodeConfigResource) Name() string {
|
||||||
return n.Resource.Id()
|
return n.Resource.Id()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
`
|
|
@ -0,0 +1 @@
|
||||||
|
resource "aws_instance" "web" {}
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
`
|
Loading…
Reference in New Issue