mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-09 23:54:17 -06:00
5802f76eaa
This isn't a pretty refactor, but fixes the race issues in this package for now. Fix race on RawConfig.Config() fix command package races
296 lines
6.5 KiB
Go
296 lines
6.5 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"strconv"
|
|
|
|
"github.com/hashicorp/go-multierror"
|
|
"github.com/hashicorp/terraform/config"
|
|
)
|
|
|
|
// EvalApply is an EvalNode implementation that writes the diff to
|
|
// the full diff.
|
|
type EvalApply struct {
|
|
Info *InstanceInfo
|
|
State **InstanceState
|
|
Diff **InstanceDiff
|
|
Provider *ResourceProvider
|
|
Output **InstanceState
|
|
CreateNew *bool
|
|
Error *error
|
|
}
|
|
|
|
// TODO: test
|
|
func (n *EvalApply) Eval(ctx EvalContext) (interface{}, error) {
|
|
diff := *n.Diff
|
|
provider := *n.Provider
|
|
state := *n.State
|
|
|
|
// If we have no diff, we have nothing to do!
|
|
if diff.Empty() {
|
|
log.Printf(
|
|
"[DEBUG] apply: %s: diff is empty, doing nothing.", n.Info.Id)
|
|
return nil, nil
|
|
}
|
|
|
|
// Remove any output values from the diff
|
|
for k, ad := range diff.CopyAttributes() {
|
|
if ad.Type == DiffAttrOutput {
|
|
diff.DelAttribute(k)
|
|
}
|
|
}
|
|
|
|
// If the state is nil, make it non-nil
|
|
if state == nil {
|
|
state = new(InstanceState)
|
|
}
|
|
state.init()
|
|
|
|
// Flag if we're creating a new instance
|
|
if n.CreateNew != nil {
|
|
*n.CreateNew = state.ID == "" && !diff.GetDestroy() || diff.RequiresNew()
|
|
}
|
|
|
|
{
|
|
// Call pre-apply hook
|
|
err := ctx.Hook(func(h Hook) (HookAction, error) {
|
|
return h.PreApply(n.Info, state, diff)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// With the completed diff, apply!
|
|
log.Printf("[DEBUG] apply: %s: executing Apply", n.Info.Id)
|
|
state, err := provider.Apply(n.Info, state, diff)
|
|
if state == nil {
|
|
state = new(InstanceState)
|
|
}
|
|
state.init()
|
|
|
|
// Force the "id" attribute to be our ID
|
|
if state.ID != "" {
|
|
state.Attributes["id"] = state.ID
|
|
}
|
|
|
|
// If the value is the unknown variable value, then it is an error.
|
|
// In this case we record the error and remove it from the state
|
|
for ak, av := range state.Attributes {
|
|
if av == config.UnknownVariableValue {
|
|
err = multierror.Append(err, fmt.Errorf(
|
|
"Attribute with unknown value: %s", ak))
|
|
delete(state.Attributes, ak)
|
|
}
|
|
}
|
|
|
|
// Write the final state
|
|
if n.Output != nil {
|
|
*n.Output = state
|
|
}
|
|
|
|
// If there are no errors, then we append it to our output error
|
|
// if we have one, otherwise we just output it.
|
|
if err != nil {
|
|
if n.Error != nil {
|
|
helpfulErr := fmt.Errorf("%s: %s", n.Info.Id, err.Error())
|
|
*n.Error = multierror.Append(*n.Error, helpfulErr)
|
|
} else {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// EvalApplyPost is an EvalNode implementation that does the post-Apply work
|
|
type EvalApplyPost struct {
|
|
Info *InstanceInfo
|
|
State **InstanceState
|
|
Error *error
|
|
}
|
|
|
|
// TODO: test
|
|
func (n *EvalApplyPost) Eval(ctx EvalContext) (interface{}, error) {
|
|
state := *n.State
|
|
|
|
{
|
|
// Call post-apply hook
|
|
err := ctx.Hook(func(h Hook) (HookAction, error) {
|
|
return h.PostApply(n.Info, state, *n.Error)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return nil, *n.Error
|
|
}
|
|
|
|
// EvalApplyProvisioners is an EvalNode implementation that executes
|
|
// the provisioners for a resource.
|
|
//
|
|
// TODO(mitchellh): This should probably be split up into a more fine-grained
|
|
// ApplyProvisioner (single) that is looped over.
|
|
type EvalApplyProvisioners struct {
|
|
Info *InstanceInfo
|
|
State **InstanceState
|
|
Resource *config.Resource
|
|
InterpResource *Resource
|
|
CreateNew *bool
|
|
Error *error
|
|
}
|
|
|
|
// TODO: test
|
|
func (n *EvalApplyProvisioners) Eval(ctx EvalContext) (interface{}, error) {
|
|
state := *n.State
|
|
|
|
if !*n.CreateNew {
|
|
// If we're not creating a new resource, then don't run provisioners
|
|
return nil, nil
|
|
}
|
|
|
|
if len(n.Resource.Provisioners) == 0 {
|
|
// We have no provisioners, so don't do anything
|
|
return nil, nil
|
|
}
|
|
|
|
if n.Error != nil && *n.Error != nil {
|
|
// We're already errored creating, so mark as tainted and continue
|
|
state.Tainted = true
|
|
|
|
// We're already tainted, so just return out
|
|
return nil, nil
|
|
}
|
|
|
|
{
|
|
// Call pre hook
|
|
err := ctx.Hook(func(h Hook) (HookAction, error) {
|
|
return h.PreProvisionResource(n.Info, state)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// If there are no errors, then we append it to our output error
|
|
// if we have one, otherwise we just output it.
|
|
err := n.apply(ctx)
|
|
if err != nil {
|
|
// Provisioning failed, so mark the resource as tainted
|
|
state.Tainted = true
|
|
|
|
if n.Error != nil {
|
|
*n.Error = multierror.Append(*n.Error, err)
|
|
} else {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
{
|
|
// Call post hook
|
|
err := ctx.Hook(func(h Hook) (HookAction, error) {
|
|
return h.PostProvisionResource(n.Info, state)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func (n *EvalApplyProvisioners) apply(ctx EvalContext) error {
|
|
state := *n.State
|
|
|
|
// Store the original connection info, restore later
|
|
origConnInfo := state.Ephemeral.ConnInfo
|
|
defer func() {
|
|
state.Ephemeral.ConnInfo = origConnInfo
|
|
}()
|
|
|
|
for _, prov := range n.Resource.Provisioners {
|
|
// Get the provisioner
|
|
provisioner := ctx.Provisioner(prov.Type)
|
|
|
|
// Interpolate the provisioner config
|
|
provConfig, err := ctx.Interpolate(prov.RawConfig, n.InterpResource)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Interpolate the conn info, since it may contain variables
|
|
connInfo, err := ctx.Interpolate(prov.ConnInfo, n.InterpResource)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Merge the connection information
|
|
overlay := make(map[string]string)
|
|
if origConnInfo != nil {
|
|
for k, v := range origConnInfo {
|
|
overlay[k] = v
|
|
}
|
|
}
|
|
for k, v := range connInfo.Config {
|
|
switch vt := v.(type) {
|
|
case string:
|
|
overlay[k] = vt
|
|
case int64:
|
|
overlay[k] = strconv.FormatInt(vt, 10)
|
|
case int32:
|
|
overlay[k] = strconv.FormatInt(int64(vt), 10)
|
|
case int:
|
|
overlay[k] = strconv.FormatInt(int64(vt), 10)
|
|
case float32:
|
|
overlay[k] = strconv.FormatFloat(float64(vt), 'f', 3, 32)
|
|
case float64:
|
|
overlay[k] = strconv.FormatFloat(vt, 'f', 3, 64)
|
|
case bool:
|
|
overlay[k] = strconv.FormatBool(vt)
|
|
default:
|
|
overlay[k] = fmt.Sprintf("%v", vt)
|
|
}
|
|
}
|
|
state.Ephemeral.ConnInfo = overlay
|
|
|
|
{
|
|
// Call pre hook
|
|
err := ctx.Hook(func(h Hook) (HookAction, error) {
|
|
return h.PreProvision(n.Info, prov.Type)
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// The output function
|
|
outputFn := func(msg string) {
|
|
ctx.Hook(func(h Hook) (HookAction, error) {
|
|
h.ProvisionOutput(n.Info, prov.Type, msg)
|
|
return HookActionContinue, nil
|
|
})
|
|
}
|
|
|
|
// Invoke the Provisioner
|
|
output := CallbackUIOutput{OutputFn: outputFn}
|
|
if err := provisioner.Apply(&output, state, provConfig); err != nil {
|
|
return err
|
|
}
|
|
|
|
{
|
|
// Call post hook
|
|
err := ctx.Hook(func(h Hook) (HookAction, error) {
|
|
return h.PostProvision(n.Info, prov.Type)
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|