mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-25 08:21:07 -06:00
256 lines
7.5 KiB
Go
256 lines
7.5 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package tofu
|
|
|
|
import (
|
|
"log"
|
|
|
|
"github.com/opentofu/opentofu/internal/addrs"
|
|
"github.com/opentofu/opentofu/internal/configs"
|
|
"github.com/opentofu/opentofu/internal/dag"
|
|
"github.com/opentofu/opentofu/internal/lang"
|
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
|
)
|
|
|
|
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 (
|
|
_ GraphNodeExecutable = (*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(addrs.ParseRef, n.ModuleCall.Count)
|
|
refs = append(refs, countRefs...)
|
|
}
|
|
if n.ModuleCall.ForEach != nil {
|
|
forEachRefs, _ := lang.ReferencesInExpr(addrs.ParseRef, n.ModuleCall.ForEach)
|
|
refs = append(refs, forEachRefs...)
|
|
}
|
|
return 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()
|
|
}
|
|
|
|
// GraphNodeExecutable
|
|
func (n *nodeExpandModule) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) {
|
|
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, ctDiags := evaluateCountExpression(n.ModuleCall.Count, ctx)
|
|
diags = diags.Append(ctDiags)
|
|
if diags.HasErrors() {
|
|
return diags
|
|
}
|
|
expander.SetModuleCount(module, call, count)
|
|
|
|
case n.ModuleCall.ForEach != nil:
|
|
forEach, feDiags := evaluateForEachExpression(n.ModuleCall.ForEach, ctx)
|
|
diags = diags.Append(feDiags)
|
|
if diags.HasErrors() {
|
|
return diags
|
|
}
|
|
expander.SetModuleForEach(module, call, forEach)
|
|
|
|
default:
|
|
expander.SetModuleSingle(module, call)
|
|
}
|
|
}
|
|
|
|
return diags
|
|
|
|
}
|
|
|
|
// 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.
|
|
// The root module instance also closes any remaining provisioner plugins which
|
|
// do not have a lifecycle controlled by individual graph nodes.
|
|
type nodeCloseModule struct {
|
|
Addr addrs.Module
|
|
}
|
|
|
|
var (
|
|
_ GraphNodeReferenceable = (*nodeCloseModule)(nil)
|
|
_ GraphNodeReferenceOutside = (*nodeCloseModule)(nil)
|
|
_ GraphNodeExecutable = (*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)"
|
|
}
|
|
|
|
func (n *nodeCloseModule) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) {
|
|
if !n.Addr.IsRoot() {
|
|
return
|
|
}
|
|
|
|
// If this is the root module, we are cleaning up the walk, so close
|
|
// any running provisioners
|
|
diags = diags.Append(ctx.CloseProvisioners())
|
|
|
|
switch op {
|
|
case walkApply, walkDestroy:
|
|
state := ctx.State().Lock()
|
|
defer ctx.State().Unlock()
|
|
|
|
for modKey, mod := range state.Modules {
|
|
// 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
|
|
default:
|
|
return 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
|
|
}
|
|
|
|
var _ GraphNodeExecutable = (*nodeValidateModule)(nil)
|
|
|
|
// GraphNodeEvalable
|
|
func (n *nodeValidateModule) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) {
|
|
_, 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:
|
|
_, countDiags := evaluateCountExpressionValue(n.ModuleCall.Count, ctx)
|
|
diags = diags.Append(countDiags)
|
|
|
|
case n.ModuleCall.ForEach != nil:
|
|
_, forEachDiags := evaluateForEachExpressionValue(n.ModuleCall.ForEach, ctx, true)
|
|
diags = diags.Append(forEachDiags)
|
|
}
|
|
|
|
diags = diags.Append(validateDependsOn(ctx, n.ModuleCall.DependsOn))
|
|
|
|
// now set our own mode to single
|
|
expander.SetModuleSingle(module, call)
|
|
}
|
|
|
|
return diags
|
|
}
|