github.com/opentofu/opentofu@v1.7.1/internal/tofu/node_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/hashicorp/hcl/v2/hclsyntax"
    12  
    13  	"github.com/opentofu/opentofu/internal/addrs"
    14  	"github.com/opentofu/opentofu/internal/checks"
    15  	"github.com/opentofu/opentofu/internal/configs"
    16  	"github.com/opentofu/opentofu/internal/dag"
    17  	"github.com/opentofu/opentofu/internal/lang"
    18  	"github.com/opentofu/opentofu/internal/tfdiags"
    19  )
    20  
    21  var (
    22  	_ GraphNodeModulePath = (*nodeReportCheck)(nil)
    23  	_ GraphNodeExecutable = (*nodeReportCheck)(nil)
    24  )
    25  
    26  // nodeReportCheck calls the ReportCheckableObjects function for our assertions
    27  // within the check blocks.
    28  //
    29  // We need this to happen before the checks are actually verified and before any
    30  // nested data blocks, so the creator of this structure should make sure this
    31  // node is a parent of any nested data blocks.
    32  //
    33  // This needs to be separate to nodeExpandCheck, because the actual checks
    34  // should happen after referenced data blocks rather than before.
    35  type nodeReportCheck struct {
    36  	addr addrs.ConfigCheck
    37  }
    38  
    39  func (n *nodeReportCheck) ModulePath() addrs.Module {
    40  	return n.addr.Module
    41  }
    42  
    43  func (n *nodeReportCheck) Execute(ctx EvalContext, _ walkOperation) tfdiags.Diagnostics {
    44  	exp := ctx.InstanceExpander()
    45  	modInsts := exp.ExpandModule(n.ModulePath())
    46  
    47  	instAddrs := addrs.MakeSet[addrs.Checkable]()
    48  	for _, modAddr := range modInsts {
    49  		instAddrs.Add(n.addr.Check.Absolute(modAddr))
    50  	}
    51  	ctx.Checks().ReportCheckableObjects(n.addr, instAddrs)
    52  	return nil
    53  }
    54  
    55  func (n *nodeReportCheck) Name() string {
    56  	return n.addr.String() + " (report)"
    57  }
    58  
    59  var (
    60  	_ GraphNodeModulePath        = (*nodeExpandCheck)(nil)
    61  	_ GraphNodeDynamicExpandable = (*nodeExpandCheck)(nil)
    62  	_ GraphNodeReferencer        = (*nodeExpandCheck)(nil)
    63  )
    64  
    65  // nodeExpandCheck creates child nodes that actually execute the assertions for
    66  // a given check block.
    67  //
    68  // This must happen after any other nodes/resources/data sources that are
    69  // referenced, so we implement GraphNodeReferencer.
    70  //
    71  // This needs to be separate to nodeReportCheck as nodeReportCheck must happen
    72  // first, while nodeExpandCheck must execute after any referenced blocks.
    73  type nodeExpandCheck struct {
    74  	addr   addrs.ConfigCheck
    75  	config *configs.Check
    76  
    77  	makeInstance func(addrs.AbsCheck, *configs.Check) dag.Vertex
    78  }
    79  
    80  func (n *nodeExpandCheck) ModulePath() addrs.Module {
    81  	return n.addr.Module
    82  }
    83  
    84  func (n *nodeExpandCheck) DynamicExpand(ctx EvalContext) (*Graph, error) {
    85  	exp := ctx.InstanceExpander()
    86  	modInsts := exp.ExpandModule(n.ModulePath())
    87  
    88  	var g Graph
    89  	for _, modAddr := range modInsts {
    90  		testAddr := n.addr.Check.Absolute(modAddr)
    91  		log.Printf("[TRACE] nodeExpandCheck: Node for %s", testAddr)
    92  		g.Add(n.makeInstance(testAddr, n.config))
    93  	}
    94  	addRootNodeToGraph(&g)
    95  
    96  	return &g, nil
    97  }
    98  
    99  func (n *nodeExpandCheck) References() []*addrs.Reference {
   100  	var refs []*addrs.Reference
   101  	for _, assert := range n.config.Asserts {
   102  		// Check blocks reference anything referenced by conditions or messages
   103  		// in their check rules.
   104  		condition, _ := lang.ReferencesInExpr(addrs.ParseRef, assert.Condition)
   105  		message, _ := lang.ReferencesInExpr(addrs.ParseRef, assert.ErrorMessage)
   106  		refs = append(refs, condition...)
   107  		refs = append(refs, message...)
   108  	}
   109  	if n.config.DataResource != nil {
   110  		// We'll also always reference our nested data block if it exists, as
   111  		// there is nothing enforcing that it has to also be referenced by our
   112  		// conditions or messages.
   113  		//
   114  		// We don't need to make this addr absolute, because the check block and
   115  		// the data resource are always within the same module/instance.
   116  		traversal, _ := hclsyntax.ParseTraversalAbs(
   117  			[]byte(n.config.DataResource.Addr().String()),
   118  			n.config.DataResource.DeclRange.Filename,
   119  			n.config.DataResource.DeclRange.Start)
   120  		ref, _ := addrs.ParseRef(traversal)
   121  		refs = append(refs, ref)
   122  	}
   123  	return refs
   124  }
   125  
   126  func (n *nodeExpandCheck) Name() string {
   127  	return n.addr.String() + " (expand)"
   128  }
   129  
   130  var (
   131  	_ GraphNodeModuleInstance = (*nodeCheckAssert)(nil)
   132  	_ GraphNodeExecutable     = (*nodeCheckAssert)(nil)
   133  )
   134  
   135  type nodeCheckAssert struct {
   136  	addr   addrs.AbsCheck
   137  	config *configs.Check
   138  
   139  	// We only want to actually execute the checks during the plan and apply
   140  	// operations, but we still want to validate our config during
   141  	// other operations.
   142  	executeChecks bool
   143  }
   144  
   145  func (n *nodeCheckAssert) ModulePath() addrs.Module {
   146  	return n.Path().Module()
   147  }
   148  
   149  func (n *nodeCheckAssert) Path() addrs.ModuleInstance {
   150  	return n.addr.Module
   151  }
   152  
   153  func (n *nodeCheckAssert) Execute(ctx EvalContext, _ walkOperation) tfdiags.Diagnostics {
   154  
   155  	// We only want to actually execute the checks during specific
   156  	// operations, such as plan and applies.
   157  	if n.executeChecks {
   158  		if status := ctx.Checks().ObjectCheckStatus(n.addr); status == checks.StatusFail || status == checks.StatusError {
   159  			// This check is already failing, so we won't try and evaluate it.
   160  			// This typically means there was an error in a data block within
   161  			// the check block.
   162  			return nil
   163  		}
   164  
   165  		return evalCheckRules(
   166  			addrs.CheckAssertion,
   167  			n.config.Asserts,
   168  			ctx,
   169  			n.addr,
   170  			EvalDataForNoInstanceKey,
   171  			tfdiags.Warning)
   172  
   173  	}
   174  
   175  	// Otherwise let's still validate the config and references and return
   176  	// diagnostics if references do not exist etc.
   177  	var diags tfdiags.Diagnostics
   178  	for ix, assert := range n.config.Asserts {
   179  		_, _, moreDiags := validateCheckRule(addrs.NewCheckRule(n.addr, addrs.CheckAssertion, ix), assert, ctx, EvalDataForNoInstanceKey)
   180  		diags = diags.Append(moreDiags)
   181  	}
   182  	return diags
   183  }
   184  
   185  func (n *nodeCheckAssert) Name() string {
   186  	return n.addr.String() + " (assertions)"
   187  }
   188  
   189  var (
   190  	_ GraphNodeExecutable = (*nodeCheckStart)(nil)
   191  )
   192  
   193  // We need to ensure that any nested data sources execute after all other
   194  // resource changes have been applied. This node acts as a single point of
   195  // dependency that can enforce this ordering.
   196  type nodeCheckStart struct{}
   197  
   198  func (n *nodeCheckStart) Execute(context EvalContext, operation walkOperation) tfdiags.Diagnostics {
   199  	// This node doesn't actually do anything, except simplify the underlying
   200  	// graph structure.
   201  	return nil
   202  }
   203  
   204  func (n *nodeCheckStart) Name() string {
   205  	return "(execute checks)"
   206  }