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 }