github.com/opentofu/opentofu@v1.7.1/internal/tofu/transform_check_starter.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package tofu
     7  
     8  import (
     9  	"github.com/opentofu/opentofu/internal/addrs"
    10  	"github.com/opentofu/opentofu/internal/configs"
    11  	"github.com/opentofu/opentofu/internal/dag"
    12  )
    13  
    14  var _ GraphTransformer = (*checkStartTransformer)(nil)
    15  
    16  // checkStartTransformer checks if the configuration has any data blocks nested
    17  // within check blocks, and if it does then it introduces a nodeCheckStart
    18  // vertex that ensures all resources have been applied before it starts loading
    19  // the nested data sources.
    20  type checkStartTransformer struct {
    21  	// Config for the entire module.
    22  	Config *configs.Config
    23  
    24  	// Operation is the current operation this node will be part of.
    25  	Operation walkOperation
    26  }
    27  
    28  func (s *checkStartTransformer) Transform(graph *Graph) error {
    29  	if s.Operation != walkApply && s.Operation != walkPlan {
    30  		// We only actually execute the checks during plan apply operations
    31  		// so if we are doing something else we can just skip this and
    32  		// leave the graph alone.
    33  		return nil
    34  	}
    35  
    36  	var resources []dag.Vertex
    37  	var nested []dag.Vertex
    38  
    39  	// We're going to step through all the vertices and pull out the relevant
    40  	// resources and data sources.
    41  	for _, vertex := range graph.Vertices() {
    42  		if node, isResource := vertex.(GraphNodeCreator); isResource {
    43  			addr := node.CreateAddr()
    44  
    45  			if addr.Resource.Resource.Mode == addrs.ManagedResourceMode {
    46  				// This is a resource, so we want to make sure it executes
    47  				// before any nested data sources.
    48  
    49  				// We can reduce the number of additional edges we write into
    50  				// the graph by only including "leaf" resources, that is
    51  				// resources that aren't referenced by other resources. If a
    52  				// resource is referenced by another resource then we know that
    53  				// it will execute before that resource so we only need to worry
    54  				// about the referencing resource.
    55  
    56  				leafResource := true
    57  				for _, other := range graph.UpEdges(vertex) {
    58  					if otherResource, isResource := other.(GraphNodeCreator); isResource {
    59  						otherAddr := otherResource.CreateAddr()
    60  						if otherAddr.Resource.Resource.Mode == addrs.ManagedResourceMode {
    61  							// Then this resource is being referenced so skip
    62  							// it.
    63  							leafResource = false
    64  							break
    65  						}
    66  					}
    67  				}
    68  
    69  				if leafResource {
    70  					resources = append(resources, vertex)
    71  				}
    72  
    73  				// We've handled the resource so move to the next vertex.
    74  				continue
    75  			}
    76  
    77  			// Now, we know we are processing a data block.
    78  
    79  			config := s.Config
    80  			if !addr.Module.IsRoot() {
    81  				config = s.Config.Descendent(addr.Module.Module())
    82  			}
    83  			if config == nil {
    84  				// might have been deleted, so it won't be subject to any checks
    85  				// anyway.
    86  				continue
    87  			}
    88  
    89  			resource := config.Module.ResourceByAddr(addr.Resource.Resource)
    90  			if resource == nil {
    91  				// might have been deleted, so it won't be subject to any checks
    92  				// anyway.
    93  				continue
    94  			}
    95  
    96  			if _, ok := resource.Container.(*configs.Check); ok {
    97  				// Then this is a data source within a check block, so let's
    98  				// make a note of it.
    99  				nested = append(nested, vertex)
   100  			}
   101  
   102  			// Otherwise, it's just a normal data source. From a check block we
   103  			// don't really care when OpenTofu is loading non-nested data
   104  			// sources so we'll just forget about it and move on.
   105  		}
   106  	}
   107  
   108  	if len(nested) > 0 {
   109  
   110  		// We don't need to do any of this if we don't have any nested data
   111  		// sources, so we check that first.
   112  		//
   113  		// Otherwise we introduce a vertex that can act as a pauser between
   114  		// our nested data sources and leaf resources.
   115  
   116  		check := &nodeCheckStart{}
   117  		graph.Add(check)
   118  
   119  		// Finally, connect everything up so it all executes in order.
   120  
   121  		for _, vertex := range nested {
   122  			graph.Connect(dag.BasicEdge(vertex, check))
   123  		}
   124  
   125  		for _, vertex := range resources {
   126  			graph.Connect(dag.BasicEdge(check, vertex))
   127  		}
   128  	}
   129  
   130  	return nil
   131  }