Merge pull request #15780 from hashicorp/jbardin/input-module-vars
Allow module variable interpolation to fail during Input
This commit is contained in:
commit
d756e59824
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
variable "in" {}
|
||||||
|
|
||||||
|
output "out" {
|
||||||
|
value = "${var.in}"
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
data "null_data_source" "bar" {
|
||||||
|
foo = ["a", "b"]
|
||||||
|
}
|
||||||
|
|
||||||
|
module "child" {
|
||||||
|
source = "./child"
|
||||||
|
in = "${data.null_data_source.bar.foo[1]}"
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue