terraform: provisioners

This commit is contained in:
Mitchell Hashimoto 2015-02-13 09:49:29 -08:00
parent f8871917f5
commit 819aed67d4
9 changed files with 254 additions and 18 deletions

View File

@ -3204,8 +3204,7 @@ func TestContext2Apply_nilDiff(t *testing.T) {
} }
} }
/* func TestContext2Apply_Provisioner_compute(t *testing.T) {
func TestContextApply_Provisioner_compute(t *testing.T) {
m := testModule(t, "apply-provisioner-compute") m := testModule(t, "apply-provisioner-compute")
p := testProvider("aws") p := testProvider("aws")
pr := testProvisioner() pr := testProvisioner()
@ -3219,7 +3218,7 @@ func TestContextApply_Provisioner_compute(t *testing.T) {
return nil return nil
} }
ctx := testContext(t, &ContextOpts{ ctx := testContext2(t, &ContextOpts{
Module: m, Module: m,
Providers: map[string]ResourceProviderFactory{ Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p), "aws": testProviderFuncFixed(p),
@ -3253,6 +3252,7 @@ func TestContextApply_Provisioner_compute(t *testing.T) {
} }
} }
/*
func TestContextApply_provisionerCreateFail(t *testing.T) { func TestContextApply_provisionerCreateFail(t *testing.T) {
m := testModule(t, "apply-provisioner-fail-create") m := testModule(t, "apply-provisioner-fail-create")
p := testProvider("aws") p := testProvider("aws")

View File

@ -38,7 +38,7 @@ func (EvalEarlyExitError) Error() string { return "early exit" }
func Eval(n EvalNode, ctx EvalContext) (interface{}, error) { func Eval(n EvalNode, ctx EvalContext) (interface{}, error) {
// Call the lower level eval which doesn't understand early exit, // Call the lower level eval which doesn't understand early exit,
// and if we early exit, it isn't an error. // and if we early exit, it isn't an error.
result, err := eval(n, ctx) result, err := EvalRaw(n, ctx)
if err != nil { if err != nil {
if _, ok := err.(EvalEarlyExitError); ok { if _, ok := err.(EvalEarlyExitError); ok {
return nil, nil return nil, nil
@ -48,11 +48,13 @@ func Eval(n EvalNode, ctx EvalContext) (interface{}, error) {
return result, err 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() argNodes, _ := n.Args()
args := make([]interface{}, len(argNodes)) args := make([]interface{}, len(argNodes))
for i, n := range argNodes { for i, n := range argNodes {
v, err := eval(n, ctx) v, err := EvalRaw(n, ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -3,6 +3,7 @@ package terraform
import ( import (
"fmt" "fmt"
"log" "log"
"strconv"
"github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config"
@ -16,6 +17,8 @@ type EvalApply struct {
Diff **InstanceDiff Diff **InstanceDiff
Provider *ResourceProvider Provider *ResourceProvider
Output **InstanceState Output **InstanceState
Error *error
Tainted *bool
} }
func (n *EvalApply) Args() ([]EvalNode, []EvalType) { func (n *EvalApply) Args() ([]EvalNode, []EvalType) {
@ -81,9 +84,186 @@ func (n *EvalApply) Eval(
*n.Output = state *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 return nil, nil
} }
func (n *EvalApply) Type() EvalType { func (n *EvalApply) Type() EvalType {
return EvalTypeNull 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
}

View File

@ -66,8 +66,9 @@ type EvalWriteState struct {
ResourceType string ResourceType string
Dependencies []string Dependencies []string
State **InstanceState State **InstanceState
Tainted bool Tainted *bool
TaintedIndex int TaintedIndex int
TaintedClearPrimary bool
} }
func (n *EvalWriteState) Args() ([]EvalNode, []EvalType) { func (n *EvalWriteState) Args() ([]EvalNode, []EvalType) {
@ -102,9 +103,15 @@ func (n *EvalWriteState) Eval(
rs.Type = n.ResourceType rs.Type = n.ResourceType
rs.Dependencies = n.Dependencies rs.Dependencies = n.Dependencies
if n.Tainted { if n.Tainted != nil && *n.Tainted {
if n.TaintedIndex != -1 { if n.TaintedIndex != -1 {
rs.Tainted[n.TaintedIndex] = *n.State rs.Tainted[n.TaintedIndex] = *n.State
} else {
rs.Tainted = append(rs.Tainted, *n.State)
}
if n.TaintedClearPrimary {
rs.Primary = nil
} }
} else { } else {
// Set the primary state // Set the primary state

View File

@ -185,9 +185,9 @@ func (n *GraphNodeConfigResource) DependableName() []string {
// GraphNodeDependent impl. // GraphNodeDependent impl.
func (n *GraphNodeConfigResource) DependentOn() []string { func (n *GraphNodeConfigResource) DependentOn() []string {
result := make([]string, len(n.Resource.DependsOn), 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.RawConfig.Variables)+
len(n.Resource.DependsOn)) len(n.Resource.DependsOn))*2)
copy(result, n.Resource.DependsOn) copy(result, n.Resource.DependsOn)
for _, v := range n.Resource.RawCount.Variables { for _, v := range n.Resource.RawCount.Variables {
if vn := varNameForVar(v); vn != "" { if vn := varNameForVar(v); vn != "" {
@ -199,6 +199,18 @@ func (n *GraphNodeConfigResource) DependentOn() []string {
result = append(result, vn) 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 return result
} }

View File

@ -222,6 +222,8 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
// Diff the resource for destruction // Diff the resource for destruction
var provider ResourceProvider var provider ResourceProvider
var diffApply *InstanceDiff var diffApply *InstanceDiff
var err error
var tainted bool
seq.Nodes = append(seq.Nodes, &EvalOpFilter{ seq.Nodes = append(seq.Nodes, &EvalOpFilter{
Ops: []walkOperation{walkApply}, Ops: []walkOperation{walkApply},
Node: &EvalSequence{ Node: &EvalSequence{
@ -262,6 +264,8 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
Diff: &diffApply, Diff: &diffApply,
Provider: &provider, Provider: &provider,
Output: &state, Output: &state,
Error: &err,
Tainted: &tainted,
}, },
&EvalWriteState{ &EvalWriteState{
Name: n.stateId(), Name: n.stateId(),
@ -269,6 +273,23 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
Dependencies: n.DependentOn(), Dependencies: n.DependentOn(),
State: &state, 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,
},
}, },
}, },
}) })

View File

@ -66,6 +66,7 @@ func (n *graphNodeTaintedResource) ProvidedBy() []string {
// GraphNodeEvalable impl. // GraphNodeEvalable impl.
func (n *graphNodeTaintedResource) EvalTree() EvalNode { func (n *graphNodeTaintedResource) EvalTree() EvalNode {
var state *InstanceState var state *InstanceState
tainted := true
seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)} seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
@ -95,7 +96,7 @@ func (n *graphNodeTaintedResource) EvalTree() EvalNode {
ResourceType: n.ResourceType, ResourceType: n.ResourceType,
Dependencies: n.DependentOn(), Dependencies: n.DependentOn(),
State: &state, State: &state,
Tainted: true, Tainted: &tainted,
TaintedIndex: n.Index, TaintedIndex: n.Index,
}, },
}, },
@ -140,7 +141,7 @@ func (n *graphNodeTaintedResource) EvalTree() EvalNode {
ResourceType: n.ResourceType, ResourceType: n.ResourceType,
Dependencies: n.DependentOn(), Dependencies: n.DependentOn(),
State: &state, State: &state,
Tainted: true, Tainted: &tainted,
TaintedIndex: n.Index, TaintedIndex: n.Index,
}, },
}, },

View File

@ -1,5 +1,9 @@
package terraform package terraform
type CallbackUIOutput struct { type CallbackUIOutput struct {
OutputFun func(string) OutputFn func(string)
}
func (o *CallbackUIOutput) Output(v string) {
o.OutputFn(v)
} }

View File

@ -0,0 +1,9 @@
package terraform
import (
"testing"
)
func TestCallbackUIOutput_impl(t *testing.T) {
var _ UIOutput = new(CallbackUIOutput)
}