Files
opentf/internal/tofu/transform_check_starter.go
Martin Atkins 75c7834ca6 tofu: GraphTransformer.Transform takes context.Context
Most of our transformers are pure compute and so don't really have a strong
need to generate trace spans under our current focus of only exposing
user-facing concepts and external requests in our traces, but unfortunately
some of them indirectly depend on provider schema, which in turn means that
they can potentially be unlucky enough to be the trigger for making all
of the provider requests needed to fill the schema cache and therefore
would end up with provider request spans being reported beneath them.

As usual with these interface updates, this initial change focuses only
on changing the interface and updating its direct callers and implementers
to match, without any further refactoring or attempts to plumb contexts
to or from other functions that don't have them yet. That means there are
a few new context.TODO() calls here that we'll tidy up in a later commit
that hopefully won't involve all of the noise that is caused by changing
an interface API.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-05-08 07:16:09 -07:00

134 lines
3.9 KiB
Go

// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package tofu
import (
"context"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/dag"
)
var _ GraphTransformer = (*checkStartTransformer)(nil)
// checkStartTransformer checks if the configuration has any data blocks nested
// within check blocks, and if it does then it introduces a nodeCheckStart
// vertex that ensures all resources have been applied before it starts loading
// the nested data sources.
type checkStartTransformer struct {
// Config for the entire module.
Config *configs.Config
// Operation is the current operation this node will be part of.
Operation walkOperation
}
func (s *checkStartTransformer) Transform(_ context.Context, graph *Graph) error {
if s.Operation != walkApply && s.Operation != walkPlan {
// We only actually execute the checks during plan apply operations
// so if we are doing something else we can just skip this and
// leave the graph alone.
return nil
}
var resources []dag.Vertex
var nested []dag.Vertex
// We're going to step through all the vertices and pull out the relevant
// resources and data sources.
for _, vertex := range graph.Vertices() {
if node, isResource := vertex.(GraphNodeCreator); isResource {
addr := node.CreateAddr()
if addr.Resource.Resource.Mode == addrs.ManagedResourceMode {
// This is a resource, so we want to make sure it executes
// before any nested data sources.
// We can reduce the number of additional edges we write into
// the graph by only including "leaf" resources, that is
// resources that aren't referenced by other resources. If a
// resource is referenced by another resource then we know that
// it will execute before that resource so we only need to worry
// about the referencing resource.
leafResource := true
for _, other := range graph.UpEdges(vertex) {
if otherResource, isResource := other.(GraphNodeCreator); isResource {
otherAddr := otherResource.CreateAddr()
if otherAddr.Resource.Resource.Mode == addrs.ManagedResourceMode {
// Then this resource is being referenced so skip
// it.
leafResource = false
break
}
}
}
if leafResource {
resources = append(resources, vertex)
}
// We've handled the resource so move to the next vertex.
continue
}
// Now, we know we are processing a data block.
config := s.Config
if !addr.Module.IsRoot() {
config = s.Config.Descendent(addr.Module.Module())
}
if config == nil {
// might have been deleted, so it won't be subject to any checks
// anyway.
continue
}
resource := config.Module.ResourceByAddr(addr.Resource.Resource)
if resource == nil {
// might have been deleted, so it won't be subject to any checks
// anyway.
continue
}
if _, ok := resource.Container.(*configs.Check); ok {
// Then this is a data source within a check block, so let's
// make a note of it.
nested = append(nested, vertex)
}
// Otherwise, it's just a normal data source. From a check block we
// don't really care when OpenTofu is loading non-nested data
// sources so we'll just forget about it and move on.
}
}
if len(nested) > 0 {
// We don't need to do any of this if we don't have any nested data
// sources, so we check that first.
//
// Otherwise we introduce a vertex that can act as a pauser between
// our nested data sources and leaf resources.
check := &nodeCheckStart{}
graph.Add(check)
// Finally, connect everything up so it all executes in order.
for _, vertex := range nested {
graph.Connect(dag.BasicEdge(vertex, check))
}
for _, vertex := range resources {
graph.Connect(dag.BasicEdge(check, vertex))
}
}
return nil
}