terraform: transform for adding orphan resources + tests
This commit is contained in:
parent
6337829786
commit
2608c5f282
|
@ -0,0 +1 @@
|
|||
# Purposefully empty
|
|
@ -0,0 +1 @@
|
|||
resource "aws_instance" "foo" { count = 3 }
|
|
@ -0,0 +1,74 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/config/module"
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
)
|
||||
|
||||
// OrphanResourceTransformer is a GraphTransformer that adds resource
|
||||
// orphans to the graph. A resource orphan is a resource that is
|
||||
// represented in the state but not in the configuration.
|
||||
//
|
||||
// This only adds orphans that have no representation at all in the
|
||||
// configuration.
|
||||
type OrphanResourceTransformer struct {
|
||||
Concrete ConcreteResourceNodeFunc
|
||||
|
||||
// State is the global state. We require the global state to
|
||||
// properly find module orphans at our path.
|
||||
State *State
|
||||
|
||||
// Module is the root module. We'll look up the proper configuration
|
||||
// using the graph path.
|
||||
Module *module.Tree
|
||||
}
|
||||
|
||||
func (t *OrphanResourceTransformer) Transform(g *Graph) error {
|
||||
if t.State == nil {
|
||||
// If the entire state is nil, there can't be any orphans
|
||||
return nil
|
||||
}
|
||||
|
||||
// Go through the modules and for each module transform in order
|
||||
// to add the orphan.
|
||||
for _, ms := range t.State.Modules {
|
||||
if err := t.transform(g, ms); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *OrphanResourceTransformer) transform(g *Graph, ms *ModuleState) error {
|
||||
// Get the configuration for this path. The configuration might be
|
||||
// nil if the module was removed from the configuration. This is okay,
|
||||
// this just means that every resource is an orphan.
|
||||
var c *config.Config
|
||||
if m := t.Module.Child(ms.Path[1:]); m != nil {
|
||||
c = m.Config()
|
||||
}
|
||||
|
||||
// Go through the orphans and add them all to the state
|
||||
for _, key := range ms.Orphans(c) {
|
||||
// Build the abstract resource
|
||||
addr, err := parseResourceAddressInternal(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
addr.Path = ms.Path[1:]
|
||||
|
||||
// Build the abstract node and the concrete one
|
||||
abstract := &NodeAbstractResource{Addr: addr}
|
||||
var node dag.Vertex = abstract
|
||||
if f := t.Concrete; f != nil {
|
||||
node = f(abstract)
|
||||
}
|
||||
|
||||
// Add it to the graph
|
||||
g.Add(node)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,246 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
)
|
||||
|
||||
func TestOrphanResourceTransformer(t *testing.T) {
|
||||
mod := testModule(t, "transform-orphan-basic")
|
||||
state := &State{
|
||||
Modules: []*ModuleState{
|
||||
&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 := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
tf := &OrphanResourceTransformer{
|
||||
Concrete: testOrphanResourceConcreteFunc,
|
||||
State: state, Module: mod,
|
||||
}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testTransformOrphanResourceBasicStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrphanResourceTransformer_countGood(t *testing.T) {
|
||||
mod := testModule(t, "transform-orphan-count")
|
||||
state := &State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: RootModulePath,
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.foo.0": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"aws_instance.foo.1": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
tf := &OrphanResourceTransformer{
|
||||
Concrete: testOrphanResourceConcreteFunc,
|
||||
State: state, Module: mod,
|
||||
}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testTransformOrphanResourceCountStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrphanResourceTransformer_countBad(t *testing.T) {
|
||||
mod := testModule(t, "transform-orphan-count-empty")
|
||||
state := &State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: RootModulePath,
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.foo.0": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"aws_instance.foo.1": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
tf := &OrphanResourceTransformer{
|
||||
Concrete: testOrphanResourceConcreteFunc,
|
||||
State: state, Module: mod,
|
||||
}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testTransformOrphanResourceCountBadStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrphanResourceTransformer_modules(t *testing.T) {
|
||||
mod := testModule(t, "transform-orphan-modules")
|
||||
state := &State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: RootModulePath,
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.foo": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
&ModuleState{
|
||||
Path: []string{"root", "child"},
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.web": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
tf := &OrphanResourceTransformer{
|
||||
Concrete: testOrphanResourceConcreteFunc,
|
||||
State: state, Module: mod,
|
||||
}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testTransformOrphanResourceModulesStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
const testTransformOrphanResourceBasicStr = `
|
||||
aws_instance.db (orphan)
|
||||
aws_instance.web
|
||||
`
|
||||
|
||||
const testTransformOrphanResourceCountStr = `
|
||||
aws_instance.foo
|
||||
`
|
||||
|
||||
const testTransformOrphanResourceCountBadStr = `
|
||||
aws_instance.foo[0] (orphan)
|
||||
aws_instance.foo[1] (orphan)
|
||||
`
|
||||
|
||||
const testTransformOrphanResourceModulesStr = `
|
||||
aws_instance.foo
|
||||
module.child.aws_instance.web (orphan)
|
||||
`
|
||||
|
||||
func testOrphanResourceConcreteFunc(a *NodeAbstractResource) dag.Vertex {
|
||||
return &testOrphanResourceConcrete{a}
|
||||
}
|
||||
|
||||
type testOrphanResourceConcrete struct {
|
||||
*NodeAbstractResource
|
||||
}
|
||||
|
||||
func (n *testOrphanResourceConcrete) Name() string {
|
||||
return fmt.Sprintf("%s (orphan)", n.NodeAbstractResource.Name())
|
||||
}
|
Loading…
Reference in New Issue