mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-30 10:47:14 -06:00
c937c06a03
Due to how deeply the configuration types go into Terraform Core, there isn't a great way to switch out to HCL2 gradually. As a consequence, this huge commit gets us from the old state to a _compilable_ new state, but does not yet attempt to fix any tests and has a number of known missing parts and bugs. We will continue to iterate on this in forthcoming commits, heading back towards passing tests and making Terraform fully-functional again. The three main goals here are: - Use the configuration models from the "configs" package instead of the older models in the "config" package, which is now deprecated and preserved only to help us write our migration tool. - Do expression inspection and evaluation using the functionality of the new "lang" package, instead of the Interpolator type and related functionality in the main "terraform" package. - Represent addresses of various objects using types in the addrs package, rather than hand-constructed strings. This is not critical to support the above, but was a big help during the implementation of these other points since it made it much more explicit what kind of address is expected in each context. Since our new packages are built to accommodate some future planned features that are not yet implemented (e.g. the "for_each" argument on resources, "count"/"for_each" on modules), and since there's still a fair amount of functionality still using old-style APIs, there is a moderate amount of shimming here to connect new assumptions with old, hopefully in a way that makes it easier to find and eliminate these shims later. I apologize in advance to the person who inevitably just found this huge commit while spelunking through the commit history.
375 lines
11 KiB
Go
375 lines
11 KiB
Go
package terraform
|
|
|
|
import "fmt"
|
|
|
|
// Add adds the item in the state at the given address.
|
|
//
|
|
// The item can be a ModuleState, ResourceState, or InstanceState. Depending
|
|
// on the item type, the address may or may not be valid. For example, a
|
|
// module cannot be moved to a resource address, however a resource can be
|
|
// moved to a module address (it retains the same name, under that resource).
|
|
//
|
|
// The item can also be a []*ModuleState, which is the case for nested
|
|
// modules. In this case, Add will expect the zero-index to be the top-most
|
|
// module to add and will only nest children from there. For semantics, this
|
|
// is equivalent to module => module.
|
|
//
|
|
// The full semantics of Add:
|
|
//
|
|
// ┌───────────────────┬───────────────────┬───────────────────┐
|
|
// │ Module Address │ Resource Address │ Instance Address │
|
|
// ┌─────────────────┼───────────────────┼───────────────────┼───────────────────┤
|
|
// │ ModuleState │ ✓ │ x │ x │
|
|
// ├─────────────────┼───────────────────┼───────────────────┼───────────────────┤
|
|
// │ ResourceState │ ✓ │ ✓ │ maybe* │
|
|
// ├─────────────────┼───────────────────┼───────────────────┼───────────────────┤
|
|
// │ Instance State │ ✓ │ ✓ │ ✓ │
|
|
// └─────────────────┴───────────────────┴───────────────────┴───────────────────┘
|
|
//
|
|
// *maybe - Resources can be added at an instance address only if the resource
|
|
// represents a single instance (primary). Example:
|
|
// "aws_instance.foo" can be moved to "aws_instance.bar.tainted"
|
|
//
|
|
func (s *State) Add(fromAddrRaw string, toAddrRaw string, raw interface{}) error {
|
|
// Parse the address
|
|
|
|
toAddr, err := ParseResourceAddress(toAddrRaw)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Parse the from address
|
|
fromAddr, err := ParseResourceAddress(fromAddrRaw)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Determine the types
|
|
from := detectValueAddLoc(raw)
|
|
to := detectAddrAddLoc(toAddr)
|
|
|
|
// Find the function to do this
|
|
fromMap, ok := stateAddFuncs[from]
|
|
if !ok {
|
|
return fmt.Errorf("invalid source to add to state: %T", raw)
|
|
}
|
|
f, ok := fromMap[to]
|
|
if !ok {
|
|
return fmt.Errorf("invalid destination: %s (%d)", toAddr, to)
|
|
}
|
|
|
|
// Call the migrator
|
|
if err := f(s, fromAddr, toAddr, raw); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Prune the state
|
|
s.prune()
|
|
return nil
|
|
}
|
|
|
|
func stateAddFunc_Module_Module(s *State, fromAddr, addr *ResourceAddress, raw interface{}) error {
|
|
// raw can be either *ModuleState or []*ModuleState. The former means
|
|
// we're moving just one module. The latter means we're moving a module
|
|
// and children.
|
|
root := raw
|
|
var rest []*ModuleState
|
|
if list, ok := raw.([]*ModuleState); ok {
|
|
// We need at least one item
|
|
if len(list) == 0 {
|
|
return fmt.Errorf("module move with no value to: %s", addr)
|
|
}
|
|
|
|
// The first item is always the root
|
|
root = list[0]
|
|
if len(list) > 1 {
|
|
rest = list[1:]
|
|
}
|
|
}
|
|
|
|
// Get the actual module state
|
|
src := root.(*ModuleState).deepcopy()
|
|
|
|
// If the target module exists, it is an error
|
|
path := normalizeModulePath(addr.Path)
|
|
if s.ModuleByPath(path) != nil {
|
|
return fmt.Errorf("module target is not empty: %s", addr)
|
|
}
|
|
|
|
// Create it and copy our outputs and dependencies
|
|
mod := s.AddModule(path)
|
|
mod.Outputs = src.Outputs
|
|
mod.Dependencies = src.Dependencies
|
|
|
|
// Go through the resources perform an add for each of those
|
|
for k, v := range src.Resources {
|
|
resourceKey, err := ParseResourceStateKey(k)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Update the resource address for this
|
|
addrCopy := *addr
|
|
addrCopy.Type = resourceKey.Type
|
|
addrCopy.Name = resourceKey.Name
|
|
addrCopy.Index = resourceKey.Index
|
|
addrCopy.Mode = resourceKey.Mode
|
|
|
|
// Perform an add
|
|
if err := s.Add(fromAddr.String(), addrCopy.String(), v); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Add all the children if we have them
|
|
for _, item := range rest {
|
|
// If item isn't a descendent of our root, then ignore it
|
|
if !src.IsDescendent(item) {
|
|
continue
|
|
}
|
|
|
|
// It is! Strip the leading prefix and attach that to our address
|
|
extra := item.Path[len(src.Path):]
|
|
addrCopy := addr.Copy()
|
|
addrCopy.Path = append(addrCopy.Path, extra...)
|
|
|
|
// Add it
|
|
s.Add(fromAddr.String(), addrCopy.String(), item)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func stateAddFunc_Resource_Module(
|
|
s *State, from, to *ResourceAddress, raw interface{}) error {
|
|
// Build the more specific to addr
|
|
addr := *to
|
|
addr.Type = from.Type
|
|
addr.Name = from.Name
|
|
|
|
return s.Add(from.String(), addr.String(), raw)
|
|
}
|
|
|
|
func stateAddFunc_Resource_Resource(s *State, fromAddr, addr *ResourceAddress, raw interface{}) error {
|
|
// raw can be either *ResourceState or []*ResourceState. The former means
|
|
// we're moving just one resource. The latter means we're moving a count
|
|
// of resources.
|
|
if list, ok := raw.([]*ResourceState); ok {
|
|
// We need at least one item
|
|
if len(list) == 0 {
|
|
return fmt.Errorf("resource move with no value to: %s", addr)
|
|
}
|
|
|
|
// If there is an index, this is an error since we can't assign
|
|
// a set of resources to a single index
|
|
if addr.Index >= 0 && len(list) > 1 {
|
|
return fmt.Errorf(
|
|
"multiple resources can't be moved to a single index: "+
|
|
"%s => %s", fromAddr, addr)
|
|
}
|
|
|
|
// Add each with a specific index
|
|
for i, rs := range list {
|
|
addrCopy := addr.Copy()
|
|
addrCopy.Index = i
|
|
|
|
if err := s.Add(fromAddr.String(), addrCopy.String(), rs); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
src := raw.(*ResourceState).deepcopy()
|
|
|
|
// Initialize the resource
|
|
resourceRaw, exists := stateAddInitAddr(s, addr)
|
|
if exists {
|
|
return fmt.Errorf("resource exists and not empty: %s", addr)
|
|
}
|
|
resource := resourceRaw.(*ResourceState)
|
|
resource.Type = src.Type
|
|
resource.Dependencies = src.Dependencies
|
|
resource.Provider = src.Provider
|
|
|
|
// Move the primary
|
|
if src.Primary != nil {
|
|
addrCopy := *addr
|
|
addrCopy.InstanceType = TypePrimary
|
|
addrCopy.InstanceTypeSet = true
|
|
if err := s.Add(fromAddr.String(), addrCopy.String(), src.Primary); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Move all deposed
|
|
if len(src.Deposed) > 0 {
|
|
resource.Deposed = src.Deposed
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func stateAddFunc_Instance_Instance(s *State, fromAddr, addr *ResourceAddress, raw interface{}) error {
|
|
src := raw.(*InstanceState).DeepCopy()
|
|
|
|
// Create the instance
|
|
instanceRaw, _ := stateAddInitAddr(s, addr)
|
|
instance := instanceRaw.(*InstanceState)
|
|
|
|
// Set it
|
|
instance.Set(src)
|
|
|
|
return nil
|
|
}
|
|
|
|
func stateAddFunc_Instance_Module(
|
|
s *State, from, to *ResourceAddress, raw interface{}) error {
|
|
addr := *to
|
|
addr.Type = from.Type
|
|
addr.Name = from.Name
|
|
|
|
return s.Add(from.String(), addr.String(), raw)
|
|
}
|
|
|
|
func stateAddFunc_Instance_Resource(
|
|
s *State, from, to *ResourceAddress, raw interface{}) error {
|
|
addr := *to
|
|
addr.InstanceType = TypePrimary
|
|
addr.InstanceTypeSet = true
|
|
|
|
return s.Add(from.String(), addr.String(), raw)
|
|
}
|
|
|
|
// stateAddFunc is the type of function for adding an item to a state
|
|
type stateAddFunc func(s *State, from, to *ResourceAddress, item interface{}) error
|
|
|
|
// stateAddFuncs has the full matrix mapping of the state adders.
|
|
var stateAddFuncs map[stateAddLoc]map[stateAddLoc]stateAddFunc
|
|
|
|
func init() {
|
|
stateAddFuncs = map[stateAddLoc]map[stateAddLoc]stateAddFunc{
|
|
stateAddModule: {
|
|
stateAddModule: stateAddFunc_Module_Module,
|
|
},
|
|
stateAddResource: {
|
|
stateAddModule: stateAddFunc_Resource_Module,
|
|
stateAddResource: stateAddFunc_Resource_Resource,
|
|
},
|
|
stateAddInstance: {
|
|
stateAddInstance: stateAddFunc_Instance_Instance,
|
|
stateAddModule: stateAddFunc_Instance_Module,
|
|
stateAddResource: stateAddFunc_Instance_Resource,
|
|
},
|
|
}
|
|
}
|
|
|
|
// stateAddLoc is an enum to represent the location where state is being
|
|
// moved from/to. We use this for quick lookups in a function map.
|
|
type stateAddLoc uint
|
|
|
|
const (
|
|
stateAddInvalid stateAddLoc = iota
|
|
stateAddModule
|
|
stateAddResource
|
|
stateAddInstance
|
|
)
|
|
|
|
// detectAddrAddLoc detects the state type for the given address. This
|
|
// function is specifically not unit tested since we consider the State.Add
|
|
// functionality to be comprehensive enough to cover this.
|
|
func detectAddrAddLoc(addr *ResourceAddress) stateAddLoc {
|
|
if addr.Name == "" {
|
|
return stateAddModule
|
|
}
|
|
|
|
if !addr.InstanceTypeSet {
|
|
return stateAddResource
|
|
}
|
|
|
|
return stateAddInstance
|
|
}
|
|
|
|
// detectValueAddLoc determines the stateAddLoc value from the raw value
|
|
// that is some State structure.
|
|
func detectValueAddLoc(raw interface{}) stateAddLoc {
|
|
switch raw.(type) {
|
|
case *ModuleState:
|
|
return stateAddModule
|
|
case []*ModuleState:
|
|
return stateAddModule
|
|
case *ResourceState:
|
|
return stateAddResource
|
|
case []*ResourceState:
|
|
return stateAddResource
|
|
case *InstanceState:
|
|
return stateAddInstance
|
|
default:
|
|
return stateAddInvalid
|
|
}
|
|
}
|
|
|
|
// stateAddInitAddr takes a ResourceAddress and creates the non-existing
|
|
// resources up to that point, returning the empty (or existing) interface
|
|
// at that address.
|
|
func stateAddInitAddr(s *State, addr *ResourceAddress) (interface{}, bool) {
|
|
addType := detectAddrAddLoc(addr)
|
|
|
|
// Get the module
|
|
path := normalizeModulePath(addr.Path)
|
|
exists := true
|
|
mod := s.ModuleByPath(path)
|
|
if mod == nil {
|
|
mod = s.AddModule(path)
|
|
exists = false
|
|
}
|
|
if addType == stateAddModule {
|
|
return mod, exists
|
|
}
|
|
|
|
// Add the resource
|
|
resourceKey := (&ResourceStateKey{
|
|
Name: addr.Name,
|
|
Type: addr.Type,
|
|
Index: addr.Index,
|
|
Mode: addr.Mode,
|
|
}).String()
|
|
exists = true
|
|
resource, ok := mod.Resources[resourceKey]
|
|
if !ok {
|
|
resource = &ResourceState{Type: addr.Type}
|
|
resource.init()
|
|
mod.Resources[resourceKey] = resource
|
|
exists = false
|
|
}
|
|
if addType == stateAddResource {
|
|
return resource, exists
|
|
}
|
|
|
|
// Get the instance
|
|
exists = true
|
|
instance := &InstanceState{}
|
|
switch addr.InstanceType {
|
|
case TypePrimary, TypeTainted:
|
|
if v := resource.Primary; v != nil {
|
|
instance = resource.Primary
|
|
} else {
|
|
exists = false
|
|
}
|
|
case TypeDeposed:
|
|
idx := addr.Index
|
|
if addr.Index < 0 {
|
|
idx = 0
|
|
}
|
|
if len(resource.Deposed) > idx {
|
|
instance = resource.Deposed[idx]
|
|
} else {
|
|
resource.Deposed = append(resource.Deposed, instance)
|
|
exists = false
|
|
}
|
|
}
|
|
|
|
return instance, exists
|
|
}
|