github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/terraform/node_resource_plan.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "log" 6 "strings" 7 8 "github.com/hashicorp/terraform/internal/addrs" 9 "github.com/hashicorp/terraform/internal/dag" 10 "github.com/hashicorp/terraform/internal/states" 11 "github.com/hashicorp/terraform/internal/tfdiags" 12 ) 13 14 // nodeExpandPlannableResource handles the first layer of resource 15 // expansion. We need this extra layer so DynamicExpand is called twice for 16 // the resource, the first to expand the Resource for each module instance, and 17 // the second to expand each ResourceInstance for the expanded Resources. 18 type nodeExpandPlannableResource struct { 19 *NodeAbstractResource 20 21 // ForceCreateBeforeDestroy might be set via our GraphNodeDestroyerCBD 22 // during graph construction, if dependencies require us to force this 23 // on regardless of what the configuration says. 24 ForceCreateBeforeDestroy *bool 25 26 // skipRefresh indicates that we should skip refreshing individual instances 27 skipRefresh bool 28 29 // skipPlanChanges indicates we should skip trying to plan change actions 30 // for any instances. 31 skipPlanChanges bool 32 33 // forceReplace are resource instance addresses where the user wants to 34 // force generating a replace action. This set isn't pre-filtered, so 35 // it might contain addresses that have nothing to do with the resource 36 // that this node represents, which the node itself must therefore ignore. 37 forceReplace []addrs.AbsResourceInstance 38 39 // We attach dependencies to the Resource during refresh, since the 40 // instances are instantiated during DynamicExpand. 41 // FIXME: These would be better off converted to a generic Set data 42 // structure in the future, as we need to compare for equality and take the 43 // union of multiple groups of dependencies. 44 dependencies []addrs.ConfigResource 45 } 46 47 var ( 48 _ GraphNodeDestroyerCBD = (*nodeExpandPlannableResource)(nil) 49 _ GraphNodeDynamicExpandable = (*nodeExpandPlannableResource)(nil) 50 _ GraphNodeReferenceable = (*nodeExpandPlannableResource)(nil) 51 _ GraphNodeReferencer = (*nodeExpandPlannableResource)(nil) 52 _ GraphNodeConfigResource = (*nodeExpandPlannableResource)(nil) 53 _ GraphNodeAttachResourceConfig = (*nodeExpandPlannableResource)(nil) 54 _ GraphNodeAttachDependencies = (*nodeExpandPlannableResource)(nil) 55 _ GraphNodeTargetable = (*nodeExpandPlannableResource)(nil) 56 ) 57 58 func (n *nodeExpandPlannableResource) Name() string { 59 return n.NodeAbstractResource.Name() + " (expand)" 60 } 61 62 // GraphNodeAttachDependencies 63 func (n *nodeExpandPlannableResource) AttachDependencies(deps []addrs.ConfigResource) { 64 n.dependencies = deps 65 } 66 67 // GraphNodeDestroyerCBD 68 func (n *nodeExpandPlannableResource) CreateBeforeDestroy() bool { 69 if n.ForceCreateBeforeDestroy != nil { 70 return *n.ForceCreateBeforeDestroy 71 } 72 73 // If we have no config, we just assume no 74 if n.Config == nil || n.Config.Managed == nil { 75 return false 76 } 77 78 return n.Config.Managed.CreateBeforeDestroy 79 } 80 81 // GraphNodeDestroyerCBD 82 func (n *nodeExpandPlannableResource) ModifyCreateBeforeDestroy(v bool) error { 83 n.ForceCreateBeforeDestroy = &v 84 return nil 85 } 86 87 func (n *nodeExpandPlannableResource) DynamicExpand(ctx EvalContext) (*Graph, error) { 88 var g Graph 89 90 expander := ctx.InstanceExpander() 91 moduleInstances := expander.ExpandModule(n.Addr.Module) 92 93 // Add the current expanded resource to the graph 94 for _, module := range moduleInstances { 95 resAddr := n.Addr.Resource.Absolute(module) 96 g.Add(&NodePlannableResource{ 97 NodeAbstractResource: n.NodeAbstractResource, 98 Addr: resAddr, 99 ForceCreateBeforeDestroy: n.ForceCreateBeforeDestroy, 100 dependencies: n.dependencies, 101 skipRefresh: n.skipRefresh, 102 skipPlanChanges: n.skipPlanChanges, 103 forceReplace: n.forceReplace, 104 }) 105 } 106 107 // Lock the state while we inspect it 108 state := ctx.State().Lock() 109 defer ctx.State().Unlock() 110 111 var orphans []*states.Resource 112 for _, res := range state.Resources(n.Addr) { 113 found := false 114 for _, m := range moduleInstances { 115 if m.Equal(res.Addr.Module) { 116 found = true 117 break 118 } 119 } 120 // Address form state was not found in the current config 121 if !found { 122 orphans = append(orphans, res) 123 } 124 } 125 126 // The concrete resource factory we'll use for orphans 127 concreteResourceOrphan := func(a *NodeAbstractResourceInstance) *NodePlannableResourceInstanceOrphan { 128 // Add the config and state since we don't do that via transforms 129 a.Config = n.Config 130 a.ResolvedProvider = n.ResolvedProvider 131 a.Schema = n.Schema 132 a.ProvisionerSchemas = n.ProvisionerSchemas 133 a.ProviderMetas = n.ProviderMetas 134 a.Dependencies = n.dependencies 135 136 return &NodePlannableResourceInstanceOrphan{ 137 NodeAbstractResourceInstance: a, 138 skipRefresh: n.skipRefresh, 139 skipPlanChanges: n.skipPlanChanges, 140 } 141 } 142 143 for _, res := range orphans { 144 for key := range res.Instances { 145 addr := res.Addr.Instance(key) 146 abs := NewNodeAbstractResourceInstance(addr) 147 abs.AttachResourceState(res) 148 n := concreteResourceOrphan(abs) 149 g.Add(n) 150 } 151 } 152 153 return &g, nil 154 } 155 156 // NodePlannableResource represents a resource that is "plannable": 157 // it is ready to be planned in order to create a diff. 158 type NodePlannableResource struct { 159 *NodeAbstractResource 160 161 Addr addrs.AbsResource 162 163 // ForceCreateBeforeDestroy might be set via our GraphNodeDestroyerCBD 164 // during graph construction, if dependencies require us to force this 165 // on regardless of what the configuration says. 166 ForceCreateBeforeDestroy *bool 167 168 // skipRefresh indicates that we should skip refreshing individual instances 169 skipRefresh bool 170 171 // skipPlanChanges indicates we should skip trying to plan change actions 172 // for any instances. 173 skipPlanChanges bool 174 175 // forceReplace are resource instance addresses where the user wants to 176 // force generating a replace action. This set isn't pre-filtered, so 177 // it might contain addresses that have nothing to do with the resource 178 // that this node represents, which the node itself must therefore ignore. 179 forceReplace []addrs.AbsResourceInstance 180 181 dependencies []addrs.ConfigResource 182 } 183 184 var ( 185 _ GraphNodeModuleInstance = (*NodePlannableResource)(nil) 186 _ GraphNodeDestroyerCBD = (*NodePlannableResource)(nil) 187 _ GraphNodeDynamicExpandable = (*NodePlannableResource)(nil) 188 _ GraphNodeReferenceable = (*NodePlannableResource)(nil) 189 _ GraphNodeReferencer = (*NodePlannableResource)(nil) 190 _ GraphNodeConfigResource = (*NodePlannableResource)(nil) 191 _ GraphNodeAttachResourceConfig = (*NodePlannableResource)(nil) 192 ) 193 194 func (n *NodePlannableResource) Path() addrs.ModuleInstance { 195 return n.Addr.Module 196 } 197 198 func (n *NodePlannableResource) Name() string { 199 return n.Addr.String() 200 } 201 202 // GraphNodeExecutable 203 func (n *NodePlannableResource) Execute(ctx EvalContext, op walkOperation) tfdiags.Diagnostics { 204 var diags tfdiags.Diagnostics 205 206 if n.Config == nil { 207 // Nothing to do, then. 208 log.Printf("[TRACE] NodeApplyableResource: no configuration present for %s", n.Name()) 209 return diags 210 } 211 212 // writeResourceState is responsible for informing the expander of what 213 // repetition mode this resource has, which allows expander.ExpandResource 214 // to work below. 215 moreDiags := n.writeResourceState(ctx, n.Addr) 216 diags = diags.Append(moreDiags) 217 if moreDiags.HasErrors() { 218 return diags 219 } 220 221 // Before we expand our resource into potentially many resource instances, 222 // we'll verify that any mention of this resource in n.forceReplace is 223 // consistent with the repetition mode of the resource. In other words, 224 // we're aiming to catch a situation where naming a particular resource 225 // instance would require an instance key but the given address has none. 226 expander := ctx.InstanceExpander() 227 instanceAddrs := expander.ExpandResource(n.ResourceAddr().Absolute(ctx.Path())) 228 229 // If there's a number of instances other than 1 then we definitely need 230 // an index. 231 mustHaveIndex := len(instanceAddrs) != 1 232 // If there's only one instance then we might still need an index, if the 233 // instance address has one. 234 if len(instanceAddrs) == 1 && instanceAddrs[0].Resource.Key != addrs.NoKey { 235 mustHaveIndex = true 236 } 237 if mustHaveIndex { 238 for _, candidateAddr := range n.forceReplace { 239 if candidateAddr.Resource.Key == addrs.NoKey { 240 if n.Addr.Resource.Equal(candidateAddr.Resource.Resource) { 241 switch { 242 case len(instanceAddrs) == 0: 243 // In this case there _are_ no instances to replace, so 244 // there isn't any alternative address for us to suggest. 245 diags = diags.Append(tfdiags.Sourceless( 246 tfdiags.Warning, 247 "Incompletely-matched force-replace resource instance", 248 fmt.Sprintf( 249 "Your force-replace request for %s doesn't match any resource instances because this resource doesn't have any instances.", 250 candidateAddr, 251 ), 252 )) 253 case len(instanceAddrs) == 1: 254 diags = diags.Append(tfdiags.Sourceless( 255 tfdiags.Warning, 256 "Incompletely-matched force-replace resource instance", 257 fmt.Sprintf( 258 "Your force-replace request for %s doesn't match any resource instances because it lacks an instance key.\n\nTo force replacement of the single declared instance, use the following option instead:\n -replace=%q", 259 candidateAddr, instanceAddrs[0], 260 ), 261 )) 262 default: 263 var possibleValidOptions strings.Builder 264 for _, addr := range instanceAddrs { 265 fmt.Fprintf(&possibleValidOptions, "\n -replace=%q", addr) 266 } 267 268 diags = diags.Append(tfdiags.Sourceless( 269 tfdiags.Warning, 270 "Incompletely-matched force-replace resource instance", 271 fmt.Sprintf( 272 "Your force-replace request for %s doesn't match any resource instances because it lacks an instance key.\n\nTo force replacement of particular instances, use one or more of the following options instead:%s", 273 candidateAddr, possibleValidOptions.String(), 274 ), 275 )) 276 } 277 } 278 } 279 } 280 } 281 // NOTE: The actual interpretation of n.forceReplace to produce replace 282 // actions is in NodeAbstractResourceInstance.plan, because we must do so 283 // on a per-instance basis rather than for the whole resource. 284 285 return diags 286 } 287 288 // GraphNodeDestroyerCBD 289 func (n *NodePlannableResource) CreateBeforeDestroy() bool { 290 if n.ForceCreateBeforeDestroy != nil { 291 return *n.ForceCreateBeforeDestroy 292 } 293 294 // If we have no config, we just assume no 295 if n.Config == nil || n.Config.Managed == nil { 296 return false 297 } 298 299 return n.Config.Managed.CreateBeforeDestroy 300 } 301 302 // GraphNodeDestroyerCBD 303 func (n *NodePlannableResource) ModifyCreateBeforeDestroy(v bool) error { 304 n.ForceCreateBeforeDestroy = &v 305 return nil 306 } 307 308 // GraphNodeDynamicExpandable 309 func (n *NodePlannableResource) DynamicExpand(ctx EvalContext) (*Graph, error) { 310 var diags tfdiags.Diagnostics 311 312 // Our instance expander should already have been informed about the 313 // expansion of this resource and of all of its containing modules, so 314 // it can tell us which instance addresses we need to process. 315 expander := ctx.InstanceExpander() 316 instanceAddrs := expander.ExpandResource(n.ResourceAddr().Absolute(ctx.Path())) 317 318 // Our graph transformers require access to the full state, so we'll 319 // temporarily lock it while we work on this. 320 state := ctx.State().Lock() 321 defer ctx.State().Unlock() 322 323 // If this is a resource that participates in custom condition checks 324 // (i.e. it has preconditions or postconditions) then the check state 325 // wants to know the addresses of the checkable objects so that it can 326 // treat them as unknown status if we encounter an error before actually 327 // visiting the checks. 328 if checkState := ctx.Checks(); checkState.ConfigHasChecks(n.NodeAbstractResource.Addr) { 329 checkableAddrs := addrs.MakeSet[addrs.Checkable]() 330 for _, addr := range instanceAddrs { 331 checkableAddrs.Add(addr) 332 } 333 checkState.ReportCheckableObjects(n.NodeAbstractResource.Addr, checkableAddrs) 334 } 335 336 // The concrete resource factory we'll use 337 concreteResource := func(a *NodeAbstractResourceInstance) dag.Vertex { 338 // check if this node is being imported first 339 for _, importTarget := range n.importTargets { 340 if importTarget.Addr.Equal(a.Addr) { 341 return &graphNodeImportState{ 342 Addr: importTarget.Addr, 343 ID: importTarget.ID, 344 ResolvedProvider: n.ResolvedProvider, 345 } 346 } 347 } 348 349 // Add the config and state since we don't do that via transforms 350 a.Config = n.Config 351 a.ResolvedProvider = n.ResolvedProvider 352 a.Schema = n.Schema 353 a.ProvisionerSchemas = n.ProvisionerSchemas 354 a.ProviderMetas = n.ProviderMetas 355 a.dependsOn = n.dependsOn 356 a.Dependencies = n.dependencies 357 358 return &NodePlannableResourceInstance{ 359 NodeAbstractResourceInstance: a, 360 361 // By the time we're walking, we've figured out whether we need 362 // to force on CreateBeforeDestroy due to dependencies on other 363 // nodes that have it. 364 ForceCreateBeforeDestroy: n.CreateBeforeDestroy(), 365 skipRefresh: n.skipRefresh, 366 skipPlanChanges: n.skipPlanChanges, 367 forceReplace: n.forceReplace, 368 } 369 } 370 371 // The concrete resource factory we'll use for orphans 372 concreteResourceOrphan := func(a *NodeAbstractResourceInstance) dag.Vertex { 373 // Add the config and state since we don't do that via transforms 374 a.Config = n.Config 375 a.ResolvedProvider = n.ResolvedProvider 376 a.Schema = n.Schema 377 a.ProvisionerSchemas = n.ProvisionerSchemas 378 a.ProviderMetas = n.ProviderMetas 379 380 return &NodePlannableResourceInstanceOrphan{ 381 NodeAbstractResourceInstance: a, 382 skipRefresh: n.skipRefresh, 383 skipPlanChanges: n.skipPlanChanges, 384 } 385 } 386 387 // Start creating the steps 388 steps := []GraphTransformer{ 389 // Expand the count or for_each (if present) 390 &ResourceCountTransformer{ 391 Concrete: concreteResource, 392 Schema: n.Schema, 393 Addr: n.ResourceAddr(), 394 InstanceAddrs: instanceAddrs, 395 }, 396 397 // Add the count/for_each orphans 398 &OrphanResourceInstanceCountTransformer{ 399 Concrete: concreteResourceOrphan, 400 Addr: n.Addr, 401 InstanceAddrs: instanceAddrs, 402 State: state, 403 }, 404 405 // Attach the state 406 &AttachStateTransformer{State: state}, 407 408 // Targeting 409 &TargetsTransformer{Targets: n.Targets}, 410 411 // Connect references so ordering is correct 412 &ReferenceTransformer{}, 413 414 // Make sure there is a single root 415 &RootTransformer{}, 416 } 417 418 // Build the graph 419 b := &BasicGraphBuilder{ 420 Steps: steps, 421 Name: "NodePlannableResource", 422 } 423 graph, diags := b.Build(ctx.Path()) 424 return graph, diags.ErrWithWarnings() 425 }