github.com/hugorut/terraform@v1.1.3/src/terraform/transform_destroy_cbd.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  
     7  	"github.com/hugorut/terraform/src/configs"
     8  	"github.com/hugorut/terraform/src/dag"
     9  	"github.com/hugorut/terraform/src/states"
    10  )
    11  
    12  // GraphNodeDestroyerCBD must be implemented by nodes that might be
    13  // create-before-destroy destroyers, or might plan a create-before-destroy
    14  // action.
    15  type GraphNodeDestroyerCBD interface {
    16  	// CreateBeforeDestroy returns true if this node represents a node
    17  	// that is doing a CBD.
    18  	CreateBeforeDestroy() bool
    19  
    20  	// ModifyCreateBeforeDestroy is called when the CBD state of a node
    21  	// is changed dynamically. This can return an error if this isn't
    22  	// allowed.
    23  	ModifyCreateBeforeDestroy(bool) error
    24  }
    25  
    26  // ForcedCBDTransformer detects when a particular CBD-able graph node has
    27  // dependencies with another that has create_before_destroy set that require
    28  // it to be forced on, and forces it on.
    29  //
    30  // This must be used in the plan graph builder to ensure that
    31  // create_before_destroy settings are properly propagated before constructing
    32  // the planned changes. This requires that the plannable resource nodes
    33  // implement GraphNodeDestroyerCBD.
    34  type ForcedCBDTransformer struct {
    35  }
    36  
    37  func (t *ForcedCBDTransformer) Transform(g *Graph) error {
    38  	for _, v := range g.Vertices() {
    39  		dn, ok := v.(GraphNodeDestroyerCBD)
    40  		if !ok {
    41  			continue
    42  		}
    43  
    44  		if !dn.CreateBeforeDestroy() {
    45  			// If there are no CBD decendent (dependent nodes), then we
    46  			// do nothing here.
    47  			if !t.hasCBDDescendent(g, v) {
    48  				log.Printf("[TRACE] ForcedCBDTransformer: %q (%T) has no CBD descendent, so skipping", dag.VertexName(v), v)
    49  				continue
    50  			}
    51  
    52  			// If this isn't naturally a CBD node, this means that an descendent is
    53  			// and we need to auto-upgrade this node to CBD. We do this because
    54  			// a CBD node depending on non-CBD will result in cycles. To avoid this,
    55  			// we always attempt to upgrade it.
    56  			log.Printf("[TRACE] ForcedCBDTransformer: forcing create_before_destroy on for %q (%T)", dag.VertexName(v), v)
    57  			if err := dn.ModifyCreateBeforeDestroy(true); err != nil {
    58  				return fmt.Errorf(
    59  					"%s: must have create before destroy enabled because "+
    60  						"a dependent resource has CBD enabled. However, when "+
    61  						"attempting to automatically do this, an error occurred: %s",
    62  					dag.VertexName(v), err)
    63  			}
    64  		} else {
    65  			log.Printf("[TRACE] ForcedCBDTransformer: %q (%T) already has create_before_destroy set", dag.VertexName(v), v)
    66  		}
    67  	}
    68  	return nil
    69  }
    70  
    71  // hasCBDDescendent returns true if any descendent (node that depends on this)
    72  // has CBD set.
    73  func (t *ForcedCBDTransformer) hasCBDDescendent(g *Graph, v dag.Vertex) bool {
    74  	s, _ := g.Descendents(v)
    75  	if s == nil {
    76  		return true
    77  	}
    78  
    79  	for _, ov := range s {
    80  		dn, ok := ov.(GraphNodeDestroyerCBD)
    81  		if !ok {
    82  			continue
    83  		}
    84  
    85  		if dn.CreateBeforeDestroy() {
    86  			// some descendent is CreateBeforeDestroy, so we need to follow suit
    87  			log.Printf("[TRACE] ForcedCBDTransformer: %q has CBD descendent %q", dag.VertexName(v), dag.VertexName(ov))
    88  			return true
    89  		}
    90  	}
    91  
    92  	return false
    93  }
    94  
    95  // CBDEdgeTransformer modifies the edges of CBD nodes that went through
    96  // the DestroyEdgeTransformer to have the right dependencies. There are
    97  // two real tasks here:
    98  //
    99  //   1. With CBD, the destroy edge is inverted: the destroy depends on
   100  //      the creation.
   101  //
   102  //   2. A_d must depend on resources that depend on A. This is to enable
   103  //      the destroy to only happen once nodes that depend on A successfully
   104  //      update to A. Example: adding a web server updates the load balancer
   105  //      before deleting the old web server.
   106  //
   107  // This transformer requires that a previous transformer has already forced
   108  // create_before_destroy on for nodes that are depended on by explicit CBD
   109  // nodes. This is the logic in ForcedCBDTransformer, though in practice we
   110  // will get here by recording the CBD-ness of each change in the plan during
   111  // the plan walk and then forcing the nodes into the appropriate setting during
   112  // DiffTransformer when building the apply graph.
   113  type CBDEdgeTransformer struct {
   114  	// Module and State are only needed to look up dependencies in
   115  	// any way possible. Either can be nil if not availabile.
   116  	Config *configs.Config
   117  	State  *states.State
   118  }
   119  
   120  func (t *CBDEdgeTransformer) Transform(g *Graph) error {
   121  	// Go through and reverse any destroy edges
   122  	for _, v := range g.Vertices() {
   123  		dn, ok := v.(GraphNodeDestroyerCBD)
   124  		if !ok {
   125  			continue
   126  		}
   127  		if _, ok = v.(GraphNodeDestroyer); !ok {
   128  			continue
   129  		}
   130  
   131  		if !dn.CreateBeforeDestroy() {
   132  			continue
   133  		}
   134  
   135  		// Find the resource edges
   136  		for _, e := range g.EdgesTo(v) {
   137  			src := e.Source()
   138  
   139  			// If source is a create node, invert the edge.
   140  			// This covers both the node's own creator, as well as reversing
   141  			// any dependants' edges.
   142  			if _, ok := src.(GraphNodeCreator); ok {
   143  				log.Printf("[TRACE] CBDEdgeTransformer: reversing edge %s -> %s", dag.VertexName(src), dag.VertexName(v))
   144  				g.RemoveEdge(e)
   145  				g.Connect(dag.BasicEdge(v, src))
   146  			}
   147  		}
   148  	}
   149  	return nil
   150  }