mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-28 01:41:48 -06:00
146 lines
4.4 KiB
Go
146 lines
4.4 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"
|
|
)
|
|
|
|
type checkTransformer struct {
|
|
// Config for the entire module.
|
|
Config *configs.Config
|
|
|
|
// Operation is the current operation this node will be part of.
|
|
Operation walkOperation
|
|
}
|
|
|
|
var _ GraphTransformer = (*checkTransformer)(nil)
|
|
|
|
func (t *checkTransformer) Transform(graph *Graph) error {
|
|
return t.transform(graph, t.Config, graph.Vertices())
|
|
}
|
|
|
|
func (t *checkTransformer) transform(g *Graph, cfg *configs.Config, allNodes []dag.Vertex) error {
|
|
|
|
if t.Operation == walkDestroy || t.Operation == walkPlanDestroy {
|
|
// Don't include anything about checks during destroy operations.
|
|
//
|
|
// For other plan and normal apply operations we do everything, for
|
|
// destroy operations we do nothing. For any other operations we still
|
|
// include the check nodes, but we don't actually execute the checks
|
|
// instead we still validate their references and make sure their
|
|
// conditions make sense etc.
|
|
return nil
|
|
}
|
|
|
|
moduleAddr := cfg.Path
|
|
|
|
for _, check := range cfg.Module.Checks {
|
|
configAddr := check.Addr().InModule(moduleAddr)
|
|
|
|
// We want to create a node for each check block. This node will execute
|
|
// after anything it references, and will update the checks object
|
|
// embedded in the plan and/or state.
|
|
|
|
log.Printf("[TRACE] checkTransformer: Nodes and edges for %s", configAddr)
|
|
expand := &nodeExpandCheck{
|
|
addr: configAddr,
|
|
config: check,
|
|
makeInstance: func(addr addrs.AbsCheck, cfg *configs.Check) dag.Vertex {
|
|
return &nodeCheckAssert{
|
|
addr: addr,
|
|
config: cfg,
|
|
executeChecks: t.ExecuteChecks(),
|
|
}
|
|
},
|
|
}
|
|
g.Add(expand)
|
|
|
|
// We also need to report the checks we are going to execute before we
|
|
// try and execute them.
|
|
if t.ReportChecks() {
|
|
report := &nodeReportCheck{
|
|
addr: configAddr,
|
|
}
|
|
g.Add(report)
|
|
|
|
// Make sure we report our checks before we start executing the
|
|
// actual checks.
|
|
g.Connect(dag.BasicEdge(expand, report))
|
|
|
|
if check.DataResource != nil {
|
|
// If we have a nested data source, we need to make sure we
|
|
// also report the check before the data source executes.
|
|
//
|
|
// We loop through all the nodes in the graph to find the one
|
|
// that contains our data source and connect it.
|
|
for _, other := range allNodes {
|
|
if resource, isResource := other.(GraphNodeConfigResource); isResource {
|
|
resourceAddr := resource.ResourceAddr()
|
|
if !resourceAddr.Module.Equal(moduleAddr) {
|
|
// This resource isn't in the same module as our check
|
|
// so skip it.
|
|
continue
|
|
}
|
|
|
|
resourceCfg := cfg.Module.ResourceByAddr(resourceAddr.Resource)
|
|
if resourceCfg != nil && resourceCfg.Container != nil && resourceCfg.Container.Accessible(check.Addr()) {
|
|
// Make sure we report our checks before we execute any
|
|
// embedded data resource.
|
|
g.Connect(dag.BasicEdge(other, report))
|
|
|
|
// There's at most one embedded data source, and
|
|
// we've found it so stop looking.
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, child := range cfg.Children {
|
|
if err := t.transform(g, child, allNodes); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ReportChecks returns true if this operation should report any check blocks
|
|
// that it is about to execute.
|
|
//
|
|
// This is true for planning operations, as apply operations recreate the
|
|
// expected checks from the plan.
|
|
//
|
|
// We'll also report the checks during an import operation. We still execute
|
|
// our check blocks during an import operation so they need to be reported
|
|
// first.
|
|
func (t *checkTransformer) ReportChecks() bool {
|
|
return t.Operation == walkPlan || t.Operation == walkImport
|
|
}
|
|
|
|
// ExecuteChecks returns true if this operation should actually execute any
|
|
// check blocks in the config.
|
|
//
|
|
// If this returns false we will still create and execute check nodes in the
|
|
// graph, but they will only validate things like references and syntax.
|
|
func (t *checkTransformer) ExecuteChecks() bool {
|
|
switch t.Operation {
|
|
case walkPlan, walkApply, walkImport:
|
|
// We only actually execute the checks for plan and apply operations.
|
|
return true
|
|
default:
|
|
// For everything else, we still want to validate the checks make sense
|
|
// logically and syntactically, but we won't actually resolve the check
|
|
// conditions.
|
|
return false
|
|
}
|
|
}
|