mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-30 10:47:14 -06:00
97bb7cb65c
Allow module variables to fail interpolation during input. This is OK since they will be verified again during Plan. Because Input happens before Refresh, module variable interpolation can fail when referencing values that aren't yet in the state, but are expected after Refresh.
327 lines
8.2 KiB
Go
327 lines
8.2 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
)
|
|
|
|
// EvalReadState is an EvalNode implementation that reads the
|
|
// primary InstanceState for a specific resource out of the state.
|
|
type EvalReadState struct {
|
|
Name string
|
|
Output **InstanceState
|
|
}
|
|
|
|
func (n *EvalReadState) Eval(ctx EvalContext) (interface{}, error) {
|
|
return readInstanceFromState(ctx, n.Name, n.Output, func(rs *ResourceState) (*InstanceState, error) {
|
|
return rs.Primary, nil
|
|
})
|
|
}
|
|
|
|
// EvalReadStateDeposed is an EvalNode implementation that reads the
|
|
// deposed InstanceState for a specific resource out of the state
|
|
type EvalReadStateDeposed struct {
|
|
Name string
|
|
Output **InstanceState
|
|
// Index indicates which instance in the Deposed list to target, or -1 for
|
|
// the last item.
|
|
Index int
|
|
}
|
|
|
|
func (n *EvalReadStateDeposed) Eval(ctx EvalContext) (interface{}, error) {
|
|
return readInstanceFromState(ctx, n.Name, n.Output, func(rs *ResourceState) (*InstanceState, error) {
|
|
// Get the index. If it is negative, then we get the last one
|
|
idx := n.Index
|
|
if idx < 0 {
|
|
idx = len(rs.Deposed) - 1
|
|
}
|
|
if idx >= 0 && idx < len(rs.Deposed) {
|
|
return rs.Deposed[idx], nil
|
|
} else {
|
|
return nil, fmt.Errorf("bad deposed index: %d, for resource: %#v", idx, rs)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Does the bulk of the work for the various flavors of ReadState eval nodes.
|
|
// Each node just provides a reader function to get from the ResourceState to the
|
|
// InstanceState, and this takes care of all the plumbing.
|
|
func readInstanceFromState(
|
|
ctx EvalContext,
|
|
resourceName string,
|
|
output **InstanceState,
|
|
readerFn func(*ResourceState) (*InstanceState, error),
|
|
) (*InstanceState, error) {
|
|
state, lock := ctx.State()
|
|
|
|
// Get a read lock so we can access this instance
|
|
lock.RLock()
|
|
defer lock.RUnlock()
|
|
|
|
// Look for the module state. If we don't have one, then it doesn't matter.
|
|
mod := state.ModuleByPath(ctx.Path())
|
|
if mod == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
// Look for the resource state. If we don't have one, then it is okay.
|
|
rs := mod.Resources[resourceName]
|
|
if rs == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
// Use the delegate function to get the instance state from the resource state
|
|
is, err := readerFn(rs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Write the result to the output pointer
|
|
if output != nil {
|
|
*output = is
|
|
}
|
|
|
|
return is, nil
|
|
}
|
|
|
|
// EvalRequireState is an EvalNode implementation that early exits
|
|
// if the state doesn't have an ID.
|
|
type EvalRequireState struct {
|
|
State **InstanceState
|
|
}
|
|
|
|
func (n *EvalRequireState) Eval(ctx EvalContext) (interface{}, error) {
|
|
if n.State == nil {
|
|
return nil, EvalEarlyExitError{}
|
|
}
|
|
|
|
state := *n.State
|
|
if state == nil || state.ID == "" {
|
|
return nil, EvalEarlyExitError{}
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// EvalUpdateStateHook is an EvalNode implementation that calls the
|
|
// PostStateUpdate hook with the current state.
|
|
type EvalUpdateStateHook struct{}
|
|
|
|
func (n *EvalUpdateStateHook) Eval(ctx EvalContext) (interface{}, error) {
|
|
state, lock := ctx.State()
|
|
|
|
// Get a full lock. Even calling something like WriteState can modify
|
|
// (prune) the state, so we need the full lock.
|
|
lock.Lock()
|
|
defer lock.Unlock()
|
|
|
|
// Call the hook
|
|
err := ctx.Hook(func(h Hook) (HookAction, error) {
|
|
return h.PostStateUpdate(state)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// EvalWriteState is an EvalNode implementation that writes the
|
|
// primary InstanceState for a specific resource into the state.
|
|
type EvalWriteState struct {
|
|
Name string
|
|
ResourceType string
|
|
Provider string
|
|
Dependencies []string
|
|
State **InstanceState
|
|
}
|
|
|
|
func (n *EvalWriteState) Eval(ctx EvalContext) (interface{}, error) {
|
|
return writeInstanceToState(ctx, n.Name, n.ResourceType, n.Provider, n.Dependencies,
|
|
func(rs *ResourceState) error {
|
|
rs.Primary = *n.State
|
|
return nil
|
|
},
|
|
)
|
|
}
|
|
|
|
// EvalWriteStateDeposed is an EvalNode implementation that writes
|
|
// an InstanceState out to the Deposed list of a resource in the state.
|
|
type EvalWriteStateDeposed struct {
|
|
Name string
|
|
ResourceType string
|
|
Provider string
|
|
Dependencies []string
|
|
State **InstanceState
|
|
// Index indicates which instance in the Deposed list to target, or -1 to append.
|
|
Index int
|
|
}
|
|
|
|
func (n *EvalWriteStateDeposed) Eval(ctx EvalContext) (interface{}, error) {
|
|
return writeInstanceToState(ctx, n.Name, n.ResourceType, n.Provider, n.Dependencies,
|
|
func(rs *ResourceState) error {
|
|
if n.Index == -1 {
|
|
rs.Deposed = append(rs.Deposed, *n.State)
|
|
} else {
|
|
rs.Deposed[n.Index] = *n.State
|
|
}
|
|
return nil
|
|
},
|
|
)
|
|
}
|
|
|
|
// Pulls together the common tasks of the EvalWriteState nodes. All the args
|
|
// are passed directly down from the EvalNode along with a `writer` function
|
|
// which is yielded the *ResourceState and is responsible for writing an
|
|
// InstanceState to the proper field in the ResourceState.
|
|
func writeInstanceToState(
|
|
ctx EvalContext,
|
|
resourceName string,
|
|
resourceType string,
|
|
provider string,
|
|
dependencies []string,
|
|
writerFn func(*ResourceState) error,
|
|
) (*InstanceState, error) {
|
|
state, lock := ctx.State()
|
|
if state == nil {
|
|
return nil, fmt.Errorf("cannot write state to nil state")
|
|
}
|
|
|
|
// Get a write lock so we can access this instance
|
|
lock.Lock()
|
|
defer lock.Unlock()
|
|
|
|
// Look for the module state. If we don't have one, create it.
|
|
mod := state.ModuleByPath(ctx.Path())
|
|
if mod == nil {
|
|
mod = state.AddModule(ctx.Path())
|
|
}
|
|
|
|
// Look for the resource state.
|
|
rs := mod.Resources[resourceName]
|
|
if rs == nil {
|
|
rs = &ResourceState{}
|
|
rs.init()
|
|
mod.Resources[resourceName] = rs
|
|
}
|
|
rs.Type = resourceType
|
|
rs.Dependencies = dependencies
|
|
rs.Provider = provider
|
|
|
|
if err := writerFn(rs); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// EvalClearPrimaryState is an EvalNode implementation that clears the primary
|
|
// instance from a resource state.
|
|
type EvalClearPrimaryState struct {
|
|
Name string
|
|
}
|
|
|
|
func (n *EvalClearPrimaryState) Eval(ctx EvalContext) (interface{}, error) {
|
|
state, lock := ctx.State()
|
|
|
|
// Get a read lock so we can access this instance
|
|
lock.RLock()
|
|
defer lock.RUnlock()
|
|
|
|
// Look for the module state. If we don't have one, then it doesn't matter.
|
|
mod := state.ModuleByPath(ctx.Path())
|
|
if mod == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
// Look for the resource state. If we don't have one, then it is okay.
|
|
rs := mod.Resources[n.Name]
|
|
if rs == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
// Clear primary from the resource state
|
|
rs.Primary = nil
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// EvalDeposeState is an EvalNode implementation that takes the primary
|
|
// out of a state and makes it Deposed. This is done at the beginning of
|
|
// create-before-destroy calls so that the create can create while preserving
|
|
// the old state of the to-be-destroyed resource.
|
|
type EvalDeposeState struct {
|
|
Name string
|
|
}
|
|
|
|
// TODO: test
|
|
func (n *EvalDeposeState) Eval(ctx EvalContext) (interface{}, error) {
|
|
state, lock := ctx.State()
|
|
|
|
// Get a read lock so we can access this instance
|
|
lock.RLock()
|
|
defer lock.RUnlock()
|
|
|
|
// Look for the module state. If we don't have one, then it doesn't matter.
|
|
mod := state.ModuleByPath(ctx.Path())
|
|
if mod == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
// Look for the resource state. If we don't have one, then it is okay.
|
|
rs := mod.Resources[n.Name]
|
|
if rs == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
// If we don't have a primary, we have nothing to depose
|
|
if rs.Primary == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
// Depose
|
|
rs.Deposed = append(rs.Deposed, rs.Primary)
|
|
rs.Primary = nil
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// EvalUndeposeState is an EvalNode implementation that reads the
|
|
// InstanceState for a specific resource out of the state.
|
|
type EvalUndeposeState struct {
|
|
Name string
|
|
State **InstanceState
|
|
}
|
|
|
|
// TODO: test
|
|
func (n *EvalUndeposeState) Eval(ctx EvalContext) (interface{}, error) {
|
|
state, lock := ctx.State()
|
|
|
|
// Get a read lock so we can access this instance
|
|
lock.RLock()
|
|
defer lock.RUnlock()
|
|
|
|
// Look for the module state. If we don't have one, then it doesn't matter.
|
|
mod := state.ModuleByPath(ctx.Path())
|
|
if mod == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
// Look for the resource state. If we don't have one, then it is okay.
|
|
rs := mod.Resources[n.Name]
|
|
if rs == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
// If we don't have any desposed resource, then we don't have anything to do
|
|
if len(rs.Deposed) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
// Undepose
|
|
idx := len(rs.Deposed) - 1
|
|
rs.Primary = rs.Deposed[idx]
|
|
rs.Deposed[idx] = *n.State
|
|
|
|
return nil, nil
|
|
}
|