opentofu/terraform/transform_destroy.go
Mitchell Hashimoto f3a62c694d
terraform: when promoting non-CBD to CBD, mark the config as such
Fixes #10439

When a CBD resource depends on a non-CBD resource, the non-CBD resource
is auto-promoted to CBD. This was done in
cf3a259. This PR makes it so that we
also set the config CBD to true. This causes the proper runtime
execution behavior to occur where we depose state and so on.

So in addition to simple graph edge tricks we also treat the non-CBD
resources as CBD resources.
2016-12-02 09:46:04 -05:00

285 lines
7.9 KiB
Go

package terraform
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
// destroy nodes in the graph and the dependency ordering of those destroys.
type GraphNodeDestroyable interface {
// 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() GraphNodeDestroy
}
// GraphNodeDestroy is the interface that must implemented by
// nodes that destroy.
type GraphNodeDestroy interface {
dag.Vertex
// CreateBeforeDestroy is called to check whether this node
// should be created before it is destroyed. The CreateBeforeDestroy
// transformer uses this information to setup the graph.
CreateBeforeDestroy() bool
// CreateNode returns the node used for the create side of this
// destroy. This must already exist within the graph.
CreateNode() dag.Vertex
}
// GraphNodeDestroyPrunable is the interface that can be implemented to
// signal that this node can be pruned depending on state.
type GraphNodeDestroyPrunable interface {
// DestroyInclude is called to check if this node should be included
// with the given state. The state and diff must NOT be modified.
DestroyInclude(*ModuleDiff, *ModuleState) bool
}
// GraphNodeEdgeInclude can be implemented to not include something
// as an edge within the destroy graph. This is usually done because it
// might cause unnecessary cycles.
type GraphNodeDestroyEdgeInclude interface {
DestroyEdgeInclude(dag.Vertex) bool
}
// DestroyTransformer is a GraphTransformer that creates the destruction
// nodes for things that _might_ be destroyed.
type DestroyTransformer struct {
FullDestroy bool
}
func (t *DestroyTransformer) Transform(g *Graph) 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
cn, ok := v.(GraphNodeDestroyable)
if !ok {
continue
}
// Grab the destroy side of the node and connect it through
n := cn.DestroyNode()
if n == nil {
continue
}
// Store it
nodeToCn[n] = cn
nodeToDn[cn] = n
// If the creation node is equal to the destroy node, then
// don't do any of the edge jump rope below.
if n.(interface{}) == cn.(interface{}) {
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 {
// If this thing specifically requests to not be depended on
// by destroy nodes, then don't.
if i, ok := edgeRaw.(GraphNodeDestroyEdgeInclude); ok &&
!i.DestroyEdgeInclude(v) {
continue
}
g.Connect(dag.BasicEdge(n, edgeRaw.(dag.Vertex)))
}
// Add a new edge to connect the node to be created to
// the destroy node.
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 nodeToCn {
for _, downRaw := range g.DownEdges(n).List() {
target := downRaw.(dag.Vertex)
cn2, ok := target.(GraphNodeDestroyable)
if !ok {
continue
}
newTarget := nodeToDn[cn2]
if newTarget == nil {
continue
}
// Make the new edge and transpose
connect = append(connect, dag.BasicEdge(newTarget, n))
// Remove the old edge
remove = append(remove, dag.BasicEdge(n, target))
}
}
// Atomatically add/remove the edges
for _, e := range connect {
g.Connect(e)
}
for _, e := range remove {
g.RemoveEdge(e)
}
return nil
}
// CreateBeforeDestroyTransformer is a GraphTransformer that modifies
// the destroys of some nodes so that the creation happens before the
// destroy.
type CreateBeforeDestroyTransformer struct{}
func (t *CreateBeforeDestroyTransformer) Transform(g *Graph) error {
// We "stage" the edge connections/destroys in these slices so that
// while we're doing the edge transformations (transpositions) in
// the graph, we're not affecting future edge transpositions. These
// slices let us stage ALL the changes that WILL happen so that all
// of the transformations happen atomically.
var connect, destroy []dag.Edge
for _, v := range g.Vertices() {
// We only care to use the destroy nodes
dn, ok := v.(GraphNodeDestroy)
if !ok {
continue
}
// If the node doesn't need to create before destroy, then 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
cn := dn.CreateNode()
// Take all the things which depend on the creation node and
// make them dependencies on the destruction. Clarifying this
// with an example: if you have a web server and a load balancer
// and the load balancer depends on the web server, then when we
// do a create before destroy, we want to make sure the steps are:
//
// 1.) Create new web server
// 2.) Update load balancer
// 3.) Delete old web server
//
// This ensures that.
for _, sourceRaw := range g.UpEdges(cn).List() {
source := sourceRaw.(dag.Vertex)
// If the graph has a "root" node (one added by a RootTransformer and not
// just a resource that happens to have no ancestors), we don't want to
// add any edges to it, because then it ceases to be a root.
if _, ok := source.(graphNodeRoot); ok {
continue
}
connect = append(connect, dag.BasicEdge(dn, source))
}
// Swap the edge so that the destroy depends on the creation
// happening...
connect = append(connect, dag.BasicEdge(dn, cn))
destroy = append(destroy, dag.BasicEdge(cn, dn))
}
for _, edge := range connect {
g.Connect(edge)
}
for _, edge := range destroy {
g.RemoveEdge(edge)
}
return nil
}
// noCreateBeforeDestroyAncestors verifies that a vertex has no ancestors that
// are CreateBeforeDestroy.
// If this vertex has an ancestor with CreateBeforeDestroy, we will need to
// inherit that behavior and re-order the edges even if this node type doesn't
// directly implement CreateBeforeDestroy.
func noCreateBeforeDestroyAncestors(g *Graph, v dag.Vertex) bool {
s, _ := g.Ancestors(v)
if s == nil {
return true
}
for _, v := range s.List() {
dn, ok := v.(GraphNodeDestroy)
if !ok {
continue
}
if dn.CreateBeforeDestroy() {
// some ancestor is CreateBeforeDestroy, so we need to follow suit
return false
}
}
return true
}
// PruneDestroyTransformer is a GraphTransformer that removes the destroy
// nodes that aren't in the diff.
type PruneDestroyTransformer struct {
Diff *Diff
State *State
}
func (t *PruneDestroyTransformer) Transform(g *Graph) error {
for _, v := range g.Vertices() {
// If it is not a destroyer, we don't care
dn, ok := v.(GraphNodeDestroyPrunable)
if !ok {
continue
}
path := g.Path
if pn, ok := v.(GraphNodeSubPath); ok {
path = pn.Path()
}
var modDiff *ModuleDiff
var modState *ModuleState
if t.Diff != nil {
modDiff = t.Diff.ModuleByPath(path)
}
if t.State != nil {
modState = t.State.ModuleByPath(path)
}
// Remove it if we should
if !dn.DestroyInclude(modDiff, modState) {
g.Remove(v)
}
}
return nil
}