Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

terraform: when promoting non-CBD to CBD, mark the config as such #10455

Merged
merged 2 commits into from
Dec 2, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions terraform/context_apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,73 @@ func TestContext2Apply_createBeforeDestroyUpdate(t *testing.T) {
}
}

// This tests that when a CBD resource depends on a non-CBD resource,
// we can still properly apply changes that require new for both.
func TestContext2Apply_createBeforeDestroy_dependsNonCBD(t *testing.T) {
m := testModule(t, "apply-cbd-depends-non-cbd")
p := testProvider("aws")
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn
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{
"require_new": "abc",
},
},
},

"aws_instance.foo": &ResourceState{
Type: "aws_instance",
Primary: &InstanceState{
ID: "foo",
Attributes: map[string]string{
"require_new": "abc",
},
},
},
},
},
},
}
ctx := testContext2(t, &ContextOpts{
Module: m,
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
State: state,
})

if p, err := ctx.Plan(); err != nil {
t.Fatalf("err: %s", err)
} else {
t.Logf(p.String())
}

state, err := ctx.Apply()
if err != nil {
t.Fatalf("err: %s", err)
}

checkStateString(t, state, `
aws_instance.bar:
ID = foo
require_new = yes
type = aws_instance
value = foo
aws_instance.foo:
ID = foo
require_new = yes
type = aws_instance
`)
}

func TestContext2Apply_createBeforeDestroy_hook(t *testing.T) {
h := new(MockHook)
m := testModule(t, "apply-good-create-before")
Expand Down
14 changes: 14 additions & 0 deletions terraform/node_resource_destroy.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,20 @@ func (n *NodeDestroyResource) CreateBeforeDestroy() bool {
return n.Config.Lifecycle.CreateBeforeDestroy
}

// GraphNodeDestroyerCBD
func (n *NodeDestroyResource) ModifyCreateBeforeDestroy(v bool) error {
// If we have no config, do nothing since it won't affect the
// create step anyways.
if n.Config == nil {
return nil
}

// Set CBD to true
n.Config.Lifecycle.CreateBeforeDestroy = true

return nil
}

// GraphNodeReferenceable, overriding NodeAbstractResource
func (n *NodeDestroyResource) ReferenceableName() []string {
result := n.NodeAbstractResource.ReferenceableName()
Expand Down
12 changes: 12 additions & 0 deletions terraform/test-fixtures/apply-cbd-depends-non-cbd/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
resource "aws_instance" "foo" {
require_new = "yes"
}

resource "aws_instance" "bar" {
require_new = "yes"
value = "${aws_instance.foo.id}"

lifecycle {
create_before_destroy = true
}
}
29 changes: 26 additions & 3 deletions terraform/transform_destroy.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package terraform

import "github.com/hashicorp/terraform/dag"
import (
"github.com/hashicorp/terraform/config"
"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
Expand Down Expand Up @@ -151,8 +154,28 @@ func (t *CreateBeforeDestroyTransformer) Transform(g *Graph) error {
}

// If the node doesn't need to create before destroy, then continue
if !dn.CreateBeforeDestroy() && noCreateBeforeDestroyAncestors(g, dn) {
continue
if !dn.CreateBeforeDestroy() {
if noCreateBeforeDestroyAncestors(g, dn) {
continue
}

// PURPOSELY HACKY FIX SINCE THIS TRANSFORM IS DEPRECATED.
// This is a hacky way to fix GH-10439. For a detailed description
// of the fix, see CBDEdgeTransformer, which is the equivalent
// transform used by the new graphs.
//
// This transform is deprecated because it is only used by the
// old graphs which are going to be removed.
var update *config.Resource
if dn, ok := v.(*graphNodeResourceDestroy); ok {
update = dn.Original.Resource
}
if dn, ok := v.(*graphNodeResourceDestroyFlat); ok {
update = dn.Original.Resource
}
if update != nil {
update.Lifecycle.CreateBeforeDestroy = true
}
}

// Get the creation side of this node
Expand Down
47 changes: 46 additions & 1 deletion terraform/transform_destroy_cbd.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package terraform

import (
"fmt"
"log"

"github.com/hashicorp/terraform/config/module"
Expand All @@ -15,6 +16,11 @@ type GraphNodeDestroyerCBD interface {
// CreateBeforeDestroy returns true if this node represents a node
// that is doing a CBD.
CreateBeforeDestroy() bool

// ModifyCreateBeforeDestroy is called when the CBD state of a node
// is changed dynamically. This can return an error if this isn't
// allowed.
ModifyCreateBeforeDestroy(bool) error
}

// CBDEdgeTransformer modifies the edges of CBD nodes that went through
Expand Down Expand Up @@ -48,7 +54,23 @@ func (t *CBDEdgeTransformer) Transform(g *Graph) error {
}

if !dn.CreateBeforeDestroy() {
continue
// If there are no CBD ancestors (dependent nodes), then we
// do nothing here.
if !t.hasCBDAncestor(g, v) {
continue
}

// If this isn't naturally a CBD node, this means that an ancestor is
// and we need to auto-upgrade this node to CBD. We do this because
// a CBD node depending on non-CBD will result in cycles. To avoid this,
// we always attempt to upgrade it.
if err := dn.ModifyCreateBeforeDestroy(true); err != nil {
return fmt.Errorf(
"%s: must have create before destroy enabled because "+
"a dependent resource has CBD enabled. However, when "+
"attempting to automatically do this, an error occurred: %s",
dag.VertexName(v), err)
}
}

// Find the destroy edge. There should only be one.
Expand Down Expand Up @@ -189,3 +211,26 @@ func (t *CBDEdgeTransformer) depMap(

return depMap, nil
}

// hasCBDAncestor returns true if any ancestor (node that depends on this)
// has CBD set.
func (t *CBDEdgeTransformer) hasCBDAncestor(g *Graph, v dag.Vertex) bool {
s, _ := g.Ancestors(v)
if s == nil {
return true
}

for _, v := range s.List() {
dn, ok := v.(GraphNodeDestroyerCBD)
if !ok {
continue
}

if dn.CreateBeforeDestroy() {
// some ancestor is CreateBeforeDestroy, so we need to follow suit
return true
}
}

return false
}
43 changes: 43 additions & 0 deletions terraform/transform_destroy_cbd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,53 @@ func TestCBDEdgeTransformer(t *testing.T) {
}
}

func TestCBDEdgeTransformer_depNonCBD(t *testing.T) {
g := Graph{Path: RootModulePath}
g.Add(&graphNodeCreatorTest{AddrString: "test.A"})
g.Add(&graphNodeCreatorTest{AddrString: "test.B"})
g.Add(&graphNodeDestroyerTest{AddrString: "test.A"})
g.Add(&graphNodeDestroyerTest{AddrString: "test.B", CBD: true})

module := testModule(t, "transform-destroy-edge-basic")

{
tf := &DestroyEdgeTransformer{
Module: module,
}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}

{
tf := &CBDEdgeTransformer{Module: module}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}

actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformCBDEdgeDepNonCBDStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}

const testTransformCBDEdgeBasicStr = `
test.A
test.A (destroy)
test.A
test.B
test.B
`

const testTransformCBDEdgeDepNonCBDStr = `
test.A
test.A (destroy) (modified)
test.A
test.B
test.B (destroy)
test.B
test.B (destroy)
test.B
`
17 changes: 16 additions & 1 deletion terraform/transform_destroy_edge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,25 @@ func (n *graphNodeCreatorTest) CreateAddr() *ResourceAddress {
type graphNodeDestroyerTest struct {
AddrString string
CBD bool
Modified bool
}

func (n *graphNodeDestroyerTest) Name() string {
result := n.DestroyAddr().String() + " (destroy)"
if n.Modified {
result += " (modified)"
}

return result
}

func (n *graphNodeDestroyerTest) Name() string { return n.DestroyAddr().String() + " (destroy)" }
func (n *graphNodeDestroyerTest) CreateBeforeDestroy() bool { return n.CBD }

func (n *graphNodeDestroyerTest) ModifyCreateBeforeDestroy(v bool) error {
n.Modified = true
return nil
}

func (n *graphNodeDestroyerTest) DestroyAddr() *ResourceAddress {
addr, err := ParseResourceAddress(n.AddrString)
if err != nil {
Expand Down