opentofu/terraform/eval_state.go
Matt Good 21b0a03d70 Support for multiple providers of the same type
Adds an "alias" field to the provider which allows creating multiple instances
of a provider under different names. This provides support for configurations
such as multiple AWS providers for different regions. In each resource, the
provider can be set with the "provider" field.

(thanks to Cisco Cloud for their support)
2015-04-20 14:14:34 -07:00

377 lines
9.7 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
})
}
// EvalReadStateTainted is an EvalNode implementation that reads a
// tainted InstanceState for a specific resource out of the state
type EvalReadStateTainted struct {
Name string
Output **InstanceState
// Index indicates which instance in the Tainted list to target, or -1 for
// the last item.
Index int
}
func (n *EvalReadStateTainted) 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.Tainted) - 1
}
if idx >= 0 && idx < len(rs.Tainted) {
return rs.Tainted[idx], nil
} else {
return nil, fmt.Errorf("bad tainted index: %d, for resource: %#v", idx, rs)
}
})
}
// 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
},
)
}
// EvalWriteStateTainted is an EvalNode implementation that writes
// an InstanceState out to the Tainted list of a resource in the state.
type EvalWriteStateTainted struct {
Name string
ResourceType string
Provider string
Dependencies []string
State **InstanceState
// Index indicates which instance in the Tainted list to target, or -1 to append.
Index int
}
// EvalWriteStateTainted is an EvalNode implementation that writes the
// one of the tainted InstanceStates for a specific resource out of the state.
func (n *EvalWriteStateTainted) 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.Tainted = append(rs.Tainted, *n.State)
} else {
rs.Tainted[n.Index] = *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
}
// 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] = nil
return nil, nil
}