mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-27 09:21:14 -06:00
e2c64bc255
Our diagnostics model allows for optionally annotating an error or warning with information about the expression and eval context it was generated from, which the diagnostic renderer for the UI will then use to give the user some additional hints about what values may have contributed to the error. We previously didn't have those annotations on the results of evaluating for_each expressions though, because in that case we were using the helper function to evaluate an expression in one shot and thus we didn't ever have a reference to the EvalContext in order to include it in the diagnostic values. Now, at the expense of having to handle the evaluation at a slightly lower level of abstraction, we'll annotate all of the for_each error messages with source expression information. This is valuable because we see users often confused as to how their complex for_each expressions ended up being invalid, and hopefully giving some information about what the inputs were will allow more users to self-solve.
247 lines
7.1 KiB
Go
247 lines
7.1 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"
|
|
"github.com/hashicorp/terraform/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(n.ModuleCall.Count)
|
|
refs = append(refs, countRefs...)
|
|
}
|
|
if n.ModuleCall.ForEach != nil {
|
|
forEachRefs, _ := lang.ReferencesInExpr(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.
|
|
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) {
|
|
switch op {
|
|
case walkApply, walkDestroy:
|
|
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
|
|
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
|
|
}
|