github.com/opentofu/opentofu@v1.7.1/internal/tofu/transform_check.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  	"log"
    10  
    11  	"github.com/opentofu/opentofu/internal/addrs"
    12  	"github.com/opentofu/opentofu/internal/configs"
    13  	"github.com/opentofu/opentofu/internal/dag"
    14  )
    15  
    16  type checkTransformer struct {
    17  	// Config for the entire module.
    18  	Config *configs.Config
    19  
    20  	// Operation is the current operation this node will be part of.
    21  	Operation walkOperation
    22  }
    23  
    24  var _ GraphTransformer = (*checkTransformer)(nil)
    25  
    26  func (t *checkTransformer) Transform(graph *Graph) error {
    27  	return t.transform(graph, t.Config, graph.Vertices())
    28  }
    29  
    30  func (t *checkTransformer) transform(g *Graph, cfg *configs.Config, allNodes []dag.Vertex) error {
    31  
    32  	if t.Operation == walkDestroy || t.Operation == walkPlanDestroy {
    33  		// Don't include anything about checks during destroy operations.
    34  		//
    35  		// For other plan and normal apply operations we do everything, for
    36  		// destroy operations we do nothing. For any other operations we still
    37  		// include the check nodes, but we don't actually execute the checks
    38  		// instead we still validate their references and make sure their
    39  		// conditions make sense etc.
    40  		return nil
    41  	}
    42  
    43  	moduleAddr := cfg.Path
    44  
    45  	for _, check := range cfg.Module.Checks {
    46  		configAddr := check.Addr().InModule(moduleAddr)
    47  
    48  		// We want to create a node for each check block. This node will execute
    49  		// after anything it references, and will update the checks object
    50  		// embedded in the plan and/or state.
    51  
    52  		log.Printf("[TRACE] checkTransformer: Nodes and edges for %s", configAddr)
    53  		expand := &nodeExpandCheck{
    54  			addr:   configAddr,
    55  			config: check,
    56  			makeInstance: func(addr addrs.AbsCheck, cfg *configs.Check) dag.Vertex {
    57  				return &nodeCheckAssert{
    58  					addr:          addr,
    59  					config:        cfg,
    60  					executeChecks: t.ExecuteChecks(),
    61  				}
    62  			},
    63  		}
    64  		g.Add(expand)
    65  
    66  		// We also need to report the checks we are going to execute before we
    67  		// try and execute them.
    68  		if t.ReportChecks() {
    69  			report := &nodeReportCheck{
    70  				addr: configAddr,
    71  			}
    72  			g.Add(report)
    73  
    74  			// Make sure we report our checks before we start executing the
    75  			// actual checks.
    76  			g.Connect(dag.BasicEdge(expand, report))
    77  
    78  			if check.DataResource != nil {
    79  				// If we have a nested data source, we need to make sure we
    80  				// also report the check before the data source executes.
    81  				//
    82  				// We loop through all the nodes in the graph to find the one
    83  				// that contains our data source and connect it.
    84  				for _, other := range allNodes {
    85  					if resource, isResource := other.(GraphNodeConfigResource); isResource {
    86  						resourceAddr := resource.ResourceAddr()
    87  						if !resourceAddr.Module.Equal(moduleAddr) {
    88  							// This resource isn't in the same module as our check
    89  							// so skip it.
    90  							continue
    91  						}
    92  
    93  						resourceCfg := cfg.Module.ResourceByAddr(resourceAddr.Resource)
    94  						if resourceCfg != nil && resourceCfg.Container != nil && resourceCfg.Container.Accessible(check.Addr()) {
    95  							// Make sure we report our checks before we execute any
    96  							// embedded data resource.
    97  							g.Connect(dag.BasicEdge(other, report))
    98  
    99  							// There's at most one embedded data source, and
   100  							// we've found it so stop looking.
   101  							break
   102  						}
   103  					}
   104  				}
   105  			}
   106  		}
   107  	}
   108  
   109  	for _, child := range cfg.Children {
   110  		if err := t.transform(g, child, allNodes); err != nil {
   111  			return err
   112  		}
   113  	}
   114  
   115  	return nil
   116  }
   117  
   118  // ReportChecks returns true if this operation should report any check blocks
   119  // that it is about to execute.
   120  //
   121  // This is true for planning operations, as apply operations recreate the
   122  // expected checks from the plan.
   123  //
   124  // We'll also report the checks during an import operation. We still execute
   125  // our check blocks during an import operation so they need to be reported
   126  // first.
   127  func (t *checkTransformer) ReportChecks() bool {
   128  	return t.Operation == walkPlan || t.Operation == walkImport
   129  }
   130  
   131  // ExecuteChecks returns true if this operation should actually execute any
   132  // check blocks in the config.
   133  //
   134  // If this returns false we will still create and execute check nodes in the
   135  // graph, but they will only validate things like references and syntax.
   136  func (t *checkTransformer) ExecuteChecks() bool {
   137  	switch t.Operation {
   138  	case walkPlan, walkApply, walkImport:
   139  		// We only actually execute the checks for plan and apply operations.
   140  		return true
   141  	default:
   142  		// For everything else, we still want to validate the checks make sense
   143  		// logically and syntactically, but we won't actually resolve the check
   144  		// conditions.
   145  		return false
   146  	}
   147  }