diff --git a/terraform/context_test.go b/terraform/context_test.go index a2dcfc91d..90ec09f8f 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -3204,8 +3204,7 @@ func TestContext2Apply_nilDiff(t *testing.T) { } } -/* -func TestContextApply_Provisioner_compute(t *testing.T) { +func TestContext2Apply_Provisioner_compute(t *testing.T) { m := testModule(t, "apply-provisioner-compute") p := testProvider("aws") pr := testProvisioner() @@ -3219,7 +3218,7 @@ func TestContextApply_Provisioner_compute(t *testing.T) { return nil } - ctx := testContext(t, &ContextOpts{ + ctx := testContext2(t, &ContextOpts{ Module: m, Providers: map[string]ResourceProviderFactory{ "aws": testProviderFuncFixed(p), @@ -3253,6 +3252,7 @@ func TestContextApply_Provisioner_compute(t *testing.T) { } } +/* func TestContextApply_provisionerCreateFail(t *testing.T) { m := testModule(t, "apply-provisioner-fail-create") p := testProvider("aws") diff --git a/terraform/eval.go b/terraform/eval.go index 875e3440a..8dd1b1e8f 100644 --- a/terraform/eval.go +++ b/terraform/eval.go @@ -38,7 +38,7 @@ func (EvalEarlyExitError) Error() string { return "early exit" } func Eval(n EvalNode, ctx EvalContext) (interface{}, error) { // Call the lower level eval which doesn't understand early exit, // and if we early exit, it isn't an error. - result, err := eval(n, ctx) + result, err := EvalRaw(n, ctx) if err != nil { if _, ok := err.(EvalEarlyExitError); ok { return nil, nil @@ -48,11 +48,13 @@ func Eval(n EvalNode, ctx EvalContext) (interface{}, error) { return result, err } -func eval(n EvalNode, ctx EvalContext) (interface{}, error) { +// EvalRaw is like Eval except that it returns all errors, even if they +// signal something normal such as EvalEarlyExitError. +func EvalRaw(n EvalNode, ctx EvalContext) (interface{}, error) { argNodes, _ := n.Args() args := make([]interface{}, len(argNodes)) for i, n := range argNodes { - v, err := eval(n, ctx) + v, err := EvalRaw(n, ctx) if err != nil { return nil, err } diff --git a/terraform/eval_apply.go b/terraform/eval_apply.go index be954b305..70b12f388 100644 --- a/terraform/eval_apply.go +++ b/terraform/eval_apply.go @@ -3,6 +3,7 @@ package terraform import ( "fmt" "log" + "strconv" "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform/config" @@ -16,6 +17,8 @@ type EvalApply struct { Diff **InstanceDiff Provider *ResourceProvider Output **InstanceState + Error *error + Tainted *bool } func (n *EvalApply) Args() ([]EvalNode, []EvalType) { @@ -81,9 +84,186 @@ func (n *EvalApply) Eval( *n.Output = state } + // Set the tainted state + if n.Tainted != nil { + *n.Tainted = err != nil + } + + // 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 { + *n.Error = multierror.Append(*n.Error, err) + } else { + return nil, err + } + } + return nil, nil } func (n *EvalApply) Type() EvalType { return EvalTypeNull } + +// 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 + Tainted *bool + Error *error +} + +func (n *EvalApplyProvisioners) Args() ([]EvalNode, []EvalType) { + return nil, nil +} + +// TODO: test +func (n *EvalApplyProvisioners) Eval( + ctx EvalContext, args []interface{}) (interface{}, error) { + state := *n.State + + if *n.Tainted { + // 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 n.Tainted != nil { + *n.Tainted = err != nil + } + if err != nil { + 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) Type() EvalType { + return EvalTypeNull +} + +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 + +} diff --git a/terraform/eval_state.go b/terraform/eval_state.go index d7eca6432..61e12662f 100644 --- a/terraform/eval_state.go +++ b/terraform/eval_state.go @@ -62,12 +62,13 @@ func (n *EvalReadState) Type() EvalType { // EvalWriteState is an EvalNode implementation that reads the // InstanceState for a specific resource out of the state. type EvalWriteState struct { - Name string - ResourceType string - Dependencies []string - State **InstanceState - Tainted bool - TaintedIndex int + Name string + ResourceType string + Dependencies []string + State **InstanceState + Tainted *bool + TaintedIndex int + TaintedClearPrimary bool } func (n *EvalWriteState) Args() ([]EvalNode, []EvalType) { @@ -102,9 +103,15 @@ func (n *EvalWriteState) Eval( rs.Type = n.ResourceType rs.Dependencies = n.Dependencies - if n.Tainted { + if n.Tainted != nil && *n.Tainted { if n.TaintedIndex != -1 { rs.Tainted[n.TaintedIndex] = *n.State + } else { + rs.Tainted = append(rs.Tainted, *n.State) + } + + if n.TaintedClearPrimary { + rs.Primary = nil } } else { // Set the primary state diff --git a/terraform/graph_config_node.go b/terraform/graph_config_node.go index d208d7a45..bfabfe78f 100644 --- a/terraform/graph_config_node.go +++ b/terraform/graph_config_node.go @@ -185,9 +185,9 @@ func (n *GraphNodeConfigResource) DependableName() []string { // GraphNodeDependent impl. func (n *GraphNodeConfigResource) DependentOn() []string { result := make([]string, len(n.Resource.DependsOn), - len(n.Resource.RawCount.Variables)+ + (len(n.Resource.RawCount.Variables)+ len(n.Resource.RawConfig.Variables)+ - len(n.Resource.DependsOn)) + len(n.Resource.DependsOn))*2) copy(result, n.Resource.DependsOn) for _, v := range n.Resource.RawCount.Variables { if vn := varNameForVar(v); vn != "" { @@ -199,6 +199,18 @@ func (n *GraphNodeConfigResource) DependentOn() []string { result = append(result, vn) } } + for _, p := range n.Resource.Provisioners { + for _, v := range p.ConnInfo.Variables { + if vn := varNameForVar(v); vn != "" { + result = append(result, vn) + } + } + for _, v := range p.RawConfig.Variables { + if vn := varNameForVar(v); vn != "" { + result = append(result, vn) + } + } + } return result } diff --git a/terraform/transform_resource.go b/terraform/transform_resource.go index ca02951a9..497f2faa3 100644 --- a/terraform/transform_resource.go +++ b/terraform/transform_resource.go @@ -222,6 +222,8 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode { // Diff the resource for destruction var provider ResourceProvider var diffApply *InstanceDiff + var err error + var tainted bool seq.Nodes = append(seq.Nodes, &EvalOpFilter{ Ops: []walkOperation{walkApply}, Node: &EvalSequence{ @@ -262,6 +264,8 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode { Diff: &diffApply, Provider: &provider, Output: &state, + Error: &err, + Tainted: &tainted, }, &EvalWriteState{ Name: n.stateId(), @@ -269,6 +273,23 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode { Dependencies: n.DependentOn(), State: &state, }, + &EvalApplyProvisioners{ + Info: info, + State: &state, + Resource: n.Resource, + InterpResource: resource, + Tainted: &tainted, + Error: &err, + }, + &EvalWriteState{ + Name: n.stateId(), + ResourceType: n.Resource.Type, + Dependencies: n.DependentOn(), + State: &state, + Tainted: &tainted, + TaintedIndex: -1, + TaintedClearPrimary: true, + }, }, }, }) diff --git a/terraform/transform_tainted.go b/terraform/transform_tainted.go index b0b9e0d0a..2e1936fd3 100644 --- a/terraform/transform_tainted.go +++ b/terraform/transform_tainted.go @@ -66,6 +66,7 @@ func (n *graphNodeTaintedResource) ProvidedBy() []string { // GraphNodeEvalable impl. func (n *graphNodeTaintedResource) EvalTree() EvalNode { var state *InstanceState + tainted := true seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)} @@ -95,7 +96,7 @@ func (n *graphNodeTaintedResource) EvalTree() EvalNode { ResourceType: n.ResourceType, Dependencies: n.DependentOn(), State: &state, - Tainted: true, + Tainted: &tainted, TaintedIndex: n.Index, }, }, @@ -140,7 +141,7 @@ func (n *graphNodeTaintedResource) EvalTree() EvalNode { ResourceType: n.ResourceType, Dependencies: n.DependentOn(), State: &state, - Tainted: true, + Tainted: &tainted, TaintedIndex: n.Index, }, }, diff --git a/terraform/ui_output_callback.go b/terraform/ui_output_callback.go index 147515b95..135a91c5f 100644 --- a/terraform/ui_output_callback.go +++ b/terraform/ui_output_callback.go @@ -1,5 +1,9 @@ package terraform type CallbackUIOutput struct { - OutputFun func(string) + OutputFn func(string) +} + +func (o *CallbackUIOutput) Output(v string) { + o.OutputFn(v) } diff --git a/terraform/ui_output_callback_test.go b/terraform/ui_output_callback_test.go new file mode 100644 index 000000000..1dd5ccddf --- /dev/null +++ b/terraform/ui_output_callback_test.go @@ -0,0 +1,9 @@ +package terraform + +import ( + "testing" +) + +func TestCallbackUIOutput_impl(t *testing.T) { + var _ UIOutput = new(CallbackUIOutput) +}