From 2d72164c6a6718ab33245b9bbdf4b9e1fc04ed5c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 30 Jun 2014 19:10:44 -0700 Subject: [PATCH] terraform: graph can add "destroy" nodes --- terraform/graph.go | 72 +++++++++++++++++- terraform/graph_test.go | 75 +++++++++++++++++++ .../test-fixtures/graph-diff-destroy/main.tf | 2 + 3 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 terraform/test-fixtures/graph-diff-destroy/main.tf diff --git a/terraform/graph.go b/terraform/graph.go index 50988ff330..97eed7f0fa 100644 --- a/terraform/graph.go +++ b/terraform/graph.go @@ -111,9 +111,8 @@ func GraphFull(g *depgraph.Graph, ps map[string]ResourceProviderFactory) error { // destroying the VPC's subnets first, whereas creating a VPC requires // doing it before the subnets are created. This function handles inserting // these nodes for you. -// -// Note that all nodes modifying the same resource will have the same name. func GraphAddDiff(g *depgraph.Graph, d *Diff) error { + var nlist []*depgraph.Noun for _, n := range g.Nouns { rn, ok := n.Meta.(*GraphNodeResource) if !ok { @@ -124,10 +123,79 @@ func GraphAddDiff(g *depgraph.Graph, d *Diff) error { if !ok { continue } + if rd.Empty() { + continue + } + + if rd.Destroy || rd.RequiresNew() { + // If we're destroying, we create a new destroy node with + // the proper dependencies. Perform a dirty copy operation. + newNode := new(GraphNodeResource) + *newNode = *rn + newNode.Resource = new(Resource) + *newNode.Resource = *rn.Resource + + // Make the diff _just_ the destroy. + newNode.Resource.Diff = &ResourceDiff{Destroy: true} + + // Append it to the list so we handle it later + deps := make([]*depgraph.Dependency, len(n.Deps)) + copy(deps, n.Deps) + newN := &depgraph.Noun{ + Name: fmt.Sprintf("%s (destroy)", newNode.Resource.Id), + Meta: newNode, + Deps: deps, + } + nlist = append(nlist, newN) + + // Mark the old diff to not destroy since we handle that in + // the dedicated node. + rd.Destroy = false + + // Add to the new noun to our dependencies so that the destroy + // happens before the apply. + n.Deps = append(n.Deps, &depgraph.Dependency{ + Name: newN.Name, + Source: n, + Target: newN, + }) + } rn.Resource.Diff = rd } + // Go through each noun and make sure we calculate all the dependencies + // properly. + for _, n := range nlist { + rn := n.Meta.(*GraphNodeResource) + + // If we have no dependencies, then just continue + deps := rn.Resource.State.Dependencies + if len(deps) == 0 { + continue + } + + // We have dependencies. We must be destroyed BEFORE those + // dependencies. Look to see if they're managed. + for _, dep := range deps { + for _, n2 := range nlist { + rn2 := n2.Meta.(*GraphNodeResource) + if rn2.Resource.State.ID == dep.ID { + n2.Deps = append(n2.Deps, &depgraph.Dependency{ + Name: n.Name, + Source: n2, + Target: n, + }) + + break + } + } + } + } + + // Add the nouns to the graph + g.Nouns = append(g.Nouns, nlist...) + return nil } diff --git a/terraform/graph_test.go b/terraform/graph_test.go index 7b2634754d..ed379a1c6e 100644 --- a/terraform/graph_test.go +++ b/terraform/graph_test.go @@ -154,6 +154,67 @@ func TestGraphAddDiff(t *testing.T) { } } +func TestGraphAddDiff_destroy(t *testing.T) { + config := testConfig(t, "graph-diff-destroy") + state := &State{ + Resources: map[string]*ResourceState{ + "aws_instance.foo": &ResourceState{ + ID: "foo", + Type: "aws_instance", + }, + + "aws_instance.bar": &ResourceState{ + ID: "bar", + Type: "aws_instance", + Dependencies: []ResourceDependency{ + ResourceDependency{ + ID: "foo", + }, + }, + }, + }, + } + + g := Graph(config, state) + if err := g.Validate(); err != nil { + t.Fatalf("err: %s", err) + } + + diff := &Diff{ + Resources: map[string]*ResourceDiff{ + "aws_instance.foo": &ResourceDiff{ + Destroy: true, + }, + "aws_instance.bar": &ResourceDiff{ + Destroy: true, + }, + }, + } + + if err := GraphAddDiff(g, diff); err != nil { + t.Fatalf("err: %s", err) + } + if err := g.Validate(); err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(g.String()) + expected := strings.TrimSpace(testTerraformGraphDiffDestroyStr) + if actual != expected { + t.Fatalf("bad:\n\n%s", actual) + } + + // Verify that the state has been added + n := g.Noun("aws_instance.foo") + rn := n.Meta.(*GraphNodeResource) + + expected2 := diff.Resources["aws_instance.foo"] + actual2 := rn.Resource.Diff + if !reflect.DeepEqual(actual2, expected2) { + t.Fatalf("bad: %#v", actual2) + } +} + const testTerraformGraphStr = ` root: root aws_instance.web @@ -182,6 +243,20 @@ root root -> aws_instance.foo ` +const testTerraformGraphDiffDestroyStr = ` +root: root +aws_instance.bar + 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) + aws_instance.foo (destroy) -> aws_instance.bar (destroy) +root + root -> aws_instance.bar + root -> aws_instance.foo +` + const testTerraformGraphStateStr = ` root: root aws_instance.old diff --git a/terraform/test-fixtures/graph-diff-destroy/main.tf b/terraform/test-fixtures/graph-diff-destroy/main.tf new file mode 100644 index 0000000000..ca956330c7 --- /dev/null +++ b/terraform/test-fixtures/graph-diff-destroy/main.tf @@ -0,0 +1,2 @@ +resource "aws_instance" "foo" { +}