terraform: tainted destroy nodes
This commit is contained in:
parent
991611857a
commit
4789f16796
|
@ -105,7 +105,7 @@ func (b *BuiltinGraphBuilder) Steps() []GraphTransformer {
|
|||
// Create the destruction nodes
|
||||
&DestroyTransformer{},
|
||||
&CreateBeforeDestroyTransformer{},
|
||||
//&PruneDestroyTransformer{Diff: b.Diff, State: b.State},
|
||||
&PruneDestroyTransformer{Diff: b.Diff, State: b.State},
|
||||
|
||||
// Make sure we create one root
|
||||
&RootTransformer{},
|
||||
|
|
|
@ -176,10 +176,9 @@ func (n *GraphNodeConfigProvider) ProviderName() string {
|
|||
type GraphNodeConfigResource struct {
|
||||
Resource *config.Resource
|
||||
|
||||
// If set to true, this represents a resource that can only be
|
||||
// destroyed. It doesn't mean that the resource WILL be destroyed, only
|
||||
// that logically this node is where it would happen.
|
||||
Destroy bool
|
||||
// If this is set to anything other than destroyModeNone, then this
|
||||
// resource represents a resource that will be destroyed in some way.
|
||||
DestroyMode GraphNodeDestroyMode
|
||||
}
|
||||
|
||||
func (n *GraphNodeConfigResource) DependableName() []string {
|
||||
|
@ -222,8 +221,14 @@ func (n *GraphNodeConfigResource) DependentOn() []string {
|
|||
|
||||
func (n *GraphNodeConfigResource) Name() string {
|
||||
result := n.Resource.Id()
|
||||
if n.Destroy {
|
||||
switch n.DestroyMode {
|
||||
case DestroyNone:
|
||||
case DestroyPrimary:
|
||||
result += " (destroy)"
|
||||
case DestroyTainted:
|
||||
result += " (destroy tainted)"
|
||||
default:
|
||||
result += " (unknown destroy type)"
|
||||
}
|
||||
|
||||
return result
|
||||
|
@ -231,29 +236,52 @@ func (n *GraphNodeConfigResource) Name() string {
|
|||
|
||||
// GraphNodeDynamicExpandable impl.
|
||||
func (n *GraphNodeConfigResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
|
||||
state, lock := ctx.State()
|
||||
lock.RLock()
|
||||
defer lock.RUnlock()
|
||||
|
||||
// Start creating the steps
|
||||
steps := make([]GraphTransformer, 0, 5)
|
||||
steps = append(steps, &ResourceCountTransformer{
|
||||
Resource: n.Resource,
|
||||
Destroy: n.Destroy,
|
||||
})
|
||||
|
||||
// If we're destroying, then we care about adding orphans to
|
||||
// the graph. Orphans in this case are the leftover resources when
|
||||
// we decrease count.
|
||||
if n.Destroy {
|
||||
state, lock := ctx.State()
|
||||
lock.RLock()
|
||||
defer lock.RUnlock()
|
||||
// Primary and non-destroy modes are responsible for creating/destroying
|
||||
// all the nodes, expanding counts.
|
||||
switch n.DestroyMode {
|
||||
case DestroyNone:
|
||||
fallthrough
|
||||
case DestroyPrimary:
|
||||
steps = append(steps, &ResourceCountTransformer{
|
||||
Resource: n.Resource,
|
||||
Destroy: n.DestroyMode != DestroyNone,
|
||||
})
|
||||
}
|
||||
|
||||
// Additional destroy modifications.
|
||||
switch n.DestroyMode {
|
||||
case DestroyPrimary:
|
||||
// If we're destroying the primary instance, then we want to
|
||||
// expand orphans, which have all the same semantics in a destroy
|
||||
// as a primary.
|
||||
steps = append(steps, &OrphanTransformer{
|
||||
State: state,
|
||||
View: n.Resource.Id(),
|
||||
})
|
||||
|
||||
// If we're only destroying tainted resources, then we only
|
||||
// want to find tainted resources and destroy them here.
|
||||
steps = append(steps, &TaintedTransformer{
|
||||
State: state,
|
||||
View: n.Resource.Id(),
|
||||
State: state,
|
||||
View: n.Resource.Id(),
|
||||
Deposed: n.Resource.Lifecycle.CreateBeforeDestroy,
|
||||
DeposedInclude: true,
|
||||
})
|
||||
case DestroyTainted:
|
||||
// If we're only destroying tainted resources, then we only
|
||||
// want to find tainted resources and destroy them here.
|
||||
steps = append(steps, &TaintedTransformer{
|
||||
State: state,
|
||||
View: n.Resource.Id(),
|
||||
Deposed: n.Resource.Lifecycle.CreateBeforeDestroy,
|
||||
DeposedInclude: false,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -295,19 +323,17 @@ func (n *GraphNodeConfigResource) ProvisionedBy() []string {
|
|||
}
|
||||
|
||||
// GraphNodeDestroyable
|
||||
func (n *GraphNodeConfigResource) DestroyNode() GraphNodeDestroy {
|
||||
func (n *GraphNodeConfigResource) DestroyNode(mode GraphNodeDestroyMode) GraphNodeDestroy {
|
||||
// If we're already a destroy node, then don't do anything
|
||||
if n.Destroy {
|
||||
if n.DestroyMode != DestroyNone {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Just make a copy that is set to destroy
|
||||
result := &graphNodeResourceDestroy{
|
||||
GraphNodeConfigResource: *n,
|
||||
Original: n,
|
||||
}
|
||||
result.Destroy = true
|
||||
|
||||
result.DestroyMode = mode
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -320,7 +346,13 @@ type graphNodeResourceDestroy struct {
|
|||
}
|
||||
|
||||
func (n *graphNodeResourceDestroy) CreateBeforeDestroy() bool {
|
||||
return n.Original.Resource.Lifecycle.CreateBeforeDestroy
|
||||
// CBD is enabled if the resource enables it in addition to us
|
||||
// being responsible for destroying the primary state. The primary
|
||||
// state destroy node is the only destroy node that needs to be
|
||||
// "shuffled" according to the CBD rules, since tainted resources
|
||||
// don't have the same inverse dependencies.
|
||||
return n.Original.Resource.Lifecycle.CreateBeforeDestroy &&
|
||||
n.DestroyMode == DestroyPrimary
|
||||
}
|
||||
|
||||
func (n *graphNodeResourceDestroy) CreateNode() dag.Vertex {
|
||||
|
|
|
@ -6,13 +6,22 @@ import (
|
|||
"github.com/hashicorp/terraform/dag"
|
||||
)
|
||||
|
||||
type GraphNodeDestroyMode byte
|
||||
|
||||
const (
|
||||
DestroyNone GraphNodeDestroyMode = 0
|
||||
DestroyPrimary GraphNodeDestroyMode = 1 << iota
|
||||
DestroyTainted
|
||||
)
|
||||
|
||||
// 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 should
|
||||
// return a new destroy node that isn't in the graph.
|
||||
DestroyNode() GraphNodeDestroy
|
||||
// DestroyNode returns the node used for the destroy with the given
|
||||
// mode. If this returns nil, then a destroy node for that mode
|
||||
// will not be added.
|
||||
DestroyNode(GraphNodeDestroyMode) GraphNodeDestroy
|
||||
}
|
||||
|
||||
// GraphNodeDestroy is the interface that must implemented by
|
||||
|
@ -43,24 +52,51 @@ type GraphNodeDiffPrunable interface {
|
|||
type DestroyTransformer struct{}
|
||||
|
||||
func (t *DestroyTransformer) Transform(g *Graph) error {
|
||||
nodes := make(map[dag.Vertex]struct{}, len(g.Vertices()))
|
||||
var connect, remove []dag.Edge
|
||||
|
||||
modes := []GraphNodeDestroyMode{DestroyPrimary, DestroyTainted}
|
||||
for _, m := range modes {
|
||||
connectMode, removeMode, err := t.transform(g, m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
connect = append(connect, connectMode...)
|
||||
remove = append(remove, removeMode...)
|
||||
}
|
||||
|
||||
// Atomatically add/remove the edges
|
||||
for _, e := range connect {
|
||||
g.Connect(e)
|
||||
}
|
||||
for _, e := range remove {
|
||||
g.RemoveEdge(e)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *DestroyTransformer) transform(
|
||||
g *Graph, mode GraphNodeDestroyMode) ([]dag.Edge, []dag.Edge, error) {
|
||||
var connect, remove []dag.Edge
|
||||
nodeToCn := make(map[dag.Vertex]dag.Vertex, len(g.Vertices()))
|
||||
nodeToDn := make(map[dag.Vertex]dag.Vertex, len(g.Vertices()))
|
||||
for _, v := range g.Vertices() {
|
||||
// If it is not a destroyable, we don't care
|
||||
dn, ok := v.(GraphNodeDestroyable)
|
||||
cn, ok := v.(GraphNodeDestroyable)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Grab the destroy side of the node and connect it through
|
||||
n := dn.DestroyNode()
|
||||
n := cn.DestroyNode(mode)
|
||||
if n == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Store it
|
||||
nodes[n] = struct{}{}
|
||||
nodeToDn[dn] = n
|
||||
nodeToCn[n] = cn
|
||||
nodeToDn[cn] = n
|
||||
|
||||
// Add it to the graph
|
||||
g.Add(n)
|
||||
|
@ -73,33 +109,33 @@ func (t *DestroyTransformer) Transform(g *Graph) error {
|
|||
|
||||
// Add a new edge to connect the node to be created to
|
||||
// the destroy node.
|
||||
g.Connect(dag.BasicEdge(v, n))
|
||||
connect = append(connect, dag.BasicEdge(v, n))
|
||||
}
|
||||
|
||||
// Go through the nodes we added and determine if they depend
|
||||
// on any nodes with a destroy node. If so, depend on that instead.
|
||||
for n, _ := range nodes {
|
||||
for n, _ := range nodeToCn {
|
||||
for _, downRaw := range g.DownEdges(n).List() {
|
||||
target := downRaw.(dag.Vertex)
|
||||
dn, ok := target.(GraphNodeDestroyable)
|
||||
cn2, ok := target.(GraphNodeDestroyable)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
newTarget := nodeToDn[dn]
|
||||
newTarget := nodeToDn[cn2]
|
||||
if newTarget == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Make the new edge and transpose
|
||||
g.Connect(dag.BasicEdge(newTarget, n))
|
||||
connect = append(connect, dag.BasicEdge(newTarget, n))
|
||||
|
||||
// Remove the old edge
|
||||
g.RemoveEdge(dag.BasicEdge(n, target))
|
||||
remove = append(remove, dag.BasicEdge(n, target))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return connect, remove, nil
|
||||
}
|
||||
|
||||
// CreateBeforeDestroyTransformer is a GraphTransformer that modifies
|
||||
|
|
|
@ -30,31 +30,6 @@ func TestDestroyTransformer(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDestroyTransformer_deps(t *testing.T) {
|
||||
mod := testModule(t, "transform-destroy-deps")
|
||||
|
||||
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(testTransformDestroyDepsStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateBeforeDestroyTransformer(t *testing.T) {
|
||||
mod := testModule(t, "transform-create-before-destroy-basic")
|
||||
|
||||
|
@ -231,26 +206,20 @@ func TestPruneDestroyTransformer_count(t *testing.T) {
|
|||
|
||||
const testTransformDestroyBasicStr = `
|
||||
aws_instance.bar
|
||||
aws_instance.bar (destroy tainted)
|
||||
aws_instance.bar (destroy)
|
||||
aws_instance.foo
|
||||
aws_instance.bar (destroy tainted)
|
||||
aws_instance.bar (destroy)
|
||||
aws_instance.foo
|
||||
aws_instance.foo (destroy tainted)
|
||||
aws_instance.foo (destroy)
|
||||
aws_instance.foo (destroy tainted)
|
||||
aws_instance.bar (destroy tainted)
|
||||
aws_instance.foo (destroy)
|
||||
aws_instance.bar (destroy)
|
||||
`
|
||||
|
||||
const testTransformDestroyDepsStr = `
|
||||
aws_asg.bar
|
||||
aws_asg.bar (destroy)
|
||||
aws_lc.foo
|
||||
aws_asg.bar (destroy)
|
||||
aws_lc.foo
|
||||
aws_lc.foo (destroy)
|
||||
aws_lc.foo (destroy)
|
||||
aws_asg.bar (destroy)
|
||||
`
|
||||
|
||||
const testTransformPruneDestroyBasicStr = `
|
||||
aws_instance.bar
|
||||
aws_instance.foo
|
||||
|
|
|
@ -14,6 +14,12 @@ type TaintedTransformer struct {
|
|||
// View, if non-empty, is the ModuleState.View used around the state
|
||||
// to find tainted resources.
|
||||
View string
|
||||
|
||||
// Deposed, if set to true, assumes that the last tainted index
|
||||
// represents a "deposed" resource, or a resource that was previously
|
||||
// a primary but is now tainted since it is demoted.
|
||||
Deposed bool
|
||||
DeposedInclude bool
|
||||
}
|
||||
|
||||
func (t *TaintedTransformer) Transform(g *Graph) error {
|
||||
|
@ -35,8 +41,20 @@ func (t *TaintedTransformer) Transform(g *Graph) error {
|
|||
if len(rs.Tainted) == 0 {
|
||||
continue
|
||||
}
|
||||
tainted := rs.Tainted
|
||||
|
||||
for i, _ := range rs.Tainted {
|
||||
// If we expect a deposed resource, then shuffle a bit
|
||||
if t.Deposed {
|
||||
if t.DeposedInclude {
|
||||
// Only include the deposed resource
|
||||
tainted = rs.Tainted[len(rs.Tainted)-1:]
|
||||
} else {
|
||||
// Exclude the deposed resource
|
||||
tainted = rs.Tainted[:len(rs.Tainted)-1]
|
||||
}
|
||||
}
|
||||
|
||||
for i, _ := range tainted {
|
||||
// Add the graph node and make the connection from any untainted
|
||||
// resources with this name to the tainted resource, so that
|
||||
// the tainted resource gets destroyed first.
|
||||
|
|
Loading…
Reference in New Issue