github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/refactoring/move_execute.go (about)

     1  package refactoring
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  
     7  	"github.com/hashicorp/terraform/internal/addrs"
     8  	"github.com/hashicorp/terraform/internal/dag"
     9  	"github.com/hashicorp/terraform/internal/logging"
    10  	"github.com/hashicorp/terraform/internal/states"
    11  )
    12  
    13  type MoveResult struct {
    14  	From, To addrs.AbsResourceInstance
    15  }
    16  
    17  // ApplyMoves modifies in-place the given state object so that any existing
    18  // objects that are matched by a "from" argument of one of the move statements
    19  // will be moved to instead appear at the "to" argument of that statement.
    20  //
    21  // The result is a map from the unique key of each absolute address that was
    22  // either the source or destination of a move to a MoveResult describing
    23  // what happened at that address.
    24  //
    25  // ApplyMoves does not have any error situations itself, and will instead just
    26  // ignore any unresolvable move statements. Validation of a set of moves is
    27  // a separate concern applied to the configuration, because validity of
    28  // moves is always dependent only on the configuration, not on the state.
    29  //
    30  // ApplyMoves expects exclusive access to the given state while it's running.
    31  // Don't read or write any part of the state structure until ApplyMoves returns.
    32  func ApplyMoves(stmts []MoveStatement, state *states.State) map[addrs.UniqueKey]MoveResult {
    33  	results := make(map[addrs.UniqueKey]MoveResult)
    34  
    35  	// The methodology here is to construct a small graph of all of the move
    36  	// statements where the edges represent where a particular statement
    37  	// is either chained from or nested inside the effect of another statement.
    38  	// That then means we can traverse the graph in topological sort order
    39  	// to gradually move objects through potentially multiple moves each.
    40  
    41  	g := buildMoveStatementGraph(stmts)
    42  
    43  	// If there are any cycles in the graph then we'll not take any action
    44  	// at all. The separate validation step should detect this and return
    45  	// an error.
    46  	if len(g.Cycles()) != 0 {
    47  		return results
    48  	}
    49  
    50  	// The starting nodes are the ones that don't depend on any other nodes.
    51  	startNodes := make(dag.Set, len(stmts))
    52  	for _, v := range g.Vertices() {
    53  		if len(g.DownEdges(v)) == 0 {
    54  			startNodes.Add(v)
    55  		}
    56  	}
    57  
    58  	if startNodes.Len() == 0 {
    59  		log.Println("[TRACE] refactoring.ApplyMoves: No 'moved' statements to consider in this configuration")
    60  		return results
    61  	}
    62  
    63  	log.Printf("[TRACE] refactoring.ApplyMoves: Processing 'moved' statements in the configuration\n%s", logging.Indent(g.String()))
    64  
    65  	g.ReverseDepthFirstWalk(startNodes, func(v dag.Vertex, depth int) error {
    66  		stmt := v.(*MoveStatement)
    67  
    68  		for _, ms := range state.Modules {
    69  			modAddr := ms.Addr
    70  			if !stmt.From.SelectsModule(modAddr) {
    71  				continue
    72  			}
    73  
    74  			// We now know that the current module is relevant but what
    75  			// we'll do with it depends on the object kind.
    76  			switch kind := stmt.ObjectKind(); kind {
    77  			case addrs.MoveEndpointModule:
    78  				// For a module endpoint we just try the module address
    79  				// directly.
    80  				if newAddr, matches := modAddr.MoveDestination(stmt.From, stmt.To); matches {
    81  					log.Printf("[TRACE] refactoring.ApplyMoves: %s has moved to %s", modAddr, newAddr)
    82  					// We need to visit all of the resource instances in the
    83  					// module and record them individually as results.
    84  					for _, rs := range ms.Resources {
    85  						relAddr := rs.Addr.Resource
    86  						for key := range rs.Instances {
    87  							oldInst := relAddr.Instance(key).Absolute(modAddr)
    88  							newInst := relAddr.Instance(key).Absolute(newAddr)
    89  							result := MoveResult{
    90  								From: oldInst,
    91  								To:   newInst,
    92  							}
    93  							results[oldInst.UniqueKey()] = result
    94  							results[newInst.UniqueKey()] = result
    95  						}
    96  					}
    97  
    98  					state.MoveModuleInstance(modAddr, newAddr)
    99  					continue
   100  				}
   101  			case addrs.MoveEndpointResource:
   102  				// For a resource endpoint we need to search each of the
   103  				// resources and resource instances in the module.
   104  				for _, rs := range ms.Resources {
   105  					rAddr := rs.Addr
   106  					if newAddr, matches := rAddr.MoveDestination(stmt.From, stmt.To); matches {
   107  						log.Printf("[TRACE] refactoring.ApplyMoves: resource %s has moved to %s", rAddr, newAddr)
   108  						for key := range rs.Instances {
   109  							oldInst := rAddr.Instance(key)
   110  							newInst := newAddr.Instance(key)
   111  							result := MoveResult{
   112  								From: oldInst,
   113  								To:   newInst,
   114  							}
   115  							results[oldInst.UniqueKey()] = result
   116  							results[newInst.UniqueKey()] = result
   117  						}
   118  						state.MoveAbsResource(rAddr, newAddr)
   119  						continue
   120  					}
   121  					for key := range rs.Instances {
   122  						iAddr := rAddr.Instance(key)
   123  						if newAddr, matches := iAddr.MoveDestination(stmt.From, stmt.To); matches {
   124  							log.Printf("[TRACE] refactoring.ApplyMoves: resource instance %s has moved to %s", iAddr, newAddr)
   125  							result := MoveResult{From: iAddr, To: newAddr}
   126  							results[iAddr.UniqueKey()] = result
   127  							results[newAddr.UniqueKey()] = result
   128  
   129  							state.MoveAbsResourceInstance(iAddr, newAddr)
   130  							continue
   131  						}
   132  					}
   133  				}
   134  			default:
   135  				panic(fmt.Sprintf("unhandled move object kind %s", kind))
   136  			}
   137  		}
   138  
   139  		return nil
   140  	})
   141  
   142  	// FIXME: In the case of either chained or nested moves, "results" will
   143  	// be left in a pretty interesting shape where the "old" address will
   144  	// refer to a result that describes only the first step, while the "new"
   145  	// address will refer to a result that describes only the last step.
   146  	// To make that actually useful we'll need a different strategy where
   147  	// the result describes the _effective_ source and destination, skipping
   148  	// over any intermediate steps we took to get there, so that ultimately
   149  	// we'll have enough information to annotate items in the plan with the
   150  	// addresses the originally moved from.
   151  
   152  	return results
   153  }
   154  
   155  // buildMoveStatementGraph constructs a dependency graph of the given move
   156  // statements, where the nodes are all pointers to statements in the given
   157  // slice and the edges represent either chaining or nesting relationships.
   158  //
   159  // buildMoveStatementGraph doesn't do any validation of the graph, so it
   160  // may contain cycles and other sorts of invalidity.
   161  func buildMoveStatementGraph(stmts []MoveStatement) *dag.AcyclicGraph {
   162  	g := &dag.AcyclicGraph{}
   163  	for i := range stmts {
   164  		// The graph nodes are pointers to the actual statements directly.
   165  		g.Add(&stmts[i])
   166  	}
   167  
   168  	// Now we'll add the edges representing chaining and nesting relationships.
   169  	// We assume that a reasonable configuration will have at most tens of
   170  	// move statements and thus this N*M algorithm is acceptable.
   171  	for dependerI := range stmts {
   172  		depender := &stmts[dependerI]
   173  		for dependeeI := range stmts {
   174  			dependee := &stmts[dependeeI]
   175  			dependeeTo := dependee.To
   176  			dependerFrom := depender.From
   177  			if dependerFrom.CanChainFrom(dependeeTo) || dependerFrom.NestedWithin(dependeeTo) {
   178  				g.Connect(dag.BasicEdge(depender, dependee))
   179  			}
   180  		}
   181  	}
   182  
   183  	return g
   184  }