github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/terraform/transform_check_starter.go (about)

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