// 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 ( "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(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 }