opentofu/terraform/node_module_expand.go
James Bardin 700e20de5d connect references to the module closer
NodeModuleRemoved is redundant now with the concept of
nodeCloseModule, so we can replace it within the graph. This does mean
that nodeCloseModule needs to know if it's evaluating an orphaned module
that can't be expanded, but the overhead to checking this isn't too
bad.

Now that nodeModuleClose is referenceable, and we can ensure it's always
in the graph at the correct time, we can eliminate the need to connect
each resource to every single node within a module it references, and
instead connect only to the nodeModuleClose, which acts as the module
root. Since module expansion can cause exponential growth in the number
of edges in graphs, this will help with performance problems when
transforming and reducing these graphs by eliminating the bulk of
redundant edges. This will also help with general debugging, making the
graphs easier to read.
2020-04-08 12:30:35 -04:00

289 lines
7.9 KiB
Go

package terraform
import (
"fmt"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/dag"
"github.com/hashicorp/terraform/lang"
)
// graphNodeModuleCloser is an interface implemented by nodes that finalize the
// evaluation of modules.
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.
type nodeExpandModule struct {
Addr addrs.Module
Config *configs.Module
ModuleCall *configs.ModuleCall
}
var (
_ RemovableIfNotTargeted = (*nodeExpandModule)(nil)
_ GraphNodeEvalable = (*nodeExpandModule)(nil)
_ GraphNodeReferencer = (*nodeExpandModule)(nil)
)
func (n *nodeExpandModule) Name() string {
return n.Addr.String()
}
// GraphNodeModulePath implementation
func (n *nodeExpandModule) ModulePath() addrs.Module {
// This node represents the module call within a module,
// so return the CallerAddr as the path as the module
// call may expand into multiple child instances
return n.Addr.Parent()
}
// GraphNodeReferencer implementation
func (n *nodeExpandModule) References() []*addrs.Reference {
var refs []*addrs.Reference
if n.ModuleCall == nil {
return nil
}
// Expansion only uses the count and for_each expressions, so this
// particular graph node only refers to those.
// Individual variable values in the module call definition might also
// refer to other objects, but that's handled by
// NodeApplyableModuleVariable.
//
// Because our Path method returns the module instance that contains
// our call, these references will be correctly interpreted as being
// in the calling module's namespace, not the namespaces of any of the
// child module instances we might expand to during our evaluation.
if n.ModuleCall.Count != nil {
refs, _ = lang.ReferencesInExpr(n.ModuleCall.Count)
}
if n.ModuleCall.ForEach != nil {
refs, _ = lang.ReferencesInExpr(n.ModuleCall.ForEach)
}
return appendResourceDestroyReferences(refs)
}
// RemovableIfNotTargeted implementation
func (n *nodeExpandModule) RemoveIfNotTargeted() bool {
// We need to add this so that this node will be removed if
// it isn't targeted or a dependency of a target.
return true
}
// GraphNodeEvalable
func (n *nodeExpandModule) EvalTree() EvalNode {
return &evalPrepareModuleExpansion{
Addr: n.Addr,
Config: n.Config,
ModuleCall: n.ModuleCall,
}
}
// nodeCloseModule represents an expanded module during apply, and is visited
// after all other module instance nodes. This node will depend on all module
// instance resource and outputs, and anything depending on the module should
// wait on this node.
// Besides providing a root node for dependency ordering, nodeCloseModule also
// cleans up state after all the module nodes have been evaluated, removing
// empty resources and modules from the state.
type nodeCloseModule struct {
Addr addrs.Module
// orphaned indicates that this module has no expansion, because it no
// longer exists in the configuration
orphaned bool
}
var (
_ graphNodeModuleCloser = (*nodeCloseModule)(nil)
_ GraphNodeReferenceable = (*nodeCloseModule)(nil)
)
func (n *nodeCloseModule) ModulePath() addrs.Module {
mod, _ := n.Addr.Call()
return mod
}
func (n *nodeCloseModule) ReferenceableAddrs() []addrs.Referenceable {
_, call := n.Addr.Call()
return []addrs.Referenceable{
call,
}
}
func (n *nodeCloseModule) Name() string {
if len(n.Addr) == 0 {
return "root"
}
return n.Addr.String() + " (close)"
}
func (n *nodeCloseModule) CloseModule() addrs.Module {
return n.Addr
}
// RemovableIfNotTargeted implementation
func (n *nodeCloseModule) RemoveIfNotTargeted() bool {
// We need to add this so that this node will be removed if
// it isn't targeted or a dependency of a target.
return true
}
func (n *nodeCloseModule) EvalTree() EvalNode {
return &EvalSequence{
Nodes: []EvalNode{
&EvalOpFilter{
Ops: []walkOperation{walkApply, walkDestroy},
Node: &evalCloseModule{
Addr: n.Addr,
orphaned: n.orphaned,
},
},
},
}
}
type evalCloseModule struct {
Addr addrs.Module
orphaned bool
}
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()
defer ctx.State().Unlock()
expander := ctx.InstanceExpander()
var currentModuleInstances []addrs.ModuleInstance
// we can't expand if we're just removing
if !n.orphaned {
currentModuleInstances = expander.ExpandModule(n.Addr)
}
for modKey, mod := range state.Modules {
if !n.Addr.Equal(mod.Addr.Module()) {
continue
}
// clean out any empty resources
for resKey, res := range mod.Resources {
if len(res.Instances) == 0 {
delete(mod.Resources, resKey)
}
}
found := false
if n.orphaned {
// we're removing the entire module, so all instances must go
found = true
} else {
// if this instance is not in the current expansion, remove it from
// the state
for _, current := range currentModuleInstances {
if current.Equal(mod.Addr) {
found = true
break
}
}
}
if !found {
if len(mod.Resources) > 0 {
// FIXME: add more info to this error
return nil, fmt.Errorf("module %q still contains resources in state", mod.Addr)
}
delete(state.Modules, modKey)
}
}
return nil, 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 := evaluateResourceCountExpression(n.ModuleCall.Count, ctx)
if diags.HasErrors() {
return nil, diags.Err()
}
expander.SetModuleCount(module, call, count)
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)
}
}
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
}