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