mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-30 10:47:14 -06:00
a03c86f612
We'll need this again for getting the transitive depends_on references from parent module calls. This is needed to inform us how to handle data sources during refresh and plan.
301 lines
8.3 KiB
Go
301 lines
8.3 KiB
Go
package terraform
|
|
|
|
import (
|
|
"log"
|
|
|
|
"github.com/hashicorp/terraform/addrs"
|
|
"github.com/hashicorp/terraform/configs"
|
|
"github.com/hashicorp/terraform/dag"
|
|
"github.com/hashicorp/terraform/lang"
|
|
)
|
|
|
|
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)
|
|
_ GraphNodeReferenceOutside = (*nodeExpandModule)(nil)
|
|
_ graphNodeExpandsInstances = (*nodeExpandModule)(nil)
|
|
)
|
|
|
|
func (n *nodeExpandModule) expandsInstances() {}
|
|
|
|
func (n *nodeExpandModule) Name() string {
|
|
return n.Addr.String() + " (expand)"
|
|
}
|
|
|
|
// GraphNodeModulePath implementation
|
|
func (n *nodeExpandModule) ModulePath() addrs.Module {
|
|
return n.Addr
|
|
}
|
|
|
|
// GraphNodeReferencer implementation
|
|
func (n *nodeExpandModule) References() []*addrs.Reference {
|
|
var refs []*addrs.Reference
|
|
|
|
if n.ModuleCall == nil {
|
|
return nil
|
|
}
|
|
|
|
refs = append(refs, n.DependsOn()...)
|
|
|
|
// 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 {
|
|
countRefs, _ := lang.ReferencesInExpr(n.ModuleCall.Count)
|
|
refs = append(refs, countRefs...)
|
|
}
|
|
if n.ModuleCall.ForEach != nil {
|
|
forEachRefs, _ := lang.ReferencesInExpr(n.ModuleCall.ForEach)
|
|
refs = append(refs, forEachRefs...)
|
|
}
|
|
return appendResourceDestroyReferences(refs)
|
|
}
|
|
|
|
func (n *nodeExpandModule) DependsOn() []*addrs.Reference {
|
|
if n.ModuleCall == nil {
|
|
return nil
|
|
}
|
|
|
|
var refs []*addrs.Reference
|
|
for _, traversal := range n.ModuleCall.DependsOn {
|
|
ref, diags := addrs.ParseRef(traversal)
|
|
if diags.HasErrors() {
|
|
// We ignore this here, because this isn't a suitable place to return
|
|
// errors. This situation should be caught and rejected during
|
|
// validation.
|
|
log.Printf("[ERROR] Can't parse %#v from depends_on as reference: %s", traversal, diags.Err())
|
|
continue
|
|
}
|
|
|
|
refs = append(refs, ref)
|
|
}
|
|
|
|
return refs
|
|
}
|
|
|
|
// GraphNodeReferenceOutside
|
|
func (n *nodeExpandModule) ReferenceOutside() (selfPath, referencePath addrs.Module) {
|
|
return n.Addr, n.Addr.Parent()
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
var (
|
|
_ GraphNodeReferenceable = (*nodeCloseModule)(nil)
|
|
_ GraphNodeReferenceOutside = (*nodeCloseModule)(nil)
|
|
)
|
|
|
|
func (n *nodeCloseModule) ModulePath() addrs.Module {
|
|
return n.Addr
|
|
}
|
|
|
|
func (n *nodeCloseModule) ReferenceOutside() (selfPath, referencePath addrs.Module) {
|
|
return n.Addr.Parent(), n.Addr
|
|
}
|
|
|
|
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)"
|
|
}
|
|
|
|
// 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,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
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()
|
|
defer ctx.State().Unlock()
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
// empty child modules are always removed
|
|
if len(mod.Resources) == 0 && !mod.Addr.IsRoot() {
|
|
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 := 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:
|
|
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()) {
|
|
ctx = ctx.WithPath(module)
|
|
|
|
// Validate our for_each and count expressions at a basic level
|
|
// We skip validation on known, because there will be unknown values before
|
|
// a full expansion, presuming these errors will be caught in later steps
|
|
switch {
|
|
case n.ModuleCall.Count != nil:
|
|
_, diags := evaluateCountExpressionValue(n.ModuleCall.Count, ctx)
|
|
if diags.HasErrors() {
|
|
return nil, diags.Err()
|
|
}
|
|
|
|
case n.ModuleCall.ForEach != nil:
|
|
_, diags := evaluateForEachExpressionValue(n.ModuleCall.ForEach, ctx)
|
|
if diags.HasErrors() {
|
|
return nil, diags.Err()
|
|
}
|
|
}
|
|
|
|
// now set our own mode to single
|
|
expander.SetModuleSingle(module, call)
|
|
}
|
|
return nil, nil
|
|
}
|