From d7dc0291f5e866f8ba5d0cd1fc9f5a9f29ff431d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 12 Feb 2015 10:54:28 -0800 Subject: [PATCH] terraform: put destroy nodes into the graph --- terraform/graph_builder.go | 3 ++ terraform/graph_builder_test.go | 8 +++ terraform/graph_config_node.go | 15 ++++++ .../transform-destroy-basic/main.tf | 5 ++ terraform/transform_destroy.go | 54 +++++++++++++++++++ terraform/transform_destroy_test.go | 41 ++++++++++++++ 6 files changed, 126 insertions(+) create mode 100644 terraform/test-fixtures/transform-destroy-basic/main.tf create mode 100644 terraform/transform_destroy.go create mode 100644 terraform/transform_destroy_test.go diff --git a/terraform/graph_builder.go b/terraform/graph_builder.go index 5ff371ec6..53dfaeeb2 100644 --- a/terraform/graph_builder.go +++ b/terraform/graph_builder.go @@ -95,6 +95,9 @@ func (b *BuiltinGraphBuilder) Steps() []GraphTransformer { }, }, + // Create the destruction nodes + &DestroyTransformer{}, + // Make sure we create one root &RootTransformer{}, } diff --git a/terraform/graph_builder_test.go b/terraform/graph_builder_test.go index 56bc62622..166f5902b 100644 --- a/terraform/graph_builder_test.go +++ b/terraform/graph_builder_test.go @@ -106,8 +106,12 @@ const testBasicGraphBuilderStr = ` const testBuiltinGraphBuilderBasicStr = ` aws_instance.db + aws_instance.db (destroy) +aws_instance.db (destroy) provider.aws aws_instance.web + aws_instance.web (destroy) +aws_instance.web (destroy) aws_instance.db provider.aws provider.aws @@ -115,10 +119,14 @@ provider.aws const testBuiltinGraphBuilderModuleStr = ` aws_instance.web + aws_instance.web (destroy) +aws_instance.web (destroy) aws_security_group.firewall module.consul (expanded) provider.aws aws_security_group.firewall + aws_security_group.firewall (destroy) +aws_security_group.firewall (destroy) provider.aws module.consul (expanded) aws_security_group.firewall diff --git a/terraform/graph_config_node.go b/terraform/graph_config_node.go index 74756ff4d..0947e6c09 100644 --- a/terraform/graph_config_node.go +++ b/terraform/graph_config_node.go @@ -243,6 +243,21 @@ func (n *GraphNodeConfigResource) ProvisionedBy() []string { return result } +// GraphNodeDestroyable +func (n *GraphNodeConfigResource) DestroyNode() dag.Vertex { + return &GraphNodeConfigResourceDestroy{Resource: n.Resource} +} + +// GraphNodeConfigResourceDestroy represents the logical destroy step for +// a resource. +type GraphNodeConfigResourceDestroy struct { + Resource *config.Resource +} + +func (n *GraphNodeConfigResourceDestroy) Name() string { + return fmt.Sprintf("%s (destroy)", n.Resource.Id()) +} + // graphNodeModuleExpanded represents a module where the graph has // been expanded. It stores the graph of the module as well as a reference // to the map of variables. diff --git a/terraform/test-fixtures/transform-destroy-basic/main.tf b/terraform/test-fixtures/transform-destroy-basic/main.tf new file mode 100644 index 000000000..14bca3e82 --- /dev/null +++ b/terraform/test-fixtures/transform-destroy-basic/main.tf @@ -0,0 +1,5 @@ +resource "aws_instance" "foo" {} + +resource "aws_instance" "bar" { + value = "${aws_instance.foo.value}" +} diff --git a/terraform/transform_destroy.go b/terraform/transform_destroy.go new file mode 100644 index 000000000..86d7419e2 --- /dev/null +++ b/terraform/transform_destroy.go @@ -0,0 +1,54 @@ +package terraform + +import ( + "github.com/hashicorp/terraform/dag" +) + +// GraphNodeDestroyable is the interface that nodes that can be destroyed +// must implement. This is used to automatically handle the creation of +// destroy nodes in the graph and the dependency ordering of those destroys. +type GraphNodeDestroyable interface { + // DestroyNode returns the node used for the destroy. This vertex + // should not be in the graph yet. + DestroyNode() dag.Vertex +} + +// DestroyTransformer is a GraphTransformer that creates the destruction +// nodes for things that _might_ be destroyed. +type DestroyTransformer struct{} + +func (t *DestroyTransformer) Transform(g *Graph) error { + for _, v := range g.Vertices() { + // If it is not a destroyable, we don't care + dn, ok := v.(GraphNodeDestroyable) + if !ok { + continue + } + + // Grab the destroy side of the node and connect it through + n := dn.DestroyNode() + if n == nil { + continue + } + + // Add it to the graph + g.Add(n) + + // Inherit all the edges from the old node + downEdges := g.DownEdges(v).List() + for _, edgeRaw := range downEdges { + g.Connect(dag.BasicEdge(n, edgeRaw.(dag.Vertex))) + } + + // Remove all the edges from the old now + for _, edgeRaw := range downEdges { + g.RemoveEdge(dag.BasicEdge(v, edgeRaw.(dag.Vertex))) + } + + // Add a new edge to connect the node to be created to + // the destroy node. + g.Connect(dag.BasicEdge(v, n)) + } + + return nil +} diff --git a/terraform/transform_destroy_test.go b/terraform/transform_destroy_test.go new file mode 100644 index 000000000..71394490d --- /dev/null +++ b/terraform/transform_destroy_test.go @@ -0,0 +1,41 @@ +package terraform + +import ( + "strings" + "testing" +) + +func TestDestroyTransformer(t *testing.T) { + mod := testModule(t, "transform-destroy-basic") + + g := Graph{Path: RootModulePath} + { + tf := &ConfigTransformer{Module: mod} + if err := tf.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + } + + { + tf := &DestroyTransformer{} + if err := tf.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + } + + actual := strings.TrimSpace(g.String()) + expected := strings.TrimSpace(testTransformDestroyBasicStr) + if actual != expected { + t.Fatalf("bad:\n\n%s", actual) + } +} + +const testTransformDestroyBasicStr = ` +aws_instance.bar + aws_instance.bar (destroy) +aws_instance.bar (destroy) + aws_instance.foo +aws_instance.foo + aws_instance.foo (destroy) +aws_instance.foo (destroy) +`