Merge pull request #24565 from hashicorp/jbardin/validate-module-expansion
Create module validation nodes
This commit is contained in:
commit
fd4342668a
|
@ -1518,3 +1518,58 @@ variable "test" {
|
|||
t.Fatalf("unexpected error\ngot: %s", diags.Err().Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Validate_expandModules(t *testing.T) {
|
||||
m := testModuleInline(t, map[string]string{
|
||||
"main.tf": `
|
||||
module "mod1" {
|
||||
for_each = toset(["a", "b"])
|
||||
source = "./mod"
|
||||
}
|
||||
|
||||
module "mod2" {
|
||||
for_each = module.mod1
|
||||
source = "./mod"
|
||||
}
|
||||
|
||||
module "mod3" {
|
||||
count = len(module.mod2)
|
||||
source = "./mod"
|
||||
}
|
||||
`,
|
||||
"mod/main.tf": `
|
||||
resource "aws_instance" "foo" {
|
||||
}
|
||||
|
||||
module "nested" {
|
||||
count = 2
|
||||
source = "./nested"
|
||||
input = 2
|
||||
}
|
||||
`,
|
||||
"mod/nested/main.tf": `
|
||||
variable "input" {
|
||||
}
|
||||
|
||||
resource "aws_instance" "foo" {
|
||||
count = var.input
|
||||
}
|
||||
`,
|
||||
})
|
||||
|
||||
p := testProvider("aws")
|
||||
p.DiffFn = testDiffFn
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Config: m,
|
||||
ProviderResolver: providers.ResolverFixed(
|
||||
map[addrs.Provider]providers.Factory{
|
||||
addrs.NewLegacyProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
),
|
||||
})
|
||||
|
||||
diags := ctx.Validate()
|
||||
if diags.HasErrors() {
|
||||
t.Fatal(diags.ErrWithWarnings())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,6 +52,7 @@ type PlanGraphBuilder struct {
|
|||
ConcreteProvider ConcreteProviderNodeFunc
|
||||
ConcreteResource ConcreteResourceNodeFunc
|
||||
ConcreteResourceOrphan ConcreteResourceInstanceNodeFunc
|
||||
ConcreteModule ConcreteModuleNodeFunc
|
||||
|
||||
once sync.Once
|
||||
}
|
||||
|
@ -140,7 +141,10 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
|
|||
// Create expansion nodes for all of the module calls. This must
|
||||
// come after all other transformers that create nodes representing
|
||||
// objects that can belong to modules.
|
||||
&ModuleExpansionTransformer{Config: b.Config},
|
||||
&ModuleExpansionTransformer{
|
||||
Concrete: b.ConcreteModule,
|
||||
Config: b.Config,
|
||||
},
|
||||
|
||||
// Connect so that the references are ready for targeting. We'll
|
||||
// have to connect again later for providers and so on.
|
||||
|
|
|
@ -27,6 +27,12 @@ func ValidateGraphBuilder(p *PlanGraphBuilder) GraphBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
p.ConcreteModule = func(n *nodeExpandModule) dag.Vertex {
|
||||
return &nodeValidateModule{
|
||||
nodeExpandModule: *n,
|
||||
}
|
||||
}
|
||||
|
||||
// We purposely don't set any other concrete types since they don't
|
||||
// require validation.
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@ import (
|
|||
|
||||
"github.com/hashicorp/terraform/addrs"
|
||||
"github.com/hashicorp/terraform/configs"
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
"github.com/hashicorp/terraform/lang"
|
||||
"github.com/hashicorp/terraform/states"
|
||||
)
|
||||
|
||||
// graphNodeModuleCloser is an interface implemented by nodes that finalize the
|
||||
|
@ -15,6 +15,8 @@ type graphNodeModuleCloser interface {
|
|||
CloseModule() addrs.Module
|
||||
}
|
||||
|
||||
type ConcreteModuleNodeFunc func(n *nodeExpandModule) dag.Vertex
|
||||
|
||||
// nodeExpandModule represents a module call in the configuration that
|
||||
// might expand into multiple module instances depending on how it is
|
||||
// configured.
|
||||
|
@ -184,9 +186,7 @@ type evalPrepareModuleExpansion struct {
|
|||
}
|
||||
|
||||
func (n *evalPrepareModuleExpansion) Eval(ctx EvalContext) (interface{}, error) {
|
||||
eachMode := states.NoEach
|
||||
expander := ctx.InstanceExpander()
|
||||
|
||||
_, call := n.Addr.Call()
|
||||
|
||||
// nodeExpandModule itself does not have visibility into how its ancestors
|
||||
|
@ -194,29 +194,22 @@ func (n *evalPrepareModuleExpansion) Eval(ctx EvalContext) (interface{}, error)
|
|||
// to our module, and register module instances with each of them.
|
||||
for _, module := range expander.ExpandModule(n.Addr.Parent()) {
|
||||
ctx = ctx.WithPath(module)
|
||||
count, countDiags := evaluateResourceCountExpression(n.ModuleCall.Count, ctx)
|
||||
if countDiags.HasErrors() {
|
||||
return nil, countDiags.Err()
|
||||
}
|
||||
|
||||
if count >= 0 { // -1 signals "count not set"
|
||||
eachMode = states.EachList
|
||||
}
|
||||
|
||||
forEach, forEachDiags := evaluateResourceForEachExpression(n.ModuleCall.ForEach, ctx)
|
||||
if forEachDiags.HasErrors() {
|
||||
return nil, forEachDiags.Err()
|
||||
}
|
||||
|
||||
if forEach != nil {
|
||||
eachMode = states.EachMap
|
||||
}
|
||||
|
||||
switch eachMode {
|
||||
case states.EachList:
|
||||
switch {
|
||||
case n.ModuleCall.Count != nil:
|
||||
count, diags := evaluateResourceCountExpression(n.ModuleCall.Count, ctx)
|
||||
if diags.HasErrors() {
|
||||
return nil, diags.Err()
|
||||
}
|
||||
expander.SetModuleCount(module, call, count)
|
||||
case states.EachMap:
|
||||
|
||||
case n.ModuleCall.ForEach != nil:
|
||||
forEach, diags := evaluateResourceForEachExpression(n.ModuleCall.ForEach, ctx)
|
||||
if diags.HasErrors() {
|
||||
return nil, diags.Err()
|
||||
}
|
||||
expander.SetModuleForEach(module, call, forEach)
|
||||
|
||||
default:
|
||||
expander.SetModuleSingle(module, call)
|
||||
}
|
||||
|
@ -224,3 +217,40 @@ func (n *evalPrepareModuleExpansion) Eval(ctx EvalContext) (interface{}, error)
|
|||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// nodeValidateModule wraps a nodeExpand module for validation, ensuring that
|
||||
// no expansion is attempted during evaluation, when count and for_each
|
||||
// expressions may not be known.
|
||||
type nodeValidateModule struct {
|
||||
nodeExpandModule
|
||||
}
|
||||
|
||||
// GraphNodeEvalable
|
||||
func (n *nodeValidateModule) EvalTree() EvalNode {
|
||||
return &evalValidateModule{
|
||||
Addr: n.Addr,
|
||||
Config: n.Config,
|
||||
ModuleCall: n.ModuleCall,
|
||||
}
|
||||
}
|
||||
|
||||
type evalValidateModule struct {
|
||||
Addr addrs.Module
|
||||
Config *configs.Module
|
||||
ModuleCall *configs.ModuleCall
|
||||
}
|
||||
|
||||
func (n *evalValidateModule) Eval(ctx EvalContext) (interface{}, error) {
|
||||
_, call := n.Addr.Call()
|
||||
expander := ctx.InstanceExpander()
|
||||
|
||||
// Modules all evaluate to single instances during validation, only to
|
||||
// create a proper context within which to evaluate. All parent modules
|
||||
// will be a single instance, but still get our address in the expected
|
||||
// manner anyway to ensure they've been registered correctly.
|
||||
for _, module := range expander.ExpandModule(n.Addr.Parent()) {
|
||||
// now set our own mode to single
|
||||
ctx.InstanceExpander().SetModuleSingle(module, call)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
@ -17,6 +17,10 @@ import (
|
|||
// that can be contained within modules have already been added.
|
||||
type ModuleExpansionTransformer struct {
|
||||
Config *configs.Config
|
||||
|
||||
// Concrete allows injection of a wrapped module node by the graph builder
|
||||
// to alter the evaluation behavior.
|
||||
Concrete ConcreteModuleNodeFunc
|
||||
}
|
||||
|
||||
func (t *ModuleExpansionTransformer) Transform(g *Graph) error {
|
||||
|
@ -36,11 +40,16 @@ func (t *ModuleExpansionTransformer) transform(g *Graph, c *configs.Config, pare
|
|||
_, call := c.Path.Call()
|
||||
modCall := c.Parent.Module.ModuleCalls[call.Name]
|
||||
|
||||
v := &nodeExpandModule{
|
||||
n := &nodeExpandModule{
|
||||
Addr: c.Path,
|
||||
Config: c.Module,
|
||||
ModuleCall: modCall,
|
||||
}
|
||||
var v dag.Vertex = n
|
||||
if t.Concrete != nil {
|
||||
v = t.Concrete(n)
|
||||
}
|
||||
|
||||
g.Add(v)
|
||||
log.Printf("[TRACE] ModuleExpansionTransformer: Added %s as %T", c.Path, v)
|
||||
|
||||
|
|
Loading…
Reference in New Issue