terraform/terraform/eval_state.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
}