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

     1  package refactoring
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  
     7  	"github.com/hugorut/terraform/src/addrs"
     8  	"github.com/hugorut/terraform/src/dag"
     9  	"github.com/hugorut/terraform/src/logging"
    10  	"github.com/hugorut/terraform/src/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 := MoveResults{
    30  		Changes: make(map[addrs.UniqueKey]MoveSuccess),
    31  		Blocked: make(map[addrs.UniqueKey]MoveBlocked),
    32  	}
    33  
    34  	// The methodology here is to construct a small graph of all of the move
    35  	// statements where the edges represent where a particular statement
    36  	// is either chained from or nested inside the effect of another statement.
    37  	// That then means we can traverse the graph in topological sort order
    38  	// to gradually move objects through potentially multiple moves each.
    39  
    40  	g := buildMoveStatementGraph(stmts)
    41  
    42  	// If there are any cycles in the graph then we'll not take any action
    43  	// at all. The separate validation step should detect this and return
    44  	// an error.
    45  	if len(g.Cycles()) != 0 {
    46  		return ret
    47  	}
    48  
    49  	// The starting nodes are the ones that don't depend on any other nodes.
    50  	startNodes := make(dag.Set, len(stmts))
    51  	for _, v := range g.Vertices() {
    52  		if len(g.DownEdges(v)) == 0 {
    53  			startNodes.Add(v)
    54  		}
    55  	}
    56  
    57  	if startNodes.Len() == 0 {
    58  		log.Println("[TRACE] refactoring.ApplyMoves: No 'moved' statements to consider in this configuration")
    59  		return ret
    60  	}
    61  
    62  	log.Printf("[TRACE] refactoring.ApplyMoves: Processing 'moved' statements in the configuration\n%s", logging.Indent(g.String()))
    63  
    64  	recordOldAddr := func(oldAddr, newAddr addrs.AbsResourceInstance) {
    65  		oldAddrKey := oldAddr.UniqueKey()
    66  		newAddrKey := newAddr.UniqueKey()
    67  		if prevMove, exists := ret.Changes[oldAddrKey]; exists {
    68  			// If the old address was _already_ the result of a move then
    69  			// we'll replace that entry so that our results summarize a chain
    70  			// of moves into a single entry.
    71  			delete(ret.Changes, oldAddrKey)
    72  			oldAddr = prevMove.From
    73  		}
    74  		ret.Changes[newAddrKey] = MoveSuccess{
    75  			From: oldAddr,
    76  			To:   newAddr,
    77  		}
    78  	}
    79  	recordBlockage := func(newAddr, wantedAddr addrs.AbsMoveable) {
    80  		ret.Blocked[newAddr.UniqueKey()] = MoveBlocked{
    81  			Wanted: wantedAddr,
    82  			Actual: newAddr,
    83  		}
    84  	}
    85  
    86  	g.ReverseDepthFirstWalk(startNodes, func(v dag.Vertex, depth int) error {
    87  		stmt := v.(*MoveStatement)
    88  
    89  		for _, ms := range state.Modules {
    90  			modAddr := ms.Addr
    91  			if !stmt.From.SelectsModule(modAddr) {
    92  				continue
    93  			}
    94  
    95  			// We now know that the current module is relevant but what
    96  			// we'll do with it depends on the object kind.
    97  			switch kind := stmt.ObjectKind(); kind {
    98  			case addrs.MoveEndpointModule:
    99  				// For a module endpoint we just try the module address
   100  				// directly.
   101  				if newAddr, matches := modAddr.MoveDestination(stmt.From, stmt.To); matches {
   102  					log.Printf("[TRACE] refactoring.ApplyMoves: %s has moved to %s", modAddr, newAddr)
   103  
   104  					// If we already have a module at the new address then
   105  					// we'll skip this move and let the existing object take
   106  					// priority.
   107  					if ms := state.Module(newAddr); ms != nil {
   108  						log.Printf("[WARN] Skipped moving %s to %s, because there's already another module instance at the destination", modAddr, newAddr)
   109  						recordBlockage(modAddr, newAddr)
   110  						continue
   111  					}
   112  
   113  					// We need to visit all of the resource instances in the
   114  					// module and record them individually as results.
   115  					for _, rs := range ms.Resources {
   116  						relAddr := rs.Addr.Resource
   117  						for key := range rs.Instances {
   118  							oldInst := relAddr.Instance(key).Absolute(modAddr)
   119  							newInst := relAddr.Instance(key).Absolute(newAddr)
   120  							recordOldAddr(oldInst, newInst)
   121  						}
   122  					}
   123  
   124  					state.MoveModuleInstance(modAddr, newAddr)
   125  					continue
   126  				}
   127  			case addrs.MoveEndpointResource:
   128  				// For a resource endpoint we need to search each of the
   129  				// resources and resource instances in the module.
   130  				for _, rs := range ms.Resources {
   131  					rAddr := rs.Addr
   132  					if newAddr, matches := rAddr.MoveDestination(stmt.From, stmt.To); matches {
   133  						log.Printf("[TRACE] refactoring.ApplyMoves: resource %s has moved to %s", rAddr, newAddr)
   134  
   135  						// If we already have a resource at the new address then
   136  						// we'll skip this move and let the existing object take
   137  						// priority.
   138  						if rs := state.Resource(newAddr); rs != nil {
   139  							log.Printf("[WARN] Skipped moving %s to %s, because there's already another resource at the destination", rAddr, newAddr)
   140  							recordBlockage(rAddr, newAddr)
   141  							continue
   142  						}
   143  
   144  						for key := range rs.Instances {
   145  							oldInst := rAddr.Instance(key)
   146  							newInst := newAddr.Instance(key)
   147  							recordOldAddr(oldInst, newInst)
   148  						}
   149  						state.MoveAbsResource(rAddr, newAddr)
   150  						continue
   151  					}
   152  					for key := range rs.Instances {
   153  						iAddr := rAddr.Instance(key)
   154  						if newAddr, matches := iAddr.MoveDestination(stmt.From, stmt.To); matches {
   155  							log.Printf("[TRACE] refactoring.ApplyMoves: resource instance %s has moved to %s", iAddr, newAddr)
   156  
   157  							// If we already have a resource instance at the new
   158  							// address then we'll skip this move and let the existing
   159  							// object take priority.
   160  							if is := state.ResourceInstance(newAddr); is != nil {
   161  								log.Printf("[WARN] Skipped moving %s to %s, because there's already another resource instance at the destination", iAddr, newAddr)
   162  								recordBlockage(iAddr, newAddr)
   163  								continue
   164  							}
   165  
   166  							recordOldAddr(iAddr, newAddr)
   167  
   168  							state.MoveAbsResourceInstance(iAddr, newAddr)
   169  							continue
   170  						}
   171  					}
   172  				}
   173  			default:
   174  				panic(fmt.Sprintf("unhandled move object kind %s", kind))
   175  			}
   176  		}
   177  
   178  		return nil
   179  	})
   180  
   181  	return ret
   182  }
   183  
   184  // buildMoveStatementGraph constructs a dependency graph of the given move
   185  // statements, where the nodes are all pointers to statements in the given
   186  // slice and the edges represent either chaining or nesting relationships.
   187  //
   188  // buildMoveStatementGraph doesn't do any validation of the graph, so it
   189  // may contain cycles and other sorts of invalidity.
   190  func buildMoveStatementGraph(stmts []MoveStatement) *dag.AcyclicGraph {
   191  	g := &dag.AcyclicGraph{}
   192  	for i := range stmts {
   193  		// The graph nodes are pointers to the actual statements directly.
   194  		g.Add(&stmts[i])
   195  	}
   196  
   197  	// Now we'll add the edges representing chaining and nesting relationships.
   198  	// We assume that a reasonable configuration will have at most tens of
   199  	// move statements and thus this N*M algorithm is acceptable.
   200  	for dependerI := range stmts {
   201  		depender := &stmts[dependerI]
   202  		for dependeeI := range stmts {
   203  			if dependerI == dependeeI {
   204  				// skip comparing the statement to itself
   205  				continue
   206  			}
   207  			dependee := &stmts[dependeeI]
   208  
   209  			if statementDependsOn(depender, dependee) {
   210  				g.Connect(dag.BasicEdge(depender, dependee))
   211  			}
   212  		}
   213  	}
   214  
   215  	return g
   216  }
   217  
   218  // statementDependsOn returns true if statement a depends on statement b;
   219  // i.e. statement b must be executed before statement a.
   220  func statementDependsOn(a, b *MoveStatement) bool {
   221  	// chain-able moves are simple, as on the destination of one move could be
   222  	// equal to the source of another.
   223  	if a.From.CanChainFrom(b.To) {
   224  		return true
   225  	}
   226  
   227  	// Statement nesting in more complex, as we have 8 possible combinations to
   228  	// assess. Here we list all combinations, along with the statement which
   229  	// must be executed first when one address is nested within another.
   230  	// A.From  IsNestedWithin  B.From => A
   231  	// A.From  IsNestedWithin  B.To   => B
   232  	// A.To    IsNestedWithin  B.From => A
   233  	// A.To    IsNestedWithin  B.To   => B
   234  	// B.From  IsNestedWithin  A.From => B
   235  	// B.From  IsNestedWithin  A.To   => A
   236  	// B.To    IsNestedWithin  A.From => B
   237  	// B.To    IsNestedWithin  A.To   => A
   238  	//
   239  	// Since we are only interested in checking if A depends on B, we only need
   240  	// to check the 4 possibilities above which result in B being executed
   241  	// first.
   242  	return a.From.NestedWithin(b.To) ||
   243  		a.To.NestedWithin(b.To) ||
   244  		b.From.NestedWithin(a.From) ||
   245  		b.To.NestedWithin(a.From)
   246  }
   247  
   248  // MoveResults describes the outcome of an ApplyMoves call.
   249  type MoveResults struct {
   250  	// Changes is a map from the unique keys of the final new resource
   251  	// instance addresses to an object describing what changed.
   252  	//
   253  	// This includes one entry for each resource instance address that was
   254  	// the destination of a move statement. It doesn't include resource
   255  	// instances that were not affected by moves at all, but it does include
   256  	// resource instance addresses that were "blocked" (also recorded in
   257  	// BlockedAddrs) if and only if they were able to move at least
   258  	// partially along a chain before being blocked.
   259  	//
   260  	// In the return value from ApplyMoves, all of the keys are guaranteed to
   261  	// be unique keys derived from addrs.AbsResourceInstance values.
   262  	Changes map[addrs.UniqueKey]MoveSuccess
   263  
   264  	// Blocked is a map from the unique keys of the final new
   265  	// resource instances addresses to information about where they "wanted"
   266  	// to move, but were blocked by a pre-existing object at the same address.
   267  	//
   268  	// "Blocking" can arise in unusual situations where multiple points along
   269  	// a move chain were already bound to objects, and thus only one of them
   270  	// can actually adopt the final position in the chain. It can also
   271  	// occur in other similar situations, such as if a configuration contains
   272  	// a move of an entire module and a move of an individual resource into
   273  	// that module, such that the individual resource would collide with a
   274  	// resource in the whole module that was moved.
   275  	//
   276  	// In the return value from ApplyMoves, all of the keys are guaranteed to
   277  	// be unique keys derived from values of addrs.AbsMoveable types.
   278  	Blocked map[addrs.UniqueKey]MoveBlocked
   279  }
   280  
   281  type MoveSuccess struct {
   282  	From addrs.AbsResourceInstance
   283  	To   addrs.AbsResourceInstance
   284  }
   285  
   286  type MoveBlocked struct {
   287  	Wanted addrs.AbsMoveable
   288  	Actual addrs.AbsMoveable
   289  }
   290  
   291  // AddrMoved returns true if and only if the given resource instance moved to
   292  // a new address in the ApplyMoves call that the receiver is describing.
   293  //
   294  // If AddrMoved returns true, you can pass the same address to method OldAddr
   295  // to find its original address prior to moving.
   296  func (rs MoveResults) AddrMoved(newAddr addrs.AbsResourceInstance) bool {
   297  	_, ok := rs.Changes[newAddr.UniqueKey()]
   298  	return ok
   299  }
   300  
   301  // OldAddr returns the old address of the given resource instance address, or
   302  // just returns back the same address if the given instance wasn't affected by
   303  // any move statements.
   304  func (rs MoveResults) OldAddr(newAddr addrs.AbsResourceInstance) addrs.AbsResourceInstance {
   305  	change, ok := rs.Changes[newAddr.UniqueKey()]
   306  	if !ok {
   307  		return newAddr
   308  	}
   309  	return change.From
   310  }