Don't allow interpolation failure to stop Input

Allow module variables to fail interpolation during input. This is OK
since they will be verified again during Plan.  Because Input happens
before Refresh, module variable interpolation can fail when referencing
values that aren't yet in the state, but are expected after Refresh.
This commit is contained in:
James Bardin 2017-07-21 12:41:29 -04:00
parent 5bcc1bae59
commit 97bb7cb65c
6 changed files with 64 additions and 7 deletions

View File

@ -1,6 +1,10 @@
package terraform package terraform
import "github.com/hashicorp/terraform/config" import (
"log"
"github.com/hashicorp/terraform/config"
)
// EvalInterpolate is an EvalNode implementation that takes a raw // EvalInterpolate is an EvalNode implementation that takes a raw
// configuration and interpolates it. // configuration and interpolates it.
@ -22,3 +26,28 @@ func (n *EvalInterpolate) Eval(ctx EvalContext) (interface{}, error) {
return nil, nil return nil, nil
} }
// EvalTryInterpolate is an EvalNode implementation that takes a raw
// configuration and interpolates it, but only logs a warning on an
// interpolation error, and stops further Eval steps.
// This is used during Input where a value may not be known before Refresh, but
// we don't want to block Input.
type EvalTryInterpolate struct {
Config *config.RawConfig
Resource *Resource
Output **ResourceConfig
}
func (n *EvalTryInterpolate) Eval(ctx EvalContext) (interface{}, error) {
rc, err := ctx.Interpolate(n.Config, n.Resource)
if err != nil {
log.Printf("[WARN] Interpolation %q failed: %s", n.Config.Key, err)
return nil, EvalEarlyExitError{}
}
if n.Output != nil {
*n.Output = rc
}
return nil, nil
}

View File

@ -1,6 +1,8 @@
package terraform package terraform
import "fmt" import (
"fmt"
)
// EvalReadState is an EvalNode implementation that reads the // EvalReadState is an EvalNode implementation that reads the
// primary InstanceState for a specific resource out of the state. // primary InstanceState for a specific resource out of the state.

View File

@ -10,6 +10,9 @@ import (
// and is based on the PlanGraphBuilder. The PlanGraphBuilder passed in will be // and is based on the PlanGraphBuilder. The PlanGraphBuilder passed in will be
// modified and should not be used for any other operations. // modified and should not be used for any other operations.
func InputGraphBuilder(p *PlanGraphBuilder) GraphBuilder { func InputGraphBuilder(p *PlanGraphBuilder) GraphBuilder {
// convert this to an InputPlan
p.Input = true
// We're going to customize the concrete functions // We're going to customize the concrete functions
p.CustomConcrete = true p.CustomConcrete = true

View File

@ -40,6 +40,9 @@ type PlanGraphBuilder struct {
// Validate will do structural validation of the graph. // Validate will do structural validation of the graph.
Validate bool Validate bool
// Input represents that this builder is for an Input operation.
Input bool
// CustomConcrete can be set to customize the node types created // CustomConcrete can be set to customize the node types created
// for various parts of the plan. This is useful in order to customize // for various parts of the plan. This is useful in order to customize
// the plan behavior. // the plan behavior.
@ -107,7 +110,10 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
), ),
// Add module variables // Add module variables
&ModuleVariableTransformer{Module: b.Module}, &ModuleVariableTransformer{
Module: b.Module,
Input: b.Input,
},
// Connect so that the references are ready for targeting. We'll // Connect so that the references are ready for targeting. We'll
// have to connect again later for providers and so on. // have to connect again later for providers and so on.

View File

@ -15,6 +15,9 @@ type NodeApplyableModuleVariable struct {
Value *config.RawConfig // Value is the value that is set Value *config.RawConfig // Value is the value that is set
Module *module.Tree // Antiquated, want to remove Module *module.Tree // Antiquated, want to remove
// Input is set if this graph was created for the Input operation.
Input bool
} }
func (n *NodeApplyableModuleVariable) Name() string { func (n *NodeApplyableModuleVariable) Name() string {
@ -92,12 +95,24 @@ func (n *NodeApplyableModuleVariable) EvalTree() EvalNode {
// within the variables mapping. // within the variables mapping.
var config *ResourceConfig var config *ResourceConfig
variables := make(map[string]interface{}) variables := make(map[string]interface{})
return &EvalSequence{
Nodes: []EvalNode{ var interpolate EvalNode
&EvalInterpolate{
if n.Input {
interpolate = &EvalTryInterpolate{
Config: n.Value, Config: n.Value,
Output: &config, Output: &config,
}, }
} else {
interpolate = &EvalInterpolate{
Config: n.Value,
Output: &config,
}
}
return &EvalSequence{
Nodes: []EvalNode{
interpolate,
&EvalVariableBlock{ &EvalVariableBlock{
Config: &config, Config: &config,

View File

@ -17,6 +17,7 @@ type ModuleVariableTransformer struct {
Module *module.Tree Module *module.Tree
DisablePrune bool // True if pruning unreferenced should be disabled DisablePrune bool // True if pruning unreferenced should be disabled
Input bool // True if this is from an Input operation.
} }
func (t *ModuleVariableTransformer) Transform(g *Graph) error { func (t *ModuleVariableTransformer) Transform(g *Graph) error {
@ -99,6 +100,7 @@ func (t *ModuleVariableTransformer) transformSingle(g *Graph, parent, m *module.
Config: v, Config: v,
Value: value, Value: value,
Module: t.Module, Module: t.Module,
Input: t.Input,
} }
if !t.DisablePrune { if !t.DisablePrune {