mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-30 10:47:14 -06:00
324 lines
8.1 KiB
Go
324 lines
8.1 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 read lock so it doesn't change while we're calling this
|
|
lock.RLock()
|
|
defer lock.RUnlock()
|
|
|
|
// 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
|
|
}
|