opentofu/internal/terraform/node_check.go

205 lines
6.0 KiB
Go
Raw Normal View History

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package terraform
import (
"log"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/checks"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/dag"
"github.com/hashicorp/terraform/internal/lang"
"github.com/hashicorp/terraform/internal/tfdiags"
)
var (
_ GraphNodeModulePath = (*nodeReportCheck)(nil)
_ GraphNodeExecutable = (*nodeReportCheck)(nil)
)
// nodeReportCheck calls the ReportCheckableObjects function for our assertions
// within the check blocks.
//
// We need this to happen before the checks are actually verified and before any
// nested data blocks, so the creator of this structure should make sure this
// node is a parent of any nested data blocks.
//
// This needs to be separate to nodeExpandCheck, because the actual checks
// should happen after referenced data blocks rather than before.
type nodeReportCheck struct {
addr addrs.ConfigCheck
}
func (n *nodeReportCheck) ModulePath() addrs.Module {
return n.addr.Module
}
func (n *nodeReportCheck) Execute(ctx EvalContext, _ walkOperation) tfdiags.Diagnostics {
exp := ctx.InstanceExpander()
modInsts := exp.ExpandModule(n.ModulePath())
instAddrs := addrs.MakeSet[addrs.Checkable]()
for _, modAddr := range modInsts {
instAddrs.Add(n.addr.Check.Absolute(modAddr))
}
ctx.Checks().ReportCheckableObjects(n.addr, instAddrs)
return nil
}
func (n *nodeReportCheck) Name() string {
return n.addr.String() + " (report)"
}
var (
_ GraphNodeModulePath = (*nodeExpandCheck)(nil)
_ GraphNodeDynamicExpandable = (*nodeExpandCheck)(nil)
_ GraphNodeReferencer = (*nodeExpandCheck)(nil)
)
// nodeExpandCheck creates child nodes that actually execute the assertions for
// a given check block.
//
// This must happen after any other nodes/resources/data sources that are
// referenced, so we implement GraphNodeReferencer.
//
// This needs to be separate to nodeReportCheck as nodeReportCheck must happen
// first, while nodeExpandCheck must execute after any referenced blocks.
type nodeExpandCheck struct {
addr addrs.ConfigCheck
config *configs.Check
makeInstance func(addrs.AbsCheck, *configs.Check) dag.Vertex
}
func (n *nodeExpandCheck) ModulePath() addrs.Module {
return n.addr.Module
}
func (n *nodeExpandCheck) DynamicExpand(ctx EvalContext) (*Graph, error) {
exp := ctx.InstanceExpander()
modInsts := exp.ExpandModule(n.ModulePath())
var g Graph
for _, modAddr := range modInsts {
testAddr := n.addr.Check.Absolute(modAddr)
log.Printf("[TRACE] nodeExpandCheck: Node for %s", testAddr)
g.Add(n.makeInstance(testAddr, n.config))
}
addRootNodeToGraph(&g)
return &g, nil
}
func (n *nodeExpandCheck) References() []*addrs.Reference {
var refs []*addrs.Reference
for _, assert := range n.config.Asserts {
// Check blocks reference anything referenced by conditions or messages
// in their check rules.
condition, _ := lang.ReferencesInExpr(assert.Condition)
message, _ := lang.ReferencesInExpr(assert.ErrorMessage)
refs = append(refs, condition...)
refs = append(refs, message...)
}
if n.config.DataResource != nil {
// We'll also always reference our nested data block if it exists, as
// there is nothing enforcing that it has to also be referenced by our
// conditions or messages.
//
// We don't need to make this addr absolute, because the check block and
// the data resource are always within the same module/instance.
traversal, _ := hclsyntax.ParseTraversalAbs(
[]byte(n.config.DataResource.Addr().String()),
n.config.DataResource.DeclRange.Filename,
n.config.DataResource.DeclRange.Start)
ref, _ := addrs.ParseRef(traversal)
refs = append(refs, ref)
}
return refs
}
func (n *nodeExpandCheck) Name() string {
return n.addr.String() + " (expand)"
}
var (
_ GraphNodeModuleInstance = (*nodeCheckAssert)(nil)
_ GraphNodeExecutable = (*nodeCheckAssert)(nil)
)
type nodeCheckAssert struct {
addr addrs.AbsCheck
config *configs.Check
// We only want to actually execute the checks during the plan and apply
// operations, but we still want to validate our config during
// other operations.
executeChecks bool
}
func (n *nodeCheckAssert) ModulePath() addrs.Module {
return n.Path().Module()
}
func (n *nodeCheckAssert) Path() addrs.ModuleInstance {
return n.addr.Module
}
func (n *nodeCheckAssert) Execute(ctx EvalContext, _ walkOperation) tfdiags.Diagnostics {
// We only want to actually execute the checks during specific
// operations, such as plan and applies.
if n.executeChecks {
if status := ctx.Checks().ObjectCheckStatus(n.addr); status == checks.StatusFail || status == checks.StatusError {
// This check is already failing, so we won't try and evaluate it.
// This typically means there was an error in a data block within
// the check block.
return nil
}
return evalCheckRules(
addrs.CheckAssertion,
n.config.Asserts,
ctx,
n.addr,
EvalDataForNoInstanceKey,
tfdiags.Warning)
}
// Otherwise let's still validate the config and references and return
// diagnostics if references do not exist etc.
var diags tfdiags.Diagnostics
for _, assert := range n.config.Asserts {
_, _, moreDiags := validateCheckRule(addrs.CheckAssertion, assert, ctx, n.addr, EvalDataForNoInstanceKey)
diags = diags.Append(moreDiags)
}
return diags
}
func (n *nodeCheckAssert) Name() string {
return n.addr.String() + " (assertions)"
}
var (
_ GraphNodeExecutable = (*nodeCheckStart)(nil)
)
// We need to ensure that any nested data sources execute after all other
// resource changes have been applied. This node acts as a single point of
// dependency that can enforce this ordering.
type nodeCheckStart struct{}
func (n *nodeCheckStart) Execute(context EvalContext, operation walkOperation) tfdiags.Diagnostics {
// This node doesn't actually do anything, except simplify the underlying
// graph structure.
return nil
}
func (n *nodeCheckStart) Name() string {
return "(execute checks)"
}