From aef7718778ad79c6d9ececbccb5a6514f991a0a2 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Mon, 22 Sep 2014 14:25:54 -0700 Subject: [PATCH] terraform: support create-before-destroy --- terraform/graph.go | 47 ++++++-- terraform/graph_test.go | 101 +++++++++++++++++- .../graph-diff-create-before/main.tf | 6 ++ 3 files changed, 144 insertions(+), 10 deletions(-) create mode 100644 terraform/test-fixtures/graph-diff-create-before/main.tf diff --git a/terraform/graph.go b/terraform/graph.go index 279c08f26..afc439d25 100644 --- a/terraform/graph.go +++ b/terraform/graph.go @@ -482,6 +482,7 @@ func graphAddConfigResources( // these nodes for you. func graphAddDiff(g *depgraph.Graph, d *ModuleDiff) error { var nlist []*depgraph.Noun + injected := make(map[*depgraph.Dependency]struct{}) for _, n := range g.Nouns { rn, ok := n.Meta.(*GraphNodeResource) if !ok { @@ -530,13 +531,43 @@ func graphAddDiff(g *depgraph.Graph, d *ModuleDiff) error { newDiff.Destroy = false rd = newDiff - // 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, - }) + // The dependency ordering depends on if the CreateBeforeDestroy + // flag is enabled. If so, we must create the replacement first, + // and then destroy the old instance. + if rn.Config != nil && rn.Config.CreateBeforeDestroy && !rd.Empty() { + dep := &depgraph.Dependency{ + Name: n.Name, + Source: newN, + Target: n, + } + + // Add the old noun to the new noun dependencies so that + // the create happens before the destroy. + newN.Deps = append(newN.Deps, dep) + + // Mark that this dependency has been injected so that + // we do not invert the direction below. + injected[dep] = struct{}{} + + // Add a depedency from the root, since the create node + // does not depend on us + g.Root.Deps = append(g.Root.Deps, &depgraph.Dependency{ + Name: newN.Name, + Source: g.Root, + Target: newN, + }) + + } else { + dep := &depgraph.Dependency{ + Name: newN.Name, + Source: n, + Target: newN, + } + + // Add the new noun to our dependencies so that + // the destroy happens before the apply. + n.Deps = append(n.Deps, dep) + } } rn.Resource.Diff = rd @@ -544,7 +575,6 @@ func graphAddDiff(g *depgraph.Graph, d *ModuleDiff) error { // Go through each noun and make sure we calculate all the dependencies // properly. - injected := make(map[*depgraph.Dependency]struct{}) for _, n := range nlist { deps := n.Deps num := len(deps) @@ -948,6 +978,7 @@ func graphAddRoot(g *depgraph.Graph) { }) } g.Nouns = append(g.Nouns, root) + g.Root = root } // graphAddVariableDeps inspects all the nouns and adds any dependencies diff --git a/terraform/graph_test.go b/terraform/graph_test.go index 858ad1eda..710fb3417 100644 --- a/terraform/graph_test.go +++ b/terraform/graph_test.go @@ -652,6 +652,92 @@ func TestGraphAddDiff_module(t *testing.T) { } } +func TestGraphAddDiff_createBeforeDestroy(t *testing.T) { + config := testConfig(t, "graph-diff-create-before") + diff := &Diff{ + Resources: map[string]*InstanceDiff{ + "aws_instance.bar": &InstanceDiff{ + Destroy: true, + Attributes: map[string]*ResourceAttrDiff{ + "ami": &ResourceAttrDiff{ + Old: "abc", + New: "xyz", + RequiresNew: true, + }, + }, + }, + }, + } + state := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.bar": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + Attributes: map[string]string{ + "ami": "abc", + }, + }, + }, + }, + }, + }, + } + + diffHash := checksumStruct(t, diff) + + g, err := Graph(&GraphOpts{ + Config: config, + Diff: diff, + State: state, + }) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(g.String()) + expected := strings.TrimSpace(testTerraformGraphDiffCreateBeforeDestroyStr) + if actual != expected { + t.Fatalf("bad:\n\n%s\n\nexpected:\n\n%s", actual, expected) + } + + // Verify that our original structure has not been modified + diffHash2 := checksumStruct(t, diff) + if diffHash != diffHash2 { + t.Fatal("diff has been modified") + } +} + +func TestGraphInitState(t *testing.T) { + config := testConfig(t, "graph-basic") + state := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*InstanceDiff{ + "aws_instance.foo": &InstanceDiff{ + Destroy: true, + }, + }, + }, + }, + } + + g, err := Graph(&GraphOpts{Module: m, Diff: diff}) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(g.String()) + expected := strings.TrimSpace(testTerraformGraphDiffModuleStr) + if actual != expected { + t.Fatalf("bad:\n\n%s", actual) + } +} + func TestGraphAddDiff_moduleDestroy(t *testing.T) { m := testModule(t, "graph-diff-module") diff := &Diff{ @@ -1044,8 +1130,19 @@ aws_load_balancer.weblb aws_load_balancer.weblb -> provider.aws provider.aws root - root -> aws_load_balancer.weblb -` + root -> aws_load_balancer.weblb` + +const testTerraformGraphDiffCreateBeforeDestroyStr = ` +root: root +aws_instance.bar + aws_instance.bar -> provider.aws +aws_instance.bar (destroy) + aws_instance.bar (destroy) -> aws_instance.bar + aws_instance.bar (destroy) -> provider.aws +provider.aws +root + root -> aws_instance.bar + root -> aws_instance.bar (destroy)` const testTerraformGraphStateStr = ` root: root diff --git a/terraform/test-fixtures/graph-diff-create-before/main.tf b/terraform/test-fixtures/graph-diff-create-before/main.tf new file mode 100644 index 000000000..8d9dccd80 --- /dev/null +++ b/terraform/test-fixtures/graph-diff-create-before/main.tf @@ -0,0 +1,6 @@ +provider "aws" {} + +resource "aws_instance" "bar" { + ami = "abc" + create_before_destroy = true +}