Mildwonkey/eval local (#26182)
* terraform: refactor EvalLocal, remove unused EvalDeleteLocal * terraform: refactor NodeCountBoundary * terraform: node_module_expand refactor
This commit is contained in:
parent
6f9ce7310c
commit
1a1225ae29
|
@ -1,76 +0,0 @@
|
||||||
package terraform
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
|
||||||
"github.com/hashicorp/terraform/configs"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EvalCountFixZeroOneBoundaryGlobal is an EvalNode that fixes up the state
|
|
||||||
// when there is a resource count with zero/one boundary, i.e. fixing
|
|
||||||
// a resource named "aws_instance.foo" to "aws_instance.foo.0" and vice-versa.
|
|
||||||
//
|
|
||||||
// This works on the global state.
|
|
||||||
type EvalCountFixZeroOneBoundaryGlobal struct {
|
|
||||||
Config *configs.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: test
|
|
||||||
func (n *EvalCountFixZeroOneBoundaryGlobal) Eval(ctx EvalContext) (interface{}, error) {
|
|
||||||
// We'll temporarily lock the state to grab the modules, then work on each
|
|
||||||
// one separately while taking a lock again for each separate resource.
|
|
||||||
// This means that if another caller concurrently adds a module here while
|
|
||||||
// we're working then we won't update it, but that's no worse than the
|
|
||||||
// concurrent writer blocking for our entire fixup process and _then_
|
|
||||||
// adding a new module, and in practice the graph node associated with
|
|
||||||
// this eval depends on everything else in the graph anyway, so there
|
|
||||||
// should not be concurrent writers.
|
|
||||||
state := ctx.State().Lock()
|
|
||||||
moduleAddrs := make([]addrs.ModuleInstance, 0, len(state.Modules))
|
|
||||||
for _, m := range state.Modules {
|
|
||||||
moduleAddrs = append(moduleAddrs, m.Addr)
|
|
||||||
}
|
|
||||||
ctx.State().Unlock()
|
|
||||||
|
|
||||||
for _, addr := range moduleAddrs {
|
|
||||||
cfg := n.Config.DescendentForInstance(addr)
|
|
||||||
if cfg == nil {
|
|
||||||
log.Printf("[WARN] Not fixing up EachModes for %s because it has no config", addr)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := n.fixModule(ctx, addr); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *EvalCountFixZeroOneBoundaryGlobal) fixModule(ctx EvalContext, moduleAddr addrs.ModuleInstance) error {
|
|
||||||
ms := ctx.State().Module(moduleAddr)
|
|
||||||
cfg := n.Config.DescendentForInstance(moduleAddr)
|
|
||||||
if ms == nil {
|
|
||||||
// Theoretically possible for a concurrent writer to delete a module
|
|
||||||
// while we're running, but in practice the graph node that called us
|
|
||||||
// depends on everything else in the graph and so there can never
|
|
||||||
// be a concurrent writer.
|
|
||||||
return fmt.Errorf("[WARN] no state found for %s while trying to fix up EachModes", moduleAddr)
|
|
||||||
}
|
|
||||||
if cfg == nil {
|
|
||||||
return fmt.Errorf("[WARN] no config found for %s while trying to fix up EachModes", moduleAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, r := range ms.Resources {
|
|
||||||
rCfg := cfg.Module.ResourceByAddr(r.Addr.Resource)
|
|
||||||
if rCfg == nil {
|
|
||||||
log.Printf("[WARN] Not fixing up EachModes for %s because it has no config", r.Addr)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
hasCount := rCfg.Count != nil
|
|
||||||
fixResourceCountSetTransition(ctx, r.Addr.Config(), hasCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
package terraform
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/hcl/v2"
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
|
||||||
"github.com/hashicorp/terraform/lang"
|
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EvalLocal is an EvalNode implementation that evaluates the
|
|
||||||
// expression for a local value and writes it into a transient part of
|
|
||||||
// the state.
|
|
||||||
type EvalLocal struct {
|
|
||||||
Addr addrs.LocalValue
|
|
||||||
Expr hcl.Expression
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *EvalLocal) Eval(ctx EvalContext) (interface{}, error) {
|
|
||||||
var diags tfdiags.Diagnostics
|
|
||||||
|
|
||||||
// We ignore diags here because any problems we might find will be found
|
|
||||||
// again in EvaluateExpr below.
|
|
||||||
refs, _ := lang.ReferencesInExpr(n.Expr)
|
|
||||||
for _, ref := range refs {
|
|
||||||
if ref.Subject == n.Addr {
|
|
||||||
diags = diags.Append(&hcl.Diagnostic{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Self-referencing local value",
|
|
||||||
Detail: fmt.Sprintf("Local value %s cannot use its own result as part of its expression.", n.Addr),
|
|
||||||
Subject: ref.SourceRange.ToHCL().Ptr(),
|
|
||||||
Context: n.Expr.Range().Ptr(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if diags.HasErrors() {
|
|
||||||
return nil, diags.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
val, moreDiags := ctx.EvaluateExpr(n.Expr, cty.DynamicPseudoType, nil)
|
|
||||||
diags = diags.Append(moreDiags)
|
|
||||||
if moreDiags.HasErrors() {
|
|
||||||
return nil, diags.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
state := ctx.State()
|
|
||||||
if state == nil {
|
|
||||||
return nil, fmt.Errorf("cannot write local value to nil state")
|
|
||||||
}
|
|
||||||
|
|
||||||
state.SetLocalValue(n.Addr.Absolute(ctx.Path()), val)
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EvalDeleteLocal is an EvalNode implementation that deletes a Local value
|
|
||||||
// from the state. Locals aren't persisted, but we don't need to evaluate them
|
|
||||||
// during destroy.
|
|
||||||
type EvalDeleteLocal struct {
|
|
||||||
Addr addrs.LocalValue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *EvalDeleteLocal) Eval(ctx EvalContext) (interface{}, error) {
|
|
||||||
state := ctx.State()
|
|
||||||
if state == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
state.RemoveLocalValue(n.Addr.Absolute(ctx.Path()))
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
|
@ -1,6 +1,10 @@
|
||||||
package terraform
|
package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/configs"
|
"github.com/hashicorp/terraform/configs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,9 +18,59 @@ func (n *NodeCountBoundary) Name() string {
|
||||||
return "meta.count-boundary (EachMode fixup)"
|
return "meta.count-boundary (EachMode fixup)"
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphNodeEvalable
|
// GraphNodeExecutable
|
||||||
func (n *NodeCountBoundary) EvalTree() EvalNode {
|
func (n *NodeCountBoundary) Execute(ctx EvalContext, op walkOperation) error {
|
||||||
return &EvalCountFixZeroOneBoundaryGlobal{
|
// We'll temporarily lock the state to grab the modules, then work on each
|
||||||
Config: n.Config,
|
// one separately while taking a lock again for each separate resource.
|
||||||
|
// This means that if another caller concurrently adds a module here while
|
||||||
|
// we're working then we won't update it, but that's no worse than the
|
||||||
|
// concurrent writer blocking for our entire fixup process and _then_
|
||||||
|
// adding a new module, and in practice the graph node associated with
|
||||||
|
// this eval depends on everything else in the graph anyway, so there
|
||||||
|
// should not be concurrent writers.
|
||||||
|
state := ctx.State().Lock()
|
||||||
|
moduleAddrs := make([]addrs.ModuleInstance, 0, len(state.Modules))
|
||||||
|
for _, m := range state.Modules {
|
||||||
|
moduleAddrs = append(moduleAddrs, m.Addr)
|
||||||
|
}
|
||||||
|
ctx.State().Unlock()
|
||||||
|
|
||||||
|
for _, addr := range moduleAddrs {
|
||||||
|
cfg := n.Config.DescendentForInstance(addr)
|
||||||
|
if cfg == nil {
|
||||||
|
log.Printf("[WARN] Not fixing up EachModes for %s because it has no config", addr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := n.fixModule(ctx, addr); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NodeCountBoundary) fixModule(ctx EvalContext, moduleAddr addrs.ModuleInstance) error {
|
||||||
|
ms := ctx.State().Module(moduleAddr)
|
||||||
|
cfg := n.Config.DescendentForInstance(moduleAddr)
|
||||||
|
if ms == nil {
|
||||||
|
// Theoretically possible for a concurrent writer to delete a module
|
||||||
|
// while we're running, but in practice the graph node that called us
|
||||||
|
// depends on everything else in the graph and so there can never
|
||||||
|
// be a concurrent writer.
|
||||||
|
return fmt.Errorf("[WARN] no state found for %s while trying to fix up EachModes", moduleAddr)
|
||||||
|
}
|
||||||
|
if cfg == nil {
|
||||||
|
return fmt.Errorf("[WARN] no config found for %s while trying to fix up EachModes", moduleAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range ms.Resources {
|
||||||
|
rCfg := cfg.Module.ResourceByAddr(r.Addr.Resource)
|
||||||
|
if rCfg == nil {
|
||||||
|
log.Printf("[WARN] Not fixing up EachModes for %s because it has no config", r.Addr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
hasCount := rCfg.Count != nil
|
||||||
|
fixResourceCountSetTransition(ctx, r.Addr.Config(), hasCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl/v2/hcltest"
|
||||||
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
"github.com/hashicorp/terraform/configs"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNodeCountBoundaryExecute(t *testing.T) {
|
||||||
|
|
||||||
|
// Create a state with a single instance (addrs.NoKey) of test_instance.foo
|
||||||
|
state := states.NewState()
|
||||||
|
state.Module(addrs.RootModuleInstance).SetResourceInstanceCurrent(
|
||||||
|
addrs.Resource{
|
||||||
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
Type: "test_instance",
|
||||||
|
Name: "foo",
|
||||||
|
}.Instance(addrs.NoKey),
|
||||||
|
&states.ResourceInstanceObjectSrc{
|
||||||
|
Status: states.ObjectReady,
|
||||||
|
AttrsJSON: []byte(`{"type":"string","value":"hello"}`),
|
||||||
|
},
|
||||||
|
addrs.AbsProviderConfig{
|
||||||
|
Provider: addrs.NewDefaultProvider("test"),
|
||||||
|
Module: addrs.RootModule,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create a config that uses count to create 2 instances of test_instance.foo
|
||||||
|
rc := &configs.Resource{
|
||||||
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
Type: "test_instance",
|
||||||
|
Name: "foo",
|
||||||
|
Count: hcltest.MockExprLiteral(cty.NumberIntVal(2)),
|
||||||
|
Config: configs.SynthBody("", map[string]cty.Value{
|
||||||
|
"test_string": cty.StringVal("hello"),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
config := &configs.Config{
|
||||||
|
Module: &configs.Module{
|
||||||
|
ManagedResources: map[string]*configs.Resource{
|
||||||
|
"test_instance.foo": rc,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := &MockEvalContext{
|
||||||
|
StateState: state.SyncWrapper(),
|
||||||
|
}
|
||||||
|
node := NodeCountBoundary{Config: config}
|
||||||
|
|
||||||
|
err := node.Execute(ctx, walkApply)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %s", err.Error())
|
||||||
|
}
|
||||||
|
if !state.HasResources() {
|
||||||
|
t.Fatal("resources missing from state")
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify that the resource changed from test_instance.foo to
|
||||||
|
// test_instance.foo.0 in the state
|
||||||
|
actual := state.String()
|
||||||
|
expected := "test_instance.foo.0:\n ID = \n provider = provider[\"registry.terraform.io/hashicorp/test\"]\n type = string\n value = hello"
|
||||||
|
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("wrong result: %s", actual)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,16 @@
|
||||||
package terraform
|
package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/configs"
|
"github.com/hashicorp/terraform/configs"
|
||||||
"github.com/hashicorp/terraform/dag"
|
"github.com/hashicorp/terraform/dag"
|
||||||
"github.com/hashicorp/terraform/lang"
|
"github.com/hashicorp/terraform/lang"
|
||||||
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
// nodeExpandLocal represents a named local value in a configuration module,
|
// nodeExpandLocal represents a named local value in a configuration module,
|
||||||
|
@ -85,7 +89,7 @@ var (
|
||||||
_ GraphNodeModuleInstance = (*NodeLocal)(nil)
|
_ GraphNodeModuleInstance = (*NodeLocal)(nil)
|
||||||
_ GraphNodeReferenceable = (*NodeLocal)(nil)
|
_ GraphNodeReferenceable = (*NodeLocal)(nil)
|
||||||
_ GraphNodeReferencer = (*NodeLocal)(nil)
|
_ GraphNodeReferencer = (*NodeLocal)(nil)
|
||||||
_ GraphNodeEvalable = (*NodeLocal)(nil)
|
_ GraphNodeExecutable = (*NodeLocal)(nil)
|
||||||
_ graphNodeTemporaryValue = (*NodeLocal)(nil)
|
_ graphNodeTemporaryValue = (*NodeLocal)(nil)
|
||||||
_ dag.GraphNodeDotter = (*NodeLocal)(nil)
|
_ dag.GraphNodeDotter = (*NodeLocal)(nil)
|
||||||
)
|
)
|
||||||
|
@ -120,13 +124,50 @@ func (n *NodeLocal) References() []*addrs.Reference {
|
||||||
return refs
|
return refs
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphNodeEvalable
|
// GraphNodeExecutable
|
||||||
func (n *NodeLocal) EvalTree() EvalNode {
|
// NodeLocal.Execute is an Execute implementation that evaluates the
|
||||||
return &EvalLocal{
|
// expression for a local value and writes it into a transient part of
|
||||||
Addr: n.Addr.LocalValue,
|
// the state.
|
||||||
Expr: n.Config.Expr,
|
func (n *NodeLocal) Execute(ctx EvalContext, op walkOperation) error {
|
||||||
|
|
||||||
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
|
expr := n.Config.Expr
|
||||||
|
addr := n.Addr.LocalValue
|
||||||
|
|
||||||
|
// We ignore diags here because any problems we might find will be found
|
||||||
|
// again in EvaluateExpr below.
|
||||||
|
refs, _ := lang.ReferencesInExpr(expr)
|
||||||
|
for _, ref := range refs {
|
||||||
|
if ref.Subject == addr {
|
||||||
|
diags = diags.Append(&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Self-referencing local value",
|
||||||
|
Detail: fmt.Sprintf("Local value %s cannot use its own result as part of its expression.", addr),
|
||||||
|
Subject: ref.SourceRange.ToHCL().Ptr(),
|
||||||
|
Context: expr.Range().Ptr(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return diags.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
val, moreDiags := ctx.EvaluateExpr(expr, cty.DynamicPseudoType, nil)
|
||||||
|
diags = diags.Append(moreDiags)
|
||||||
|
if moreDiags.HasErrors() {
|
||||||
|
return diags.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
state := ctx.State()
|
||||||
|
if state == nil {
|
||||||
|
return fmt.Errorf("cannot write local value to nil state")
|
||||||
|
}
|
||||||
|
|
||||||
|
state.SetLocalValue(addr.Absolute(ctx.Path()), val)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// dag.GraphNodeDotter impl.
|
// dag.GraphNodeDotter impl.
|
||||||
func (n *NodeLocal) DotNode(name string, opts *dag.DotOpts) *dag.DotNode {
|
func (n *NodeLocal) DotNode(name string, opts *dag.DotOpts) *dag.DotNode {
|
||||||
|
|
|
@ -10,15 +10,12 @@ import (
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
"github.com/hashicorp/terraform/configs"
|
||||||
"github.com/hashicorp/terraform/configs/hcl2shim"
|
"github.com/hashicorp/terraform/configs/hcl2shim"
|
||||||
"github.com/hashicorp/terraform/states"
|
"github.com/hashicorp/terraform/states"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEvalLocal_impl(t *testing.T) {
|
func TestNodeLocalExecute(t *testing.T) {
|
||||||
var _ EvalNode = new(EvalLocal)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvalLocal(t *testing.T) {
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
Value string
|
Value string
|
||||||
Want interface{}
|
Want interface{}
|
||||||
|
@ -48,9 +45,11 @@ func TestEvalLocal(t *testing.T) {
|
||||||
t.Fatal(diags.Error())
|
t.Fatal(diags.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
n := &EvalLocal{
|
n := &NodeLocal{
|
||||||
Addr: addrs.LocalValue{Name: "foo"},
|
Addr: addrs.LocalValue{Name: "foo"}.Absolute(addrs.RootModuleInstance),
|
||||||
|
Config: &configs.Local{
|
||||||
Expr: expr,
|
Expr: expr,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
ctx := &MockEvalContext{
|
ctx := &MockEvalContext{
|
||||||
StateState: states.NewState().SyncWrapper(),
|
StateState: states.NewState().SyncWrapper(),
|
||||||
|
@ -58,7 +57,7 @@ func TestEvalLocal(t *testing.T) {
|
||||||
EvaluateExprResult: hcl2shim.HCL2ValueFromConfigValue(test.Want),
|
EvaluateExprResult: hcl2shim.HCL2ValueFromConfigValue(test.Want),
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := n.Eval(ctx)
|
err := n.Execute(ctx, walkApply)
|
||||||
if (err != nil) != test.Err {
|
if (err != nil) != test.Err {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error: %s", err)
|
t.Errorf("unexpected error: %s", err)
|
|
@ -22,7 +22,7 @@ type nodeExpandModule struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ GraphNodeEvalable = (*nodeExpandModule)(nil)
|
_ GraphNodeExecutable = (*nodeExpandModule)(nil)
|
||||||
_ GraphNodeReferencer = (*nodeExpandModule)(nil)
|
_ GraphNodeReferencer = (*nodeExpandModule)(nil)
|
||||||
_ GraphNodeReferenceOutside = (*nodeExpandModule)(nil)
|
_ GraphNodeReferenceOutside = (*nodeExpandModule)(nil)
|
||||||
_ graphNodeExpandsInstances = (*nodeExpandModule)(nil)
|
_ graphNodeExpandsInstances = (*nodeExpandModule)(nil)
|
||||||
|
@ -98,13 +98,38 @@ func (n *nodeExpandModule) ReferenceOutside() (selfPath, referencePath addrs.Mod
|
||||||
return n.Addr, n.Addr.Parent()
|
return n.Addr, n.Addr.Parent()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphNodeEvalable
|
// GraphNodeExecutable
|
||||||
func (n *nodeExpandModule) EvalTree() EvalNode {
|
func (n *nodeExpandModule) Execute(ctx EvalContext, op walkOperation) error {
|
||||||
return &evalPrepareModuleExpansion{
|
expander := ctx.InstanceExpander()
|
||||||
Addr: n.Addr,
|
_, call := n.Addr.Call()
|
||||||
Config: n.Config,
|
|
||||||
ModuleCall: n.ModuleCall,
|
// nodeExpandModule itself does not have visibility into how its ancestors
|
||||||
|
// were expanded, so we use the expander here to provide all possible paths
|
||||||
|
// to our module, and register module instances with each of them.
|
||||||
|
for _, module := range expander.ExpandModule(n.Addr.Parent()) {
|
||||||
|
ctx = ctx.WithPath(module)
|
||||||
|
switch {
|
||||||
|
case n.ModuleCall.Count != nil:
|
||||||
|
count, diags := evaluateCountExpression(n.ModuleCall.Count, ctx)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return diags.Err()
|
||||||
}
|
}
|
||||||
|
expander.SetModuleCount(module, call, count)
|
||||||
|
|
||||||
|
case n.ModuleCall.ForEach != nil:
|
||||||
|
forEach, diags := evaluateForEachExpression(n.ModuleCall.ForEach, ctx)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return diags.Err()
|
||||||
|
}
|
||||||
|
expander.SetModuleForEach(module, call, forEach)
|
||||||
|
|
||||||
|
default:
|
||||||
|
expander.SetModuleSingle(module, call)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// nodeCloseModule represents an expanded module during apply, and is visited
|
// nodeCloseModule represents an expanded module during apply, and is visited
|
||||||
|
@ -145,26 +170,9 @@ func (n *nodeCloseModule) Name() string {
|
||||||
return n.Addr.String() + " (close)"
|
return n.Addr.String() + " (close)"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *nodeCloseModule) EvalTree() EvalNode {
|
func (n *nodeCloseModule) Execute(ctx EvalContext, op walkOperation) error {
|
||||||
return &EvalSequence{
|
switch op {
|
||||||
Nodes: []EvalNode{
|
case walkApply, walkDestroy:
|
||||||
&EvalOpFilter{
|
|
||||||
Ops: []walkOperation{walkApply, walkDestroy},
|
|
||||||
Node: &evalCloseModule{
|
|
||||||
Addr: n.Addr,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type evalCloseModule struct {
|
|
||||||
Addr addrs.Module
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *evalCloseModule) Eval(ctx EvalContext) (interface{}, error) {
|
|
||||||
// We need the full, locked state, because SyncState does not provide a way to
|
|
||||||
// transact over multiple module instances at the moment.
|
|
||||||
state := ctx.State().Lock()
|
state := ctx.State().Lock()
|
||||||
defer ctx.State().Unlock()
|
defer ctx.State().Unlock()
|
||||||
|
|
||||||
|
@ -185,50 +193,12 @@ func (n *evalCloseModule) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
delete(state.Modules, modKey)
|
delete(state.Modules, modKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil
|
||||||
}
|
|
||||||
|
|
||||||
// evalPrepareModuleExpansion is an EvalNode implementation
|
|
||||||
// that sets the count or for_each on the instance expander
|
|
||||||
type evalPrepareModuleExpansion struct {
|
|
||||||
Addr addrs.Module
|
|
||||||
Config *configs.Module
|
|
||||||
ModuleCall *configs.ModuleCall
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *evalPrepareModuleExpansion) Eval(ctx EvalContext) (interface{}, error) {
|
|
||||||
expander := ctx.InstanceExpander()
|
|
||||||
_, call := n.Addr.Call()
|
|
||||||
|
|
||||||
// nodeExpandModule itself does not have visibility into how its ancestors
|
|
||||||
// were expanded, so we use the expander here to provide all possible paths
|
|
||||||
// to our module, and register module instances with each of them.
|
|
||||||
for _, module := range expander.ExpandModule(n.Addr.Parent()) {
|
|
||||||
ctx = ctx.WithPath(module)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case n.ModuleCall.Count != nil:
|
|
||||||
count, diags := evaluateCountExpression(n.ModuleCall.Count, ctx)
|
|
||||||
if diags.HasErrors() {
|
|
||||||
return nil, diags.Err()
|
|
||||||
}
|
|
||||||
expander.SetModuleCount(module, call, count)
|
|
||||||
|
|
||||||
case n.ModuleCall.ForEach != nil:
|
|
||||||
forEach, diags := evaluateForEachExpression(n.ModuleCall.ForEach, ctx)
|
|
||||||
if diags.HasErrors() {
|
|
||||||
return nil, diags.Err()
|
|
||||||
}
|
|
||||||
expander.SetModuleForEach(module, call, forEach)
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
expander.SetModuleSingle(module, call)
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// nodeValidateModule wraps a nodeExpand module for validation, ensuring that
|
// nodeValidateModule wraps a nodeExpand module for validation, ensuring that
|
||||||
// no expansion is attempted during evaluation, when count and for_each
|
// no expansion is attempted during evaluation, when count and for_each
|
||||||
// expressions may not be known.
|
// expressions may not be known.
|
||||||
|
@ -237,21 +207,7 @@ type nodeValidateModule struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphNodeEvalable
|
// GraphNodeEvalable
|
||||||
func (n *nodeValidateModule) EvalTree() EvalNode {
|
func (n *nodeValidateModule) Execute(ctx EvalContext, op walkOperation) error {
|
||||||
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()
|
_, call := n.Addr.Call()
|
||||||
var diags tfdiags.Diagnostics
|
var diags tfdiags.Diagnostics
|
||||||
expander := ctx.InstanceExpander()
|
expander := ctx.InstanceExpander()
|
||||||
|
@ -283,8 +239,8 @@ func (n *evalValidateModule) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
return nil, diags.ErrWithWarnings()
|
return diags.ErrWithWarnings()
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl/v2/hcltest"
|
||||||
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
"github.com/hashicorp/terraform/configs"
|
||||||
|
"github.com/hashicorp/terraform/instances"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNodeExpandModuleExecute(t *testing.T) {
|
||||||
|
ctx := &MockEvalContext{
|
||||||
|
InstanceExpanderExpander: instances.NewExpander(),
|
||||||
|
}
|
||||||
|
ctx.installSimpleEval()
|
||||||
|
|
||||||
|
node := nodeExpandModule{
|
||||||
|
Addr: addrs.Module{"child"},
|
||||||
|
ModuleCall: &configs.ModuleCall{
|
||||||
|
Count: hcltest.MockExprLiteral(cty.NumberIntVal(2)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := node.Execute(ctx, walkApply)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ctx.InstanceExpanderCalled {
|
||||||
|
t.Fatal("did not expand")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNodeCloseModuleExecute(t *testing.T) {
|
||||||
|
t.Run("walkApply", func(t *testing.T) {
|
||||||
|
state := states.NewState()
|
||||||
|
state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
|
||||||
|
ctx := &MockEvalContext{
|
||||||
|
StateState: state.SyncWrapper(),
|
||||||
|
}
|
||||||
|
node := nodeCloseModule{addrs.Module{"child"}}
|
||||||
|
err := node.Execute(ctx, walkApply)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since module.child has no resources, it should be removed
|
||||||
|
if _, ok := state.Modules["module.child"]; ok {
|
||||||
|
t.Fatal("module.child was not removed from state")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// walkImport is a no-op
|
||||||
|
t.Run("walkImport", func(t *testing.T) {
|
||||||
|
state := states.NewState()
|
||||||
|
state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
|
||||||
|
ctx := &MockEvalContext{
|
||||||
|
StateState: state.SyncWrapper(),
|
||||||
|
}
|
||||||
|
node := nodeCloseModule{addrs.Module{"child"}}
|
||||||
|
|
||||||
|
err := node.Execute(ctx, walkImport)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %s", err.Error())
|
||||||
|
}
|
||||||
|
if _, ok := state.Modules["module.child"]; !ok {
|
||||||
|
t.Fatal("module.child was removed from state, expected no-op")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNodeValidateModuleExecute(t *testing.T) {
|
||||||
|
t.Run("success", func(t *testing.T) {
|
||||||
|
ctx := &MockEvalContext{
|
||||||
|
InstanceExpanderExpander: instances.NewExpander(),
|
||||||
|
}
|
||||||
|
ctx.installSimpleEval()
|
||||||
|
node := nodeValidateModule{
|
||||||
|
nodeExpandModule{
|
||||||
|
Addr: addrs.Module{"child"},
|
||||||
|
ModuleCall: &configs.ModuleCall{
|
||||||
|
Count: hcltest.MockExprLiteral(cty.NumberIntVal(2)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := node.Execute(ctx, walkApply)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %s", err.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid count", func(t *testing.T) {
|
||||||
|
ctx := &MockEvalContext{
|
||||||
|
InstanceExpanderExpander: instances.NewExpander(),
|
||||||
|
}
|
||||||
|
ctx.installSimpleEval()
|
||||||
|
node := nodeValidateModule{
|
||||||
|
nodeExpandModule{
|
||||||
|
Addr: addrs.Module{"child"},
|
||||||
|
ModuleCall: &configs.ModuleCall{
|
||||||
|
Count: hcltest.MockExprLiteral(cty.StringVal("invalid")),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := node.Execute(ctx, walkApply)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error, got success")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue