github.com/opentofu/opentofu@v1.7.1/internal/tofu/graph_builder_plan.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 "github.com/opentofu/opentofu/internal/states" 15 "github.com/opentofu/opentofu/internal/tfdiags" 16 ) 17 18 // PlanGraphBuilder is a GraphBuilder implementation that builds a graph for 19 // planning and for other "plan-like" operations which don't require an 20 // already-calculated plan as input. 21 // 22 // Unlike the apply graph builder, this graph builder: 23 // 24 // - Makes its decisions primarily based on the given configuration, which 25 // represents the desired state. 26 // 27 // - Ignores certain lifecycle concerns like create_before_destroy, because 28 // those are only important once we already know what action we're planning 29 // to take against a particular resource instance. 30 type PlanGraphBuilder struct { 31 // Config is the configuration tree to build a plan from. 32 Config *configs.Config 33 34 // State is the current state 35 State *states.State 36 37 // RootVariableValues are the raw input values for root input variables 38 // given by the caller, which we'll resolve into final values as part 39 // of the plan walk. 40 RootVariableValues InputValues 41 42 // Plugins is a library of plug-in components (providers and 43 // provisioners) available for use. 44 Plugins *contextPlugins 45 46 // Targets are resources to target 47 Targets []addrs.Targetable 48 49 // ForceReplace are resource instances where if we would normally have 50 // generated a NoOp or Update action then we'll force generating a replace 51 // action instead. Create and Delete actions are not affected. 52 ForceReplace []addrs.AbsResourceInstance 53 54 // skipRefresh indicates that we should skip refreshing managed resources 55 skipRefresh bool 56 57 // preDestroyRefresh indicates that we are executing the refresh which 58 // happens immediately before a destroy plan, which happens to use the 59 // normal planing mode so skipPlanChanges cannot be set. 60 preDestroyRefresh bool 61 62 // skipPlanChanges indicates that we should skip the step of comparing 63 // prior state with configuration and generating planned changes to 64 // resource instances. (This is for the "refresh only" planning mode, 65 // where we _only_ do the refresh step.) 66 skipPlanChanges bool 67 68 ConcreteProvider ConcreteProviderNodeFunc 69 ConcreteResource ConcreteResourceNodeFunc 70 ConcreteResourceInstance ConcreteResourceInstanceNodeFunc 71 ConcreteResourceOrphan ConcreteResourceInstanceNodeFunc 72 ConcreteResourceInstanceDeposed ConcreteResourceInstanceDeposedNodeFunc 73 ConcreteModule ConcreteModuleNodeFunc 74 75 // Plan Operation this graph will be used for. 76 Operation walkOperation 77 78 // ExternalReferences allows the external caller to pass in references to 79 // nodes that should not be pruned even if they are not referenced within 80 // the actual graph. 81 ExternalReferences []*addrs.Reference 82 83 // ImportTargets are the list of resources to import. 84 ImportTargets []*ImportTarget 85 86 // EndpointsToRemove are the list of resources and modules to forget from 87 // the state. 88 EndpointsToRemove []addrs.ConfigRemovable 89 90 // GenerateConfig tells OpenTofu where to write and generated config for 91 // any import targets that do not already have configuration. 92 // 93 // If empty, then config will not be generated. 94 GenerateConfigPath string 95 } 96 97 // See GraphBuilder 98 func (b *PlanGraphBuilder) Build(path addrs.ModuleInstance) (*Graph, tfdiags.Diagnostics) { 99 log.Printf("[TRACE] building graph for %s", b.Operation) 100 return (&BasicGraphBuilder{ 101 Steps: b.Steps(), 102 Name: "PlanGraphBuilder", 103 }).Build(path) 104 } 105 106 // See GraphBuilder 107 func (b *PlanGraphBuilder) Steps() []GraphTransformer { 108 switch b.Operation { 109 case walkPlan: 110 b.initPlan() 111 case walkPlanDestroy: 112 b.initDestroy() 113 case walkValidate: 114 b.initValidate() 115 case walkImport: 116 b.initImport() 117 default: 118 panic("invalid plan operation: " + b.Operation.String()) 119 } 120 121 steps := []GraphTransformer{ 122 // Creates all the resources represented in the config 123 &ConfigTransformer{ 124 Concrete: b.ConcreteResource, 125 Config: b.Config, 126 127 // Resources are not added from the config on destroy. 128 skip: b.Operation == walkPlanDestroy, 129 130 importTargets: b.ImportTargets, 131 132 // We only want to generate config during a plan operation. 133 generateConfigPathForImportTargets: b.GenerateConfigPath, 134 }, 135 136 // Add dynamic values 137 &RootVariableTransformer{Config: b.Config, RawValues: b.RootVariableValues}, 138 &ModuleVariableTransformer{Config: b.Config}, 139 &LocalTransformer{Config: b.Config}, 140 &OutputTransformer{ 141 Config: b.Config, 142 RefreshOnly: b.skipPlanChanges || b.preDestroyRefresh, 143 Destroying: b.Operation == walkPlanDestroy, 144 145 // NOTE: We currently treat anything built with the plan graph 146 // builder as "planning" for our purposes here, because we share 147 // the same graph node implementation between all of the walk 148 // types and so the pre-planning walks still think they are 149 // producing a plan even though we immediately discard it. 150 Planning: true, 151 }, 152 153 // Add nodes and edges for the check block assertions. Check block data 154 // sources were added earlier. 155 &checkTransformer{ 156 Config: b.Config, 157 Operation: b.Operation, 158 }, 159 160 // Add orphan resources 161 &OrphanResourceInstanceTransformer{ 162 Concrete: b.ConcreteResourceOrphan, 163 State: b.State, 164 Config: b.Config, 165 skip: b.Operation == walkPlanDestroy, 166 }, 167 168 // We also need nodes for any deposed instance objects present in the 169 // state, so we can plan to destroy them. (During plan this will 170 // intentionally skip creating nodes for _current_ objects, since 171 // ConfigTransformer created nodes that will do that during 172 // DynamicExpand.) 173 &StateTransformer{ 174 ConcreteCurrent: b.ConcreteResourceInstance, 175 ConcreteDeposed: b.ConcreteResourceInstanceDeposed, 176 State: b.State, 177 }, 178 179 // Attach the state 180 &AttachStateTransformer{State: b.State}, 181 182 // Create orphan output nodes 183 &OrphanOutputTransformer{ 184 Config: b.Config, 185 State: b.State, 186 Planning: true, 187 }, 188 189 // Attach the configuration to any resources 190 &AttachResourceConfigTransformer{Config: b.Config}, 191 192 // add providers 193 transformProviders(b.ConcreteProvider, b.Config), 194 195 // Remove modules no longer present in the config 196 &RemovedModuleTransformer{Config: b.Config, State: b.State}, 197 198 // Must attach schemas before ReferenceTransformer so that we can 199 // analyze the configuration to find references. 200 &AttachSchemaTransformer{Plugins: b.Plugins, Config: b.Config}, 201 202 // After schema transformer, we can add function references 203 &ProviderFunctionTransformer{Config: b.Config}, 204 205 // Remove unused providers and proxies 206 &PruneProviderTransformer{}, 207 208 // Create expansion nodes for all of the module calls. This must 209 // come after all other transformers that create nodes representing 210 // objects that can belong to modules. 211 &ModuleExpansionTransformer{Concrete: b.ConcreteModule, Config: b.Config}, 212 213 // Plug in any external references. 214 &ExternalReferenceTransformer{ 215 ExternalReferences: b.ExternalReferences, 216 }, 217 218 &ReferenceTransformer{}, 219 220 &AttachDependenciesTransformer{}, 221 222 // Make sure data sources are aware of any depends_on from the 223 // configuration 224 &attachDataResourceDependsOnTransformer{}, 225 226 // DestroyEdgeTransformer is only required during a plan so that the 227 // TargetsTransformer can determine which nodes to keep in the graph. 228 &DestroyEdgeTransformer{ 229 Operation: b.Operation, 230 }, 231 232 &pruneUnusedNodesTransformer{ 233 skip: b.Operation != walkPlanDestroy, 234 }, 235 236 // Target 237 &TargetsTransformer{Targets: b.Targets}, 238 239 // Detect when create_before_destroy must be forced on for a particular 240 // node due to dependency edges, to avoid graph cycles during apply. 241 &ForcedCBDTransformer{}, 242 243 // Close opened plugin connections 244 &CloseProviderTransformer{}, 245 246 // Close the root module 247 &CloseRootModuleTransformer{}, 248 249 // Perform the transitive reduction to make our graph a bit 250 // more understandable if possible (it usually is possible). 251 &TransitiveReductionTransformer{}, 252 } 253 254 return steps 255 } 256 257 func (b *PlanGraphBuilder) initPlan() { 258 b.ConcreteProvider = func(a *NodeAbstractProvider) dag.Vertex { 259 return &NodeApplyableProvider{ 260 NodeAbstractProvider: a, 261 } 262 } 263 264 b.ConcreteResource = func(a *NodeAbstractResource) dag.Vertex { 265 return &nodeExpandPlannableResource{ 266 NodeAbstractResource: a, 267 skipRefresh: b.skipRefresh, 268 skipPlanChanges: b.skipPlanChanges, 269 preDestroyRefresh: b.preDestroyRefresh, 270 forceReplace: b.ForceReplace, 271 } 272 } 273 274 b.ConcreteResourceOrphan = func(a *NodeAbstractResourceInstance) dag.Vertex { 275 return &NodePlannableResourceInstanceOrphan{ 276 NodeAbstractResourceInstance: a, 277 skipRefresh: b.skipRefresh, 278 skipPlanChanges: b.skipPlanChanges, 279 EndpointsToRemove: b.EndpointsToRemove, 280 } 281 } 282 283 b.ConcreteResourceInstanceDeposed = func(a *NodeAbstractResourceInstance, key states.DeposedKey) dag.Vertex { 284 return &NodePlanDeposedResourceInstanceObject{ 285 NodeAbstractResourceInstance: a, 286 DeposedKey: key, 287 288 skipRefresh: b.skipRefresh, 289 skipPlanChanges: b.skipPlanChanges, 290 EndpointsToRemove: b.EndpointsToRemove, 291 } 292 } 293 } 294 295 func (b *PlanGraphBuilder) initDestroy() { 296 b.initPlan() 297 298 b.ConcreteResourceInstance = func(a *NodeAbstractResourceInstance) dag.Vertex { 299 return &NodePlanDestroyableResourceInstance{ 300 NodeAbstractResourceInstance: a, 301 skipRefresh: b.skipRefresh, 302 } 303 } 304 } 305 306 func (b *PlanGraphBuilder) initValidate() { 307 // Set the provider to the normal provider. This will ask for input. 308 b.ConcreteProvider = func(a *NodeAbstractProvider) dag.Vertex { 309 return &NodeApplyableProvider{ 310 NodeAbstractProvider: a, 311 } 312 } 313 314 b.ConcreteResource = func(a *NodeAbstractResource) dag.Vertex { 315 return &NodeValidatableResource{ 316 NodeAbstractResource: a, 317 } 318 } 319 320 b.ConcreteModule = func(n *nodeExpandModule) dag.Vertex { 321 return &nodeValidateModule{ 322 nodeExpandModule: *n, 323 } 324 } 325 } 326 327 func (b *PlanGraphBuilder) initImport() { 328 b.ConcreteProvider = func(a *NodeAbstractProvider) dag.Vertex { 329 return &NodeApplyableProvider{ 330 NodeAbstractProvider: a, 331 } 332 } 333 334 b.ConcreteResource = func(a *NodeAbstractResource) dag.Vertex { 335 return &nodeExpandPlannableResource{ 336 NodeAbstractResource: a, 337 338 // For now we always skip planning changes for import, since we are 339 // not going to combine importing with other changes. This is 340 // temporary to try and maintain existing import behaviors, but 341 // planning will need to be allowed for more complex configurations. 342 skipPlanChanges: true, 343 344 // We also skip refresh for now, since the plan output is written 345 // as the new state, and users are not expecting the import process 346 // to update any other instances in state. 347 skipRefresh: true, 348 } 349 } 350 }