github.com/trawler/terraform@v0.10.8-0.20171106022149-4b1c7a1d9b48/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  		// If the address has an index, we strip that. Our depMap creation
    93  		// graph doesn't expand counts so we don't currently get _exact_
    94  		// dependencies. One day when we limit dependencies more exactly
    95  		// this will have to change. We have a test case covering this
    96  		// (depNonCBDCountBoth) so it'll be caught.
    97  		addr := dn.DestroyAddr()
    98  		if addr.Index >= 0 {
    99  			addr = addr.Copy() // Copy so that we don't modify any pointers
   100  			addr.Index = -1
   101  		}
   102  
   103  		// Add this to the list of nodes that we need to fix up
   104  		// the edges for (step 2 above in the docs).
   105  		key := addr.String()
   106  		destroyMap[key] = append(destroyMap[key], v)
   107  	}
   108  
   109  	// If we have no CBD nodes, then our work here is done
   110  	if len(destroyMap) == 0 {
   111  		return nil
   112  	}
   113  
   114  	// We have CBD nodes. We now have to move on to the much more difficult
   115  	// task of connecting dependencies of the creation side of the destroy
   116  	// to the destruction node. The easiest way to explain this is an example:
   117  	//
   118  	// Given a pre-destroy dependence of: A => B
   119  	//   And A has CBD set.
   120  	//
   121  	// The resulting graph should be: A => B => A_d
   122  	//
   123  	// They key here is that B happens before A is destroyed. This is to
   124  	// facilitate the primary purpose for CBD: making sure that downstreams
   125  	// are properly updated to avoid downtime before the resource is destroyed.
   126  	//
   127  	// We can't trust that the resource being destroyed or anything that
   128  	// depends on it is actually in our current graph so we make a new
   129  	// graph in order to determine those dependencies and add them in.
   130  	log.Printf("[TRACE] CBDEdgeTransformer: building graph to find dependencies...")
   131  	depMap, err := t.depMap(destroyMap)
   132  	if err != nil {
   133  		return err
   134  	}
   135  
   136  	// We now have the mapping of resource addresses to the destroy
   137  	// nodes they need to depend on. We now go through our own vertices to
   138  	// find any matching these addresses and make the connection.
   139  	for _, v := range g.Vertices() {
   140  		// We're looking for creators
   141  		rn, ok := v.(GraphNodeCreator)
   142  		if !ok {
   143  			continue
   144  		}
   145  
   146  		// Get the address
   147  		addr := rn.CreateAddr()
   148  
   149  		// If the address has an index, we strip that. Our depMap creation
   150  		// graph doesn't expand counts so we don't currently get _exact_
   151  		// dependencies. One day when we limit dependencies more exactly
   152  		// this will have to change. We have a test case covering this
   153  		// (depNonCBDCount) so it'll be caught.
   154  		if addr.Index >= 0 {
   155  			addr = addr.Copy() // Copy so that we don't modify any pointers
   156  			addr.Index = -1
   157  		}
   158  
   159  		// If there is nothing this resource should depend on, ignore it
   160  		key := addr.String()
   161  		dns, ok := depMap[key]
   162  		if !ok {
   163  			continue
   164  		}
   165  
   166  		// We have nodes! Make the connection
   167  		for _, dn := range dns {
   168  			log.Printf("[TRACE] CBDEdgeTransformer: destroy depends on dependence: %s => %s",
   169  				dag.VertexName(dn), dag.VertexName(v))
   170  			g.Connect(dag.BasicEdge(dn, v))
   171  		}
   172  	}
   173  
   174  	return nil
   175  }
   176  
   177  func (t *CBDEdgeTransformer) depMap(
   178  	destroyMap map[string][]dag.Vertex) (map[string][]dag.Vertex, error) {
   179  	// Build the graph of our config, this ensures that all resources
   180  	// are present in the graph.
   181  	g, err := (&BasicGraphBuilder{
   182  		Steps: []GraphTransformer{
   183  			&FlatConfigTransformer{Module: t.Module},
   184  			&AttachResourceConfigTransformer{Module: t.Module},
   185  			&AttachStateTransformer{State: t.State},
   186  			&ReferenceTransformer{},
   187  		},
   188  		Name: "CBDEdgeTransformer",
   189  	}).Build(nil)
   190  	if err != nil {
   191  		return nil, err
   192  	}
   193  
   194  	// Using this graph, build the list of destroy nodes that each resource
   195  	// address should depend on. For example, when we find B, we map the
   196  	// address of B to A_d in the "depMap" variable below.
   197  	depMap := make(map[string][]dag.Vertex)
   198  	for _, v := range g.Vertices() {
   199  		// We're looking for resources.
   200  		rn, ok := v.(GraphNodeResource)
   201  		if !ok {
   202  			continue
   203  		}
   204  
   205  		// Get the address
   206  		addr := rn.ResourceAddr()
   207  		key := addr.String()
   208  
   209  		// Get the destroy nodes that are destroying this resource.
   210  		// If there aren't any, then we don't need to worry about
   211  		// any connections.
   212  		dns, ok := destroyMap[key]
   213  		if !ok {
   214  			continue
   215  		}
   216  
   217  		// Get the nodes that depend on this on. In the example above:
   218  		// finding B in A => B.
   219  		for _, v := range g.UpEdges(v).List() {
   220  			// We're looking for resources.
   221  			rn, ok := v.(GraphNodeResource)
   222  			if !ok {
   223  				continue
   224  			}
   225  
   226  			// Keep track of the destroy nodes that this address
   227  			// needs to depend on.
   228  			key := rn.ResourceAddr().String()
   229  			depMap[key] = append(depMap[key], dns...)
   230  		}
   231  	}
   232  
   233  	return depMap, nil
   234  }
   235  
   236  // hasCBDAncestor returns true if any ancestor (node that depends on this)
   237  // has CBD set.
   238  func (t *CBDEdgeTransformer) hasCBDAncestor(g *Graph, v dag.Vertex) bool {
   239  	s, _ := g.Ancestors(v)
   240  	if s == nil {
   241  		return true
   242  	}
   243  
   244  	for _, v := range s.List() {
   245  		dn, ok := v.(GraphNodeDestroyerCBD)
   246  		if !ok {
   247  			continue
   248  		}
   249  
   250  		if dn.CreateBeforeDestroy() {
   251  			// some ancestor is CreateBeforeDestroy, so we need to follow suit
   252  			return true
   253  		}
   254  	}
   255  
   256  	return false
   257  }