github.com/pulumi/terraform@v1.4.0/pkg/addrs/move_endpoint.go (about)

     1  package addrs
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/hashicorp/hcl/v2"
     7  	"github.com/pulumi/terraform/pkg/tfdiags"
     8  )
     9  
    10  // MoveEndpoint is to AbsMoveable and ConfigMoveable what Target is to
    11  // Targetable: a wrapping struct that captures the result of decoding an HCL
    12  // traversal representing a relative path from the current module to
    13  // a moveable object.
    14  //
    15  // Its name reflects that its primary purpose is for the "from" and "to"
    16  // addresses in a "moved" statement in the configuration, but it's also
    17  // valid to use MoveEndpoint for other similar mechanisms that give
    18  // Terraform hints about historical configuration changes that might
    19  // prompt creating a different plan than Terraform would by default.
    20  //
    21  // To obtain a full address from a MoveEndpoint you must use
    22  // either the package function UnifyMoveEndpoints (to get an AbsMoveable) or
    23  // the method ConfigMoveable (to get a ConfigMoveable).
    24  type MoveEndpoint struct {
    25  	// SourceRange is the location of the physical endpoint address
    26  	// in configuration, if this MoveEndpoint was decoded from a
    27  	// configuration expresson.
    28  	SourceRange tfdiags.SourceRange
    29  
    30  	// Internally we (ab)use AbsMoveable as the representation of our
    31  	// relative address, even though everywhere else in Terraform
    32  	// AbsMoveable always represents a fully-absolute address.
    33  	// In practice, due to the implementation of ParseMoveEndpoint,
    34  	// this is always either a ModuleInstance or an AbsResourceInstance,
    35  	// and we only consider the possibility of interpreting it as
    36  	// a AbsModuleCall or an AbsResource in UnifyMoveEndpoints.
    37  	// This is intentionally unexported to encapsulate this unusual
    38  	// meaning of AbsMoveable.
    39  	relSubject AbsMoveable
    40  }
    41  
    42  func (e *MoveEndpoint) ObjectKind() MoveEndpointKind {
    43  	return absMoveableEndpointKind(e.relSubject)
    44  }
    45  
    46  func (e *MoveEndpoint) String() string {
    47  	// Our internal pseudo-AbsMoveable representing the relative
    48  	// address (either ModuleInstance or AbsResourceInstance) is
    49  	// a good enough proxy for the relative move endpoint address
    50  	// serialization.
    51  	return e.relSubject.String()
    52  }
    53  
    54  func (e *MoveEndpoint) Equal(other *MoveEndpoint) bool {
    55  	switch {
    56  	case (e == nil) != (other == nil):
    57  		return false
    58  	case e == nil:
    59  		return true
    60  	default:
    61  		// Since we only use ModuleInstance and AbsResourceInstance in our
    62  		// string representation, we have no ambiguity between address types
    63  		// and can safely just compare the string representations to
    64  		// compare the relSubject values.
    65  		return e.String() == other.String() && e.SourceRange == other.SourceRange
    66  	}
    67  }
    68  
    69  // MightUnifyWith returns true if it is possible that a later call to
    70  // UnifyMoveEndpoints might succeed if given the reciever and the other
    71  // given endpoint.
    72  //
    73  // This is intended for early static validation of obviously-wrong situations,
    74  // although there are still various semantic errors that this cannot catch.
    75  func (e *MoveEndpoint) MightUnifyWith(other *MoveEndpoint) bool {
    76  	// For our purposes here we'll just do a unify without a base module
    77  	// address, because the rules for whether unify can succeed depend
    78  	// only on the relative part of the addresses, not on which module
    79  	// they were declared in.
    80  	from, to := UnifyMoveEndpoints(RootModule, e, other)
    81  	return from != nil && to != nil
    82  }
    83  
    84  // ConfigMovable transforms the reciever into a ConfigMovable by resolving it
    85  // relative to the given base module, which should be the module where
    86  // the MoveEndpoint expression was found.
    87  //
    88  // The result is useful for finding the target object in the configuration,
    89  // but it's not sufficient for fully interpreting a move statement because
    90  // it lacks the specific module and resource instance keys.
    91  func (e *MoveEndpoint) ConfigMoveable(baseModule Module) ConfigMoveable {
    92  	addr := e.relSubject
    93  	switch addr := addr.(type) {
    94  	case ModuleInstance:
    95  		ret := make(Module, 0, len(baseModule)+len(addr))
    96  		ret = append(ret, baseModule...)
    97  		ret = append(ret, addr.Module()...)
    98  		return ret
    99  	case AbsResourceInstance:
   100  		moduleAddr := make(Module, 0, len(baseModule)+len(addr.Module))
   101  		moduleAddr = append(moduleAddr, baseModule...)
   102  		moduleAddr = append(moduleAddr, addr.Module.Module()...)
   103  		return ConfigResource{
   104  			Module:   moduleAddr,
   105  			Resource: addr.Resource.Resource,
   106  		}
   107  	default:
   108  		// The above should be exhaustive for all of the types
   109  		// that ParseMoveEndpoint produces as our intermediate
   110  		// address representation.
   111  		panic(fmt.Sprintf("unsupported address type %T", addr))
   112  	}
   113  
   114  }
   115  
   116  // ParseMoveEndpoint attempts to interpret the given traversal as a
   117  // "move endpoint" address, which is a relative path from the module containing
   118  // the traversal to a movable object in either the same module or in some
   119  // child module.
   120  //
   121  // This deals only with the syntactic element of a move endpoint expression
   122  // in configuration. Before the result will be useful you'll need to combine
   123  // it with the address of the module where it was declared in order to get
   124  // an absolute address relative to the root module.
   125  func ParseMoveEndpoint(traversal hcl.Traversal) (*MoveEndpoint, tfdiags.Diagnostics) {
   126  	path, remain, diags := parseModuleInstancePrefix(traversal)
   127  	if diags.HasErrors() {
   128  		return nil, diags
   129  	}
   130  
   131  	rng := tfdiags.SourceRangeFromHCL(traversal.SourceRange())
   132  
   133  	if len(remain) == 0 {
   134  		return &MoveEndpoint{
   135  			relSubject:  path,
   136  			SourceRange: rng,
   137  		}, diags
   138  	}
   139  
   140  	riAddr, moreDiags := parseResourceInstanceUnderModule(path, remain)
   141  	diags = diags.Append(moreDiags)
   142  	if diags.HasErrors() {
   143  		return nil, diags
   144  	}
   145  
   146  	return &MoveEndpoint{
   147  		relSubject:  riAddr,
   148  		SourceRange: rng,
   149  	}, diags
   150  }
   151  
   152  // UnifyMoveEndpoints takes a pair of MoveEndpoint objects representing the
   153  // "from" and "to" addresses in a moved block, and returns a pair of
   154  // MoveEndpointInModule addresses guaranteed to be of the same dynamic type
   155  // that represent what the two MoveEndpoint addresses refer to.
   156  //
   157  // moduleAddr must be the address of the module where the move was declared.
   158  //
   159  // This function deals both with the conversion from relative to absolute
   160  // addresses and with resolving the ambiguity between no-key instance
   161  // addresses and whole-object addresses, returning the least specific
   162  // address type possible.
   163  //
   164  // Not all combinations of addresses are unifyable: the two addresses must
   165  // either both include resources or both just be modules. If the two
   166  // given addresses are incompatible then UnifyMoveEndpoints returns (nil, nil),
   167  // in which case the caller should typically report an error to the user
   168  // stating the unification constraints.
   169  func UnifyMoveEndpoints(moduleAddr Module, relFrom, relTo *MoveEndpoint) (modFrom, modTo *MoveEndpointInModule) {
   170  
   171  	// First we'll make a decision about which address type we're
   172  	// ultimately trying to unify to. For our internal purposes
   173  	// here we're going to borrow TargetableAddrType just as a
   174  	// convenient way to talk about our address types, even though
   175  	// targetable address types are not 100% aligned with moveable
   176  	// address types.
   177  	fromType := relFrom.internalAddrType()
   178  	toType := relTo.internalAddrType()
   179  	var wantType TargetableAddrType
   180  
   181  	// Our goal here is to choose the whole-resource or whole-module-call
   182  	// addresses if both agree on it, but to use specific instance addresses
   183  	// otherwise. This is a somewhat-arbitrary way to resolve syntactic
   184  	// ambiguity between the two situations which allows both for renaming
   185  	// whole resources and for switching from a single-instance object to
   186  	// a multi-instance object.
   187  	switch {
   188  	case fromType == AbsResourceInstanceAddrType || toType == AbsResourceInstanceAddrType:
   189  		wantType = AbsResourceInstanceAddrType
   190  	case fromType == AbsResourceAddrType || toType == AbsResourceAddrType:
   191  		wantType = AbsResourceAddrType
   192  	case fromType == ModuleInstanceAddrType || toType == ModuleInstanceAddrType:
   193  		wantType = ModuleInstanceAddrType
   194  	case fromType == ModuleAddrType || toType == ModuleAddrType:
   195  		// NOTE: We're fudging a little here and using
   196  		// ModuleAddrType to represent AbsModuleCall rather
   197  		// than Module.
   198  		wantType = ModuleAddrType
   199  	default:
   200  		panic("unhandled move address types")
   201  	}
   202  
   203  	modFrom = relFrom.prepareMoveEndpointInModule(moduleAddr, wantType)
   204  	modTo = relTo.prepareMoveEndpointInModule(moduleAddr, wantType)
   205  	if modFrom == nil || modTo == nil {
   206  		// if either of them failed then they both failed, to make the
   207  		// caller's life a little easier.
   208  		return nil, nil
   209  	}
   210  	return modFrom, modTo
   211  }
   212  
   213  func (e *MoveEndpoint) prepareMoveEndpointInModule(moduleAddr Module, wantType TargetableAddrType) *MoveEndpointInModule {
   214  	// relAddr can only be either AbsResourceInstance or ModuleInstance, the
   215  	// internal intermediate representation produced by ParseMoveEndpoint.
   216  	relAddr := e.relSubject
   217  
   218  	switch relAddr := relAddr.(type) {
   219  	case ModuleInstance:
   220  		switch wantType {
   221  		case ModuleInstanceAddrType:
   222  			// Since our internal representation is already a module instance,
   223  			// we can just rewrap this one.
   224  			return &MoveEndpointInModule{
   225  				SourceRange: e.SourceRange,
   226  				module:      moduleAddr,
   227  				relSubject:  relAddr,
   228  			}
   229  		case ModuleAddrType:
   230  			// NOTE: We're fudging a little here and using
   231  			// ModuleAddrType to represent AbsModuleCall rather
   232  			// than Module.
   233  			callerAddr, callAddr := relAddr.Call()
   234  			absCallAddr := AbsModuleCall{
   235  				Module: callerAddr,
   236  				Call:   callAddr,
   237  			}
   238  			return &MoveEndpointInModule{
   239  				SourceRange: e.SourceRange,
   240  				module:      moduleAddr,
   241  				relSubject:  absCallAddr,
   242  			}
   243  		default:
   244  			return nil // can't make any other types from a ModuleInstance
   245  		}
   246  	case AbsResourceInstance:
   247  		switch wantType {
   248  		case AbsResourceInstanceAddrType:
   249  			return &MoveEndpointInModule{
   250  				SourceRange: e.SourceRange,
   251  				module:      moduleAddr,
   252  				relSubject:  relAddr,
   253  			}
   254  		case AbsResourceAddrType:
   255  			return &MoveEndpointInModule{
   256  				SourceRange: e.SourceRange,
   257  				module:      moduleAddr,
   258  				relSubject:  relAddr.ContainingResource(),
   259  			}
   260  		default:
   261  			return nil // can't make any other types from an AbsResourceInstance
   262  		}
   263  	default:
   264  		panic(fmt.Sprintf("unhandled address type %T", relAddr))
   265  	}
   266  }
   267  
   268  // internalAddrType helps facilitate our slight abuse of TargetableAddrType
   269  // as a way to talk about our different possible result address types in
   270  // UnifyMoveEndpoints.
   271  //
   272  // It's not really correct to use TargetableAddrType in this way, because
   273  // it's for Targetable rather than for AbsMoveable, but as long as the two
   274  // remain aligned enough it saves introducing yet another enumeration with
   275  // similar members that would be for internal use only anyway.
   276  func (e *MoveEndpoint) internalAddrType() TargetableAddrType {
   277  	switch addr := e.relSubject.(type) {
   278  	case ModuleInstance:
   279  		if !addr.IsRoot() && addr[len(addr)-1].InstanceKey == NoKey {
   280  			// NOTE: We're fudging a little here and using
   281  			// ModuleAddrType to represent AbsModuleCall rather
   282  			// than Module.
   283  			return ModuleAddrType
   284  		}
   285  		return ModuleInstanceAddrType
   286  	case AbsResourceInstance:
   287  		if addr.Resource.Key == NoKey {
   288  			return AbsResourceAddrType
   289  		}
   290  		return AbsResourceInstanceAddrType
   291  	default:
   292  		// The above should cover all of the address types produced
   293  		// by ParseMoveEndpoint.
   294  		panic(fmt.Sprintf("unsupported address type %T", addr))
   295  	}
   296  }