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 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")

View File

@ -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
}

View File

@ -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
}

View File

@ -66,8 +66,9 @@ type EvalWriteState struct {
ResourceType string
Dependencies []string
State **InstanceState
Tainted bool
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

View File

@ -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
}

View File

@ -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,
},
},
},
})

View File

@ -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,
},
},

View File

@ -1,5 +1,9 @@
package terraform
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)
}