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 }