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
|
||||
// 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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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