360 lines
8.0 KiB
Go
360 lines
8.0 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()
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// EvalApplyPre is an EvalNode implementation that does the pre-Apply work
|
|
type EvalApplyPre struct {
|
|
Info *InstanceInfo
|
|
State **InstanceState
|
|
Diff **InstanceDiff
|
|
}
|
|
|
|
// TODO: test
|
|
func (n *EvalApplyPre) Eval(ctx EvalContext) (interface{}, error) {
|
|
state := *n.State
|
|
diff := *n.Diff
|
|
|
|
// If the state is nil, make it non-nil
|
|
if state == nil {
|
|
state = new(InstanceState)
|
|
}
|
|
state.init()
|
|
|
|
{
|
|
// Call post-apply hook
|
|
err := ctx.Hook(func(h Hook) (HookAction, error) {
|
|
return h.PreApply(n.Info, state, diff)
|
|
})
|
|
if err != nil {
|
|
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
|
|
|
|
// When is the type of provisioner to run at this point
|
|
When config.ProvisionerWhen
|
|
}
|
|
|
|
// TODO: test
|
|
func (n *EvalApplyProvisioners) Eval(ctx EvalContext) (interface{}, error) {
|
|
state := *n.State
|
|
|
|
if n.CreateNew != nil && !*n.CreateNew {
|
|
// If we're not creating a new resource, then don't run provisioners
|
|
return nil, nil
|
|
}
|
|
|
|
provs := n.filterProvisioners()
|
|
if len(provs) == 0 {
|
|
// We have no provisioners, so don't do anything
|
|
return nil, nil
|
|
}
|
|
|
|
// taint tells us whether to enable tainting.
|
|
taint := n.When == config.ProvisionerWhenCreate
|
|
|
|
if n.Error != nil && *n.Error != nil {
|
|
if taint {
|
|
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, provs)
|
|
if err != nil {
|
|
if taint {
|
|
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
|
|
}
|
|
|
|
// filterProvisioners filters the provisioners on the resource to only
|
|
// the provisioners specified by the "when" option.
|
|
func (n *EvalApplyProvisioners) filterProvisioners() []*config.Provisioner {
|
|
// Fast path the zero case
|
|
if n.Resource == nil {
|
|
return nil
|
|
}
|
|
|
|
if len(n.Resource.Provisioners) == 0 {
|
|
return nil
|
|
}
|
|
|
|
result := make([]*config.Provisioner, 0, len(n.Resource.Provisioners))
|
|
for _, p := range n.Resource.Provisioners {
|
|
if p.When == n.When {
|
|
result = append(result, p)
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (n *EvalApplyProvisioners) apply(ctx EvalContext, provs []*config.Provisioner) error {
|
|
state := *n.State
|
|
|
|
// Store the original connection info, restore later
|
|
origConnInfo := state.Ephemeral.ConnInfo
|
|
defer func() {
|
|
state.Ephemeral.ConnInfo = origConnInfo
|
|
}()
|
|
|
|
for _, prov := range provs {
|
|
// Get the provisioner
|
|
provisioner := ctx.Provisioner(prov.Type)
|
|
|
|
// Interpolate the provisioner config
|
|
provConfig, err := ctx.Interpolate(prov.RawConfig.Copy(), n.InterpResource)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Interpolate the conn info, since it may contain variables
|
|
connInfo, err := ctx.Interpolate(prov.ConnInfo.Copy(), 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}
|
|
applyErr := provisioner.Apply(&output, state, provConfig)
|
|
|
|
// Call post hook
|
|
hookErr := ctx.Hook(func(h Hook) (HookAction, error) {
|
|
return h.PostProvision(n.Info, prov.Type, applyErr)
|
|
})
|
|
|
|
// Handle the error before we deal with the hook
|
|
if applyErr != nil {
|
|
// Determine failure behavior
|
|
switch prov.OnFailure {
|
|
case config.ProvisionerOnFailureContinue:
|
|
log.Printf(
|
|
"[INFO] apply: %s [%s]: error during provision, continue requested",
|
|
n.Info.Id, prov.Type)
|
|
|
|
case config.ProvisionerOnFailureFail:
|
|
return applyErr
|
|
}
|
|
}
|
|
|
|
// Deal with the hook
|
|
if hookErr != nil {
|
|
return hookErr
|
|
}
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|