opentofu/terraform/state_add.go
Martin Atkins c937c06a03 terraform: ugly huge change to weave in new HCL2-oriented types
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.
2018-10-16 18:46:46 -07:00

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
}