Merge pull request #15780 from hashicorp/jbardin/input-module-vars

Allow module variable interpolation to fail during Input
This commit is contained in:
James Bardin 2017-08-11 10:24:27 -04:00 committed by GitHub
commit d756e59824
9 changed files with 131 additions and 7 deletions

View File

@ -719,3 +719,57 @@ func TestContext2Input_submoduleTriggersInvalidCount(t *testing.T) {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
} }
// In this case, a module variable can't be resolved from a data source until
// it's refreshed, but it can't be refreshed during Input.
func TestContext2Input_dataSourceRequiresRefresh(t *testing.T) {
input := new(MockUIInput)
p := testProvider("null")
m := testModule(t, "input-module-data-vars")
p.ReadDataDiffFn = testDataDiffFn
state := &State{
Modules: []*ModuleState{
&ModuleState{
Path: rootModulePath,
Resources: map[string]*ResourceState{
"data.null_data_source.bar": &ResourceState{
Type: "null_data_source",
Primary: &InstanceState{
ID: "-",
Attributes: map[string]string{
"foo.#": "1",
"foo.0": "a",
// foo.1 exists in the data source, but needs to be refreshed.
},
},
},
},
},
},
}
ctx := testContext2(t, &ContextOpts{
Module: m,
ProviderResolver: ResourceProviderResolverFixed(
map[string]ResourceProviderFactory{
"null": testProviderFuncFixed(p),
},
),
State: state,
UIInput: input,
})
if err := ctx.Input(InputModeStd); err != nil {
t.Fatalf("err: %s", err)
}
// ensure that plan works after Refresh
if _, err := ctx.Refresh(); err != nil {
t.Fatalf("err: %s", err)
}
if _, err := ctx.Plan(); err != nil {
t.Fatalf("err: %s", err)
}
}

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{})
var interpolate EvalNode
if n.Input {
interpolate = &EvalTryInterpolate{
Config: n.Value,
Output: &config,
}
} else {
interpolate = &EvalInterpolate{
Config: n.Value,
Output: &config,
}
}
return &EvalSequence{ return &EvalSequence{
Nodes: []EvalNode{ Nodes: []EvalNode{
&EvalInterpolate{ interpolate,
Config: n.Value,
Output: &config,
},
&EvalVariableBlock{ &EvalVariableBlock{
Config: &config, Config: &config,

View File

@ -0,0 +1,5 @@
variable "in" {}
output "out" {
value = "${var.in}"
}

View File

@ -0,0 +1,8 @@
data "null_data_source" "bar" {
foo = ["a", "b"]
}
module "child" {
source = "./child"
in = "${data.null_data_source.bar.foo[1]}"
}

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 {