github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/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  // ApplyMoves modifies in-place the given state object so that any existing
    14  // objects that are matched by a "from" argument of one of the move statements
    15  // will be moved to instead appear at the "to" argument of that statement.
    16  //
    17  // The result is a map from the unique key of each absolute address that was
    18  // either the source or destination of a move to a MoveResult describing
    19  // what happened at that address.
    20  //
    21  // ApplyMoves does not have any error situations itself, and will instead just
    22  // ignore any unresolvable move statements. Validation of a set of moves is
    23  // a separate concern applied to the configuration, because validity of
    24  // moves is always dependent only on the configuration, not on the state.
    25  //
    26  // ApplyMoves expects exclusive access to the given state while it's running.
    27  // Don't read or write any part of the state structure until ApplyMoves returns.
    28  func ApplyMoves(stmts []MoveStatement, state *states.State) MoveResults {
    29  	ret := makeMoveResults()
    30  
    31  	if len(stmts) == 0 {
    32  		return ret
    33  	}
    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 the graph is not valid the we will not take any action at all. The
    44  	// separate validation step should detect this and return an error.
    45  	if diags := validateMoveStatementGraph(g); diags.HasErrors() {
    46  		log.Printf("[ERROR] ApplyMoves: %s", diags.ErrWithWarnings())
    47  		return ret
    48  	}
    49  
    50  	// The graph must be reduced in order for ReverseDepthFirstWalk to work
    51  	// correctly, since it is built from following edges and can skip over
    52  	// dependencies if there is a direct edge to a transitive dependency.
    53  	g.TransitiveReduction()
    54  
    55  	// The starting nodes are the ones that don't depend on any other nodes.
    56  	startNodes := make(dag.Set, len(stmts))
    57  	for _, v := range g.Vertices() {
    58  		if len(g.DownEdges(v)) == 0 {
    59  			startNodes.Add(v)
    60  		}
    61  	}
    62  
    63  	if startNodes.Len() == 0 {
    64  		log.Println("[TRACE] refactoring.ApplyMoves: No 'moved' statements to consider in this configuration")
    65  		return ret
    66  	}
    67  
    68  	log.Printf("[TRACE] refactoring.ApplyMoves: Processing 'moved' statements in the configuration\n%s", logging.Indent(g.String()))
    69  
    70  	recordOldAddr := func(oldAddr, newAddr addrs.AbsResourceInstance) {
    71  		if prevMove, exists := ret.Changes.GetOk(oldAddr); exists {
    72  			// If the old address was _already_ the result of a move then
    73  			// we'll replace that entry so that our results summarize a chain
    74  			// of moves into a single entry.
    75  			ret.Changes.Remove(oldAddr)
    76  			oldAddr = prevMove.From
    77  		}
    78  		ret.Changes.Put(newAddr, MoveSuccess{
    79  			From: oldAddr,
    80  			To:   newAddr,
    81  		})
    82  	}
    83  	recordBlockage := func(newAddr, wantedAddr addrs.AbsMoveable) {
    84  		ret.Blocked.Put(newAddr, MoveBlocked{
    85  			Wanted: wantedAddr,
    86  			Actual: newAddr,
    87  		})
    88  	}
    89  
    90  	for _, v := range g.ReverseTopologicalOrder() {
    91  		stmt := v.(*MoveStatement)
    92  
    93  		for _, ms := range state.Modules {
    94  			modAddr := ms.Addr
    95  
    96  			// We don't yet know that the current module is relevant, and
    97  			// we determine that differently for each the object kind.
    98  			switch kind := stmt.ObjectKind(); kind {
    99  			case addrs.MoveEndpointModule:
   100  				// For a module endpoint we just try the module address
   101  				// directly, and execute the moves if it matches.
   102  				if newAddr, matches := modAddr.MoveDestination(stmt.From, stmt.To); matches {
   103  					log.Printf("[TRACE] refactoring.ApplyMoves: %s has moved to %s", modAddr, newAddr)
   104  
   105  					// If we already have a module at the new address then
   106  					// we'll skip this move and let the existing object take
   107  					// priority.
   108  					if ms := state.Module(newAddr); ms != nil {
   109  						log.Printf("[WARN] Skipped moving %s to %s, because there's already another module instance at the destination", modAddr, newAddr)
   110  						recordBlockage(modAddr, newAddr)
   111  						continue
   112  					}
   113  
   114  					// We need to visit all of the resource instances in the
   115  					// module and record them individually as results.
   116  					for _, rs := range ms.Resources {
   117  						relAddr := rs.Addr.Resource
   118  						for key := range rs.Instances {
   119  							oldInst := relAddr.Instance(key).Absolute(modAddr)
   120  							newInst := relAddr.Instance(key).Absolute(newAddr)
   121  							recordOldAddr(oldInst, newInst)
   122  						}
   123  					}
   124  
   125  					state.MoveModuleInstance(modAddr, newAddr)
   126  					continue
   127  				}
   128  			case addrs.MoveEndpointResource:
   129  				// For a resource endpoint we require an exact containing
   130  				// module match, because by definition a matching resource
   131  				// cannot be nested any deeper than that.
   132  				if !stmt.From.SelectsModule(modAddr) {
   133  					continue
   134  				}
   135  
   136  				// We then need to search each of the resources and resource
   137  				// instances in the module.
   138  				for _, rs := range ms.Resources {
   139  					rAddr := rs.Addr
   140  					if newAddr, matches := rAddr.MoveDestination(stmt.From, stmt.To); matches {
   141  						log.Printf("[TRACE] refactoring.ApplyMoves: resource %s has moved to %s", rAddr, newAddr)
   142  
   143  						// If we already have a resource at the new address then
   144  						// we'll skip this move and let the existing object take
   145  						// priority.
   146  						if rs := state.Resource(newAddr); rs != nil {
   147  							log.Printf("[WARN] Skipped moving %s to %s, because there's already another resource at the destination", rAddr, newAddr)
   148  							recordBlockage(rAddr, newAddr)
   149  							continue
   150  						}
   151  
   152  						for key := range rs.Instances {
   153  							oldInst := rAddr.Instance(key)
   154  							newInst := newAddr.Instance(key)
   155  							recordOldAddr(oldInst, newInst)
   156  						}
   157  						state.MoveAbsResource(rAddr, newAddr)
   158  						continue
   159  					}
   160  					for key := range rs.Instances {
   161  						iAddr := rAddr.Instance(key)
   162  						if newAddr, matches := iAddr.MoveDestination(stmt.From, stmt.To); matches {
   163  							log.Printf("[TRACE] refactoring.ApplyMoves: resource instance %s has moved to %s", iAddr, newAddr)
   164  
   165  							// If we already have a resource instance at the new
   166  							// address then we'll skip this move and let the existing
   167  							// object take priority.
   168  							if is := state.ResourceInstance(newAddr); is != nil {
   169  								log.Printf("[WARN] Skipped moving %s to %s, because there's already another resource instance at the destination", iAddr, newAddr)
   170  								recordBlockage(iAddr, newAddr)
   171  								continue
   172  							}
   173  
   174  							recordOldAddr(iAddr, newAddr)
   175  
   176  							state.MoveAbsResourceInstance(iAddr, newAddr)
   177  							continue
   178  						}
   179  					}
   180  				}
   181  			default:
   182  				panic(fmt.Sprintf("unhandled move object kind %s", kind))
   183  			}
   184  		}
   185  	}
   186  
   187  	return ret
   188  }
   189  
   190  // buildMoveStatementGraph constructs a dependency graph of the given move
   191  // statements, where the nodes are all pointers to statements in the given
   192  // slice and the edges represent either chaining or nesting relationships.
   193  //
   194  // buildMoveStatementGraph doesn't do any validation of the graph, so it
   195  // may contain cycles and other sorts of invalidity.
   196  func buildMoveStatementGraph(stmts []MoveStatement) *dag.AcyclicGraph {
   197  	g := &dag.AcyclicGraph{}
   198  	for i := range stmts {
   199  		// The graph nodes are pointers to the actual statements directly.
   200  		g.Add(&stmts[i])
   201  	}
   202  
   203  	// Now we'll add the edges representing chaining and nesting relationships.
   204  	// We assume that a reasonable configuration will have at most tens of
   205  	// move statements and thus this N*M algorithm is acceptable.
   206  	for dependerI := range stmts {
   207  		depender := &stmts[dependerI]
   208  		for dependeeI := range stmts {
   209  			if dependerI == dependeeI {
   210  				// skip comparing the statement to itself
   211  				continue
   212  			}
   213  			dependee := &stmts[dependeeI]
   214  
   215  			if statementDependsOn(depender, dependee) {
   216  				g.Connect(dag.BasicEdge(depender, dependee))
   217  			}
   218  		}
   219  	}
   220  
   221  	return g
   222  }
   223  
   224  // statementDependsOn returns true if statement a depends on statement b;
   225  // i.e. statement b must be executed before statement a.
   226  func statementDependsOn(a, b *MoveStatement) bool {
   227  	// chain-able moves are simple, as on the destination of one move could be
   228  	// equal to the source of another.
   229  	if a.From.CanChainFrom(b.To) {
   230  		return true
   231  	}
   232  
   233  	// Statement nesting in more complex, as we have 8 possible combinations to
   234  	// assess. Here we list all combinations, along with the statement which
   235  	// must be executed first when one address is nested within another.
   236  	// A.From  IsNestedWithin  B.From => A
   237  	// A.From  IsNestedWithin  B.To   => B
   238  	// A.To    IsNestedWithin  B.From => A
   239  	// A.To    IsNestedWithin  B.To   => B
   240  	// B.From  IsNestedWithin  A.From => B
   241  	// B.From  IsNestedWithin  A.To   => A
   242  	// B.To    IsNestedWithin  A.From => B
   243  	// B.To    IsNestedWithin  A.To   => A
   244  	//
   245  	// Since we are only interested in checking if A depends on B, we only need
   246  	// to check the 4 possibilities above which result in B being executed
   247  	// first. If we're there's no dependency at all we can return immediately.
   248  	if !(a.From.NestedWithin(b.To) || a.To.NestedWithin(b.To) ||
   249  		b.From.NestedWithin(a.From) || b.To.NestedWithin(a.From)) {
   250  		return false
   251  	}
   252  
   253  	// If a nested move has a dependency, we need to rule out the possibility
   254  	// that this is a move inside a module only changing indexes. If an
   255  	// ancestor module is only changing the index of a nested module, any
   256  	// nested move statements are going to match both the From and To address
   257  	// when the base name is not changing, causing a cycle in the order of
   258  	// operations.
   259  
   260  	// if A is not declared in an ancestor module, then we can't be nested
   261  	// within a module index change.
   262  	if len(a.To.Module()) >= len(b.To.Module()) {
   263  		return true
   264  	}
   265  	// We only want the nested move statement to depend on the outer module
   266  	// move, so we only test this in the reverse direction.
   267  	if a.From.IsModuleReIndex(a.To) {
   268  		return false
   269  	}
   270  
   271  	return true
   272  }
   273  
   274  // MoveResults describes the outcome of an ApplyMoves call.
   275  type MoveResults struct {
   276  	// Changes is a map from the unique keys of the final new resource
   277  	// instance addresses to an object describing what changed.
   278  	//
   279  	// This includes one entry for each resource instance address that was
   280  	// the destination of a move statement. It doesn't include resource
   281  	// instances that were not affected by moves at all, but it does include
   282  	// resource instance addresses that were "blocked" (also recorded in
   283  	// BlockedAddrs) if and only if they were able to move at least
   284  	// partially along a chain before being blocked.
   285  	//
   286  	// In the return value from ApplyMoves, all of the keys are guaranteed to
   287  	// be unique keys derived from addrs.AbsResourceInstance values.
   288  	Changes addrs.Map[addrs.AbsResourceInstance, MoveSuccess]
   289  
   290  	// Blocked is a map from the unique keys of the final new
   291  	// resource instances addresses to information about where they "wanted"
   292  	// to move, but were blocked by a pre-existing object at the same address.
   293  	//
   294  	// "Blocking" can arise in unusual situations where multiple points along
   295  	// a move chain were already bound to objects, and thus only one of them
   296  	// can actually adopt the final position in the chain. It can also
   297  	// occur in other similar situations, such as if a configuration contains
   298  	// a move of an entire module and a move of an individual resource into
   299  	// that module, such that the individual resource would collide with a
   300  	// resource in the whole module that was moved.
   301  	//
   302  	// In the return value from ApplyMoves, all of the keys are guaranteed to
   303  	// be unique keys derived from values of addrs.AbsMoveable types.
   304  	Blocked addrs.Map[addrs.AbsMoveable, MoveBlocked]
   305  }
   306  
   307  func makeMoveResults() MoveResults {
   308  	return MoveResults{
   309  		Changes: addrs.MakeMap[addrs.AbsResourceInstance, MoveSuccess](),
   310  		Blocked: addrs.MakeMap[addrs.AbsMoveable, MoveBlocked](),
   311  	}
   312  }
   313  
   314  type MoveSuccess struct {
   315  	From addrs.AbsResourceInstance
   316  	To   addrs.AbsResourceInstance
   317  }
   318  
   319  type MoveBlocked struct {
   320  	Wanted addrs.AbsMoveable
   321  	Actual addrs.AbsMoveable
   322  }
   323  
   324  // AddrMoved returns true if and only if the given resource instance moved to
   325  // a new address in the ApplyMoves call that the receiver is describing.
   326  //
   327  // If AddrMoved returns true, you can pass the same address to method OldAddr
   328  // to find its original address prior to moving.
   329  func (rs MoveResults) AddrMoved(newAddr addrs.AbsResourceInstance) bool {
   330  	return rs.Changes.Has(newAddr)
   331  }
   332  
   333  // OldAddr returns the old address of the given resource instance address, or
   334  // just returns back the same address if the given instance wasn't affected by
   335  // any move statements.
   336  func (rs MoveResults) OldAddr(newAddr addrs.AbsResourceInstance) addrs.AbsResourceInstance {
   337  	change, ok := rs.Changes.GetOk(newAddr)
   338  	if !ok {
   339  		return newAddr
   340  	}
   341  	return change.From
   342  }