From 97bb7cb65c46ab9ab862ecae65d6943b04f37024 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Fri, 21 Jul 2017 12:41:29 -0400 Subject: [PATCH] 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. --- terraform/eval_interpolate.go | 31 +++++++++++++++++++++++++- terraform/eval_state.go | 4 +++- terraform/graph_builder_input.go | 3 +++ terraform/graph_builder_plan.go | 8 ++++++- terraform/node_module_variable.go | 23 +++++++++++++++---- terraform/transform_module_variable.go | 2 ++ 6 files changed, 64 insertions(+), 7 deletions(-) diff --git a/terraform/eval_interpolate.go b/terraform/eval_interpolate.go index 6825ff5909..df3bcb98e2 100644 --- a/terraform/eval_interpolate.go +++ b/terraform/eval_interpolate.go @@ -1,6 +1,10 @@ package terraform -import "github.com/hashicorp/terraform/config" +import ( + "log" + + "github.com/hashicorp/terraform/config" +) // EvalInterpolate is an EvalNode implementation that takes a raw // configuration and interpolates it. @@ -22,3 +26,28 @@ func (n *EvalInterpolate) Eval(ctx EvalContext) (interface{}, error) { 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 +} diff --git a/terraform/eval_state.go b/terraform/eval_state.go index 126a0e63a9..1f67e3d86b 100644 --- a/terraform/eval_state.go +++ b/terraform/eval_state.go @@ -1,6 +1,8 @@ package terraform -import "fmt" +import ( + "fmt" +) // EvalReadState is an EvalNode implementation that reads the // primary InstanceState for a specific resource out of the state. diff --git a/terraform/graph_builder_input.go b/terraform/graph_builder_input.go index 0df48cdb87..10fd8b1e97 100644 --- a/terraform/graph_builder_input.go +++ b/terraform/graph_builder_input.go @@ -10,6 +10,9 @@ import ( // and is based on the PlanGraphBuilder. The PlanGraphBuilder passed in will be // modified and should not be used for any other operations. func InputGraphBuilder(p *PlanGraphBuilder) GraphBuilder { + // convert this to an InputPlan + p.Input = true + // We're going to customize the concrete functions p.CustomConcrete = true diff --git a/terraform/graph_builder_plan.go b/terraform/graph_builder_plan.go index 4b29bbb4b8..9c7e4c1dbf 100644 --- a/terraform/graph_builder_plan.go +++ b/terraform/graph_builder_plan.go @@ -40,6 +40,9 @@ type PlanGraphBuilder struct { // Validate will do structural validation of the graph. Validate bool + // Input represents that this builder is for an Input operation. + Input bool + // CustomConcrete can be set to customize the node types created // for various parts of the plan. This is useful in order to customize // the plan behavior. @@ -107,7 +110,10 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer { ), // 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 // have to connect again later for providers and so on. diff --git a/terraform/node_module_variable.go b/terraform/node_module_variable.go index 13fe8fc3ae..63b84a9c53 100644 --- a/terraform/node_module_variable.go +++ b/terraform/node_module_variable.go @@ -15,6 +15,9 @@ type NodeApplyableModuleVariable struct { Value *config.RawConfig // Value is the value that is set 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 { @@ -92,12 +95,24 @@ func (n *NodeApplyableModuleVariable) EvalTree() EvalNode { // within the variables mapping. var config *ResourceConfig variables := make(map[string]interface{}) + + var interpolate EvalNode + + if n.Input { + interpolate = &EvalTryInterpolate{ + Config: n.Value, + Output: &config, + } + } else { + interpolate = &EvalInterpolate{ + Config: n.Value, + Output: &config, + } + } + return &EvalSequence{ Nodes: []EvalNode{ - &EvalInterpolate{ - Config: n.Value, - Output: &config, - }, + interpolate, &EvalVariableBlock{ Config: &config, diff --git a/terraform/transform_module_variable.go b/terraform/transform_module_variable.go index 467950bdc6..dbfd168717 100644 --- a/terraform/transform_module_variable.go +++ b/terraform/transform_module_variable.go @@ -17,6 +17,7 @@ type ModuleVariableTransformer struct { Module *module.Tree 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 { @@ -99,6 +100,7 @@ func (t *ModuleVariableTransformer) transformSingle(g *Graph, parent, m *module. Config: v, Value: value, Module: t.Module, + Input: t.Input, } if !t.DisablePrune {