github.com/aspring/terraform@v0.8.2-0.20161216122603-6a8619a5db2e/terraform/transform_destroy_cbd.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  
     7  	"github.com/hashicorp/terraform/config/module"
     8  	"github.com/hashicorp/terraform/dag"
     9  )
    10  
    11  // GraphNodeDestroyerCBD must be implemented by nodes that might be
    12  // create-before-destroy destroyers.
    13  type GraphNodeDestroyerCBD interface {
    14  	GraphNodeDestroyer
    15  
    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  // CBDEdgeTransformer modifies the edges of CBD nodes that went through
    27  // the DestroyEdgeTransformer to have the right dependencies. There are
    28  // two real tasks here:
    29  //
    30  //   1. With CBD, the destroy edge is inverted: the destroy depends on
    31  //      the creation.
    32  //
    33  //   2. A_d must depend on resources that depend on A. This is to enable
    34  //      the destroy to only happen once nodes that depend on A successfully
    35  //      update to A. Example: adding a web server updates the load balancer
    36  //      before deleting the old web server.
    37  //
    38  type CBDEdgeTransformer struct {
    39  	// Module and State are only needed to look up dependencies in
    40  	// any way possible. Either can be nil if not availabile.
    41  	Module *module.Tree
    42  	State  *State
    43  }
    44  
    45  func (t *CBDEdgeTransformer) Transform(g *Graph) error {
    46  	log.Printf("[TRACE] CBDEdgeTransformer: Beginning CBD transformation...")
    47  
    48  	// Go through and reverse any destroy edges
    49  	destroyMap := make(map[string][]dag.Vertex)
    50  	for _, v := range g.Vertices() {
    51  		dn, ok := v.(GraphNodeDestroyerCBD)
    52  		if !ok {
    53  			continue
    54  		}
    55  
    56  		if !dn.CreateBeforeDestroy() {
    57  			// If there are no CBD ancestors (dependent nodes), then we
    58  			// do nothing here.
    59  			if !t.hasCBDAncestor(g, v) {
    60  				continue
    61  			}
    62  
    63  			// If this isn't naturally a CBD node, this means that an ancestor is
    64  			// and we need to auto-upgrade this node to CBD. We do this because
    65  			// a CBD node depending on non-CBD will result in cycles. To avoid this,
    66  			// we always attempt to upgrade it.
    67  			if err := dn.ModifyCreateBeforeDestroy(true); err != nil {
    68  				return fmt.Errorf(
    69  					"%s: must have create before destroy enabled because "+
    70  						"a dependent resource has CBD enabled. However, when "+
    71  						"attempting to automatically do this, an error occurred: %s",
    72  					dag.VertexName(v), err)
    73  			}
    74  		}
    75  
    76  		// Find the destroy edge. There should only be one.
    77  		for _, e := range g.EdgesTo(v) {
    78  			// Not a destroy edge, ignore it
    79  			de, ok := e.(*DestroyEdge)
    80  			if !ok {
    81  				continue
    82  			}
    83  
    84  			log.Printf("[TRACE] CBDEdgeTransformer: inverting edge: %s => %s",
    85  				dag.VertexName(de.Source()), dag.VertexName(de.Target()))
    86  
    87  			// Found it! Invert.
    88  			g.RemoveEdge(de)
    89  			g.Connect(&DestroyEdge{S: de.Target(), T: de.Source()})
    90  		}
    91  
    92  		// Add this to the list of nodes that we need to fix up
    93  		// the edges for (step 2 above in the docs).
    94  		key := dn.DestroyAddr().String()
    95  		destroyMap[key] = append(destroyMap[key], v)
    96  	}
    97  
    98  	// If we have no CBD nodes, then our work here is done
    99  	if len(destroyMap) == 0 {
   100  		return nil
   101  	}
   102  
   103  	// We have CBD nodes. We now have to move on to the much more difficult
   104  	// task of connecting dependencies of the creation side of the destroy
   105  	// to the destruction node. The easiest way to explain this is an example:
   106  	//
   107  	// Given a pre-destroy dependence of: A => B
   108  	//   And A has CBD set.
   109  	//
   110  	// The resulting graph should be: A => B => A_d
   111  	//
   112  	// They key here is that B happens before A is destroyed. This is to
   113  	// facilitate the primary purpose for CBD: making sure that downstreams
   114  	// are properly updated to avoid downtime before the resource is destroyed.
   115  	//
   116  	// We can't trust that the resource being destroyed or anything that
   117  	// depends on it is actually in our current graph so we make a new
   118  	// graph in order to determine those dependencies and add them in.
   119  	log.Printf("[TRACE] CBDEdgeTransformer: building graph to find dependencies...")
   120  	depMap, err := t.depMap(destroyMap)
   121  	if err != nil {
   122  		return err
   123  	}
   124  
   125  	// We now have the mapping of resource addresses to the destroy
   126  	// nodes they need to depend on. We now go through our own vertices to
   127  	// find any matching these addresses and make the connection.
   128  	for _, v := range g.Vertices() {
   129  		// We're looking for creators
   130  		rn, ok := v.(GraphNodeCreator)
   131  		if !ok {
   132  			continue
   133  		}
   134  
   135  		// Get the address
   136  		addr := rn.CreateAddr()
   137  		key := addr.String()
   138  
   139  		// If there is nothing this resource should depend on, ignore it
   140  		dns, ok := depMap[key]
   141  		if !ok {
   142  			continue
   143  		}
   144  
   145  		// We have nodes! Make the connection
   146  		for _, dn := range dns {
   147  			log.Printf("[TRACE] CBDEdgeTransformer: destroy depends on dependence: %s => %s",
   148  				dag.VertexName(dn), dag.VertexName(v))
   149  			g.Connect(dag.BasicEdge(dn, v))
   150  		}
   151  	}
   152  
   153  	return nil
   154  }
   155  
   156  func (t *CBDEdgeTransformer) depMap(
   157  	destroyMap map[string][]dag.Vertex) (map[string][]dag.Vertex, error) {
   158  	// Build the graph of our config, this ensures that all resources
   159  	// are present in the graph.
   160  	g, err := (&BasicGraphBuilder{
   161  		Steps: []GraphTransformer{
   162  			&FlatConfigTransformer{Module: t.Module},
   163  			&AttachResourceConfigTransformer{Module: t.Module},
   164  			&AttachStateTransformer{State: t.State},
   165  			&ReferenceTransformer{},
   166  		},
   167  		Name: "CBDEdgeTransformer",
   168  	}).Build(nil)
   169  	if err != nil {
   170  		return nil, err
   171  	}
   172  
   173  	// Using this graph, build the list of destroy nodes that each resource
   174  	// address should depend on. For example, when we find B, we map the
   175  	// address of B to A_d in the "depMap" variable below.
   176  	depMap := make(map[string][]dag.Vertex)
   177  	for _, v := range g.Vertices() {
   178  		// We're looking for resources.
   179  		rn, ok := v.(GraphNodeResource)
   180  		if !ok {
   181  			continue
   182  		}
   183  
   184  		// Get the address
   185  		addr := rn.ResourceAddr()
   186  		key := addr.String()
   187  
   188  		// Get the destroy nodes that are destroying this resource.
   189  		// If there aren't any, then we don't need to worry about
   190  		// any connections.
   191  		dns, ok := destroyMap[key]
   192  		if !ok {
   193  			continue
   194  		}
   195  
   196  		// Get the nodes that depend on this on. In the example above:
   197  		// finding B in A => B.
   198  		for _, v := range g.UpEdges(v).List() {
   199  			// We're looking for resources.
   200  			rn, ok := v.(GraphNodeResource)
   201  			if !ok {
   202  				continue
   203  			}
   204  
   205  			// Keep track of the destroy nodes that this address
   206  			// needs to depend on.
   207  			key := rn.ResourceAddr().String()
   208  			depMap[key] = append(depMap[key], dns...)
   209  		}
   210  	}
   211  
   212  	return depMap, nil
   213  }
   214  
   215  // hasCBDAncestor returns true if any ancestor (node that depends on this)
   216  // has CBD set.
   217  func (t *CBDEdgeTransformer) hasCBDAncestor(g *Graph, v dag.Vertex) bool {
   218  	s, _ := g.Ancestors(v)
   219  	if s == nil {
   220  		return true
   221  	}
   222  
   223  	for _, v := range s.List() {
   224  		dn, ok := v.(GraphNodeDestroyerCBD)
   225  		if !ok {
   226  			continue
   227  		}
   228  
   229  		if dn.CreateBeforeDestroy() {
   230  			// some ancestor is CreateBeforeDestroy, so we need to follow suit
   231  			return true
   232  		}
   233  	}
   234  
   235  	return false
   236  }