github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/terraform/context_plan.go (about) 1 package terraform 2 3 import ( 4 "bytes" 5 "fmt" 6 "log" 7 "sort" 8 "strings" 9 10 "github.com/zclconf/go-cty/cty" 11 12 "github.com/hashicorp/terraform/internal/addrs" 13 "github.com/hashicorp/terraform/internal/configs" 14 "github.com/hashicorp/terraform/internal/instances" 15 "github.com/hashicorp/terraform/internal/lang/globalref" 16 "github.com/hashicorp/terraform/internal/plans" 17 "github.com/hashicorp/terraform/internal/refactoring" 18 "github.com/hashicorp/terraform/internal/states" 19 "github.com/hashicorp/terraform/internal/tfdiags" 20 ) 21 22 // PlanOpts are the various options that affect the details of how Terraform 23 // will build a plan. 24 type PlanOpts struct { 25 // Mode defines what variety of plan the caller wishes to create. 26 // Refer to the documentation of the plans.Mode type and its values 27 // for more information. 28 Mode plans.Mode 29 30 // SkipRefresh specifies to trust that the current values for managed 31 // resource instances in the prior state are accurate and to therefore 32 // disable the usual step of fetching updated values for each resource 33 // instance using its corresponding provider. 34 SkipRefresh bool 35 36 // SetVariables are the raw values for root module variables as provided 37 // by the user who is requesting the run, prior to any normalization or 38 // substitution of defaults. See the documentation for the InputValue 39 // type for more information on how to correctly populate this. 40 SetVariables InputValues 41 42 // If Targets has a non-zero length then it activates targeted planning 43 // mode, where Terraform will take actions only for resource instances 44 // mentioned in this set and any other objects those resource instances 45 // depend on. 46 // 47 // Targeted planning mode is intended for exceptional use only, 48 // and so populating this field will cause Terraform to generate extra 49 // warnings as part of the planning result. 50 Targets []addrs.Targetable 51 52 // ForceReplace is a set of resource instance addresses whose corresponding 53 // objects should be forced planned for replacement if the provider's 54 // plan would otherwise have been to either update the object in-place or 55 // to take no action on it at all. 56 // 57 // A typical use of this argument is to ask Terraform to replace an object 58 // which the user has determined is somehow degraded (via information from 59 // outside of Terraform), thereby hopefully replacing it with a 60 // fully-functional new object. 61 ForceReplace []addrs.AbsResourceInstance 62 } 63 64 // Plan generates an execution plan for the given context, and returns the 65 // refreshed state. 66 // 67 // The execution plan encapsulates the context and can be stored 68 // in order to reinstantiate a context later for Apply. 69 // 70 // Plan also updates the diff of this context to be the diff generated 71 // by the plan, so Apply can be called after. 72 func (c *Context) Plan(config *configs.Config, prevRunState *states.State, opts *PlanOpts) (*plans.Plan, tfdiags.Diagnostics) { 73 defer c.acquireRun("plan")() 74 var diags tfdiags.Diagnostics 75 76 // Save the downstream functions from needing to deal with these broken situations. 77 // No real callers should rely on these, but we have a bunch of old and 78 // sloppy tests that don't always populate arguments properly. 79 if config == nil { 80 config = configs.NewEmptyConfig() 81 } 82 if prevRunState == nil { 83 prevRunState = states.NewState() 84 } 85 if opts == nil { 86 opts = &PlanOpts{ 87 Mode: plans.NormalMode, 88 } 89 } 90 91 moreDiags := c.checkConfigDependencies(config) 92 diags = diags.Append(moreDiags) 93 // If required dependencies are not available then we'll bail early since 94 // otherwise we're likely to just see a bunch of other errors related to 95 // incompatibilities, which could be overwhelming for the user. 96 if diags.HasErrors() { 97 return nil, diags 98 } 99 100 switch opts.Mode { 101 case plans.NormalMode, plans.DestroyMode: 102 // OK 103 case plans.RefreshOnlyMode: 104 if opts.SkipRefresh { 105 // The CLI layer (and other similar callers) should prevent this 106 // combination of options. 107 diags = diags.Append(tfdiags.Sourceless( 108 tfdiags.Error, 109 "Incompatible plan options", 110 "Cannot skip refreshing in refresh-only mode. This is a bug in Terraform.", 111 )) 112 return nil, diags 113 } 114 default: 115 // The CLI layer (and other similar callers) should not try to 116 // create a context for a mode that Terraform Core doesn't support. 117 diags = diags.Append(tfdiags.Sourceless( 118 tfdiags.Error, 119 "Unsupported plan mode", 120 fmt.Sprintf("Terraform Core doesn't know how to handle plan mode %s. This is a bug in Terraform.", opts.Mode), 121 )) 122 return nil, diags 123 } 124 if len(opts.ForceReplace) > 0 && opts.Mode != plans.NormalMode { 125 // The other modes don't generate no-op or update actions that we might 126 // upgrade to be "replace", so doesn't make sense to combine those. 127 diags = diags.Append(tfdiags.Sourceless( 128 tfdiags.Error, 129 "Unsupported plan mode", 130 "Forcing resource instance replacement (with -replace=...) is allowed only in normal planning mode.", 131 )) 132 return nil, diags 133 } 134 135 // By the time we get here, we should have values defined for all of 136 // the root module variables, even if some of them are "unknown". It's the 137 // caller's responsibility to have already handled the decoding of these 138 // from the various ways the CLI allows them to be set and to produce 139 // user-friendly error messages if they are not all present, and so 140 // the error message from checkInputVariables should never be seen and 141 // includes language asking the user to report a bug. 142 varDiags := checkInputVariables(config.Module.Variables, opts.SetVariables) 143 diags = diags.Append(varDiags) 144 145 if len(opts.Targets) > 0 { 146 diags = diags.Append(tfdiags.Sourceless( 147 tfdiags.Warning, 148 "Resource targeting is in effect", 149 `You are creating a plan with the -target option, which means that the result of this plan may not represent all of the changes requested by the current configuration. 150 151 The -target option is not for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when Terraform specifically suggests to use it as part of an error message.`, 152 )) 153 } 154 155 var plan *plans.Plan 156 var planDiags tfdiags.Diagnostics 157 switch opts.Mode { 158 case plans.NormalMode: 159 plan, planDiags = c.plan(config, prevRunState, opts) 160 case plans.DestroyMode: 161 plan, planDiags = c.destroyPlan(config, prevRunState, opts) 162 case plans.RefreshOnlyMode: 163 plan, planDiags = c.refreshOnlyPlan(config, prevRunState, opts) 164 default: 165 panic(fmt.Sprintf("unsupported plan mode %s", opts.Mode)) 166 } 167 diags = diags.Append(planDiags) 168 if diags.HasErrors() { 169 return nil, diags 170 } 171 172 // convert the variables into the format expected for the plan 173 varVals := make(map[string]plans.DynamicValue, len(opts.SetVariables)) 174 for k, iv := range opts.SetVariables { 175 if iv.Value == cty.NilVal { 176 continue // We only record values that the caller actually set 177 } 178 179 // We use cty.DynamicPseudoType here so that we'll save both the 180 // value _and_ its dynamic type in the plan, so we can recover 181 // exactly the same value later. 182 dv, err := plans.NewDynamicValue(iv.Value, cty.DynamicPseudoType) 183 if err != nil { 184 diags = diags.Append(tfdiags.Sourceless( 185 tfdiags.Error, 186 "Failed to prepare variable value for plan", 187 fmt.Sprintf("The value for variable %q could not be serialized to store in the plan: %s.", k, err), 188 )) 189 continue 190 } 191 varVals[k] = dv 192 } 193 194 // insert the run-specific data from the context into the plan; variables, 195 // targets and provider SHAs. 196 if plan != nil { 197 plan.VariableValues = varVals 198 plan.TargetAddrs = opts.Targets 199 } else if !diags.HasErrors() { 200 panic("nil plan but no errors") 201 } 202 203 relevantAttrs, rDiags := c.relevantResourceAttrsForPlan(config, plan) 204 diags = diags.Append(rDiags) 205 206 plan.RelevantAttributes = relevantAttrs 207 diags = diags.Append(c.checkApplyGraph(plan, config)) 208 209 return plan, diags 210 } 211 212 // checkApplyGraph builds the apply graph out of the current plan to 213 // check for any errors that may arise once the planned changes are added to 214 // the graph. This allows terraform to report errors (mostly cycles) during 215 // plan that would otherwise only crop up during apply 216 func (c *Context) checkApplyGraph(plan *plans.Plan, config *configs.Config) tfdiags.Diagnostics { 217 if plan.Changes.Empty() { 218 log.Println("[DEBUG] no planned changes, skipping apply graph check") 219 return nil 220 } 221 log.Println("[DEBUG] building apply graph to check for errors") 222 _, _, diags := c.applyGraph(plan, config, true) 223 return diags 224 } 225 226 var DefaultPlanOpts = &PlanOpts{ 227 Mode: plans.NormalMode, 228 } 229 230 // SimplePlanOpts is a constructor to help with creating "simple" values of 231 // PlanOpts which only specify a mode and input variables. 232 // 233 // This helper function is primarily intended for use in straightforward 234 // tests that don't need any of the more "esoteric" planning options. For 235 // handling real user requests to run Terraform, it'd probably be better 236 // to construct a *PlanOpts value directly and provide a way for the user 237 // to set values for all of its fields. 238 // 239 // The "mode" and "setVariables" arguments become the values of the "Mode" 240 // and "SetVariables" fields in the result. Refer to the PlanOpts type 241 // documentation to learn about the meanings of those fields. 242 func SimplePlanOpts(mode plans.Mode, setVariables InputValues) *PlanOpts { 243 return &PlanOpts{ 244 Mode: mode, 245 SetVariables: setVariables, 246 } 247 } 248 249 func (c *Context) plan(config *configs.Config, prevRunState *states.State, opts *PlanOpts) (*plans.Plan, tfdiags.Diagnostics) { 250 var diags tfdiags.Diagnostics 251 252 if opts.Mode != plans.NormalMode { 253 panic(fmt.Sprintf("called Context.plan with %s", opts.Mode)) 254 } 255 256 plan, walkDiags := c.planWalk(config, prevRunState, opts) 257 diags = diags.Append(walkDiags) 258 if diags.HasErrors() { 259 return nil, diags 260 } 261 262 // The refreshed state ends up with some placeholder objects in it for 263 // objects pending creation. We only really care about those being in 264 // the working state, since that's what we're going to use when applying, 265 // so we'll prune them all here. 266 plan.PriorState.SyncWrapper().RemovePlannedResourceInstanceObjects() 267 268 return plan, diags 269 } 270 271 func (c *Context) refreshOnlyPlan(config *configs.Config, prevRunState *states.State, opts *PlanOpts) (*plans.Plan, tfdiags.Diagnostics) { 272 var diags tfdiags.Diagnostics 273 274 if opts.Mode != plans.RefreshOnlyMode { 275 panic(fmt.Sprintf("called Context.refreshOnlyPlan with %s", opts.Mode)) 276 } 277 278 plan, walkDiags := c.planWalk(config, prevRunState, opts) 279 diags = diags.Append(walkDiags) 280 if diags.HasErrors() { 281 return nil, diags 282 } 283 284 // If the graph builder and graph nodes correctly obeyed our directive 285 // to refresh only, the set of resource changes should always be empty. 286 // We'll safety-check that here so we can return a clear message about it, 287 // rather than probably just generating confusing output at the UI layer. 288 if len(plan.Changes.Resources) != 0 { 289 // Some extra context in the logs in case the user reports this message 290 // as a bug, as a starting point for debugging. 291 for _, rc := range plan.Changes.Resources { 292 if depKey := rc.DeposedKey; depKey == states.NotDeposed { 293 log.Printf("[DEBUG] Refresh-only plan includes %s change for %s", rc.Action, rc.Addr) 294 } else { 295 log.Printf("[DEBUG] Refresh-only plan includes %s change for %s deposed object %s", rc.Action, rc.Addr, depKey) 296 } 297 } 298 diags = diags.Append(tfdiags.Sourceless( 299 tfdiags.Error, 300 "Invalid refresh-only plan", 301 "Terraform generated planned resource changes in a refresh-only plan. This is a bug in Terraform.", 302 )) 303 } 304 305 // Prune out any placeholder objects we put in the state to represent 306 // objects that would need to be created. 307 plan.PriorState.SyncWrapper().RemovePlannedResourceInstanceObjects() 308 309 // We don't populate RelevantResources for a refresh-only plan, because 310 // they never have any planned actions and so no resource can ever be 311 // "relevant" per the intended meaning of that field. 312 313 return plan, diags 314 } 315 316 func (c *Context) destroyPlan(config *configs.Config, prevRunState *states.State, opts *PlanOpts) (*plans.Plan, tfdiags.Diagnostics) { 317 var diags tfdiags.Diagnostics 318 pendingPlan := &plans.Plan{} 319 320 if opts.Mode != plans.DestroyMode { 321 panic(fmt.Sprintf("called Context.destroyPlan with %s", opts.Mode)) 322 } 323 324 priorState := prevRunState 325 326 // A destroy plan starts by running Refresh to read any pending data 327 // sources, and remove missing managed resources. This is required because 328 // a "destroy plan" is only creating delete changes, and is essentially a 329 // local operation. 330 // 331 // NOTE: if skipRefresh _is_ set then we'll rely on the destroy-plan walk 332 // below to upgrade the prevRunState and priorState both to the latest 333 // resource type schemas, so NodePlanDestroyableResourceInstance.Execute 334 // must coordinate with this by taking that action only when c.skipRefresh 335 // _is_ set. This coupling between the two is unfortunate but necessary 336 // to work within our current structure. 337 if !opts.SkipRefresh { 338 log.Printf("[TRACE] Context.destroyPlan: calling Context.plan to get the effect of refreshing the prior state") 339 normalOpts := *opts 340 normalOpts.Mode = plans.NormalMode 341 refreshPlan, refreshDiags := c.plan(config, prevRunState, &normalOpts) 342 if refreshDiags.HasErrors() { 343 // NOTE: Normally we'd append diagnostics regardless of whether 344 // there are errors, just in case there are warnings we'd want to 345 // preserve, but we're intentionally _not_ doing that here because 346 // if the first plan succeeded then we'll be running another plan 347 // in DestroyMode below, and we don't want to double-up any 348 // warnings that both plan walks would generate. 349 // (This does mean we won't show any warnings that would've been 350 // unique to only this walk, but we're assuming here that if the 351 // warnings aren't also applicable to a destroy plan then we'd 352 // rather not show them here, because this non-destroy plan for 353 // refreshing is largely an implementation detail.) 354 diags = diags.Append(refreshDiags) 355 return nil, diags 356 } 357 358 // insert the refreshed state into the destroy plan result, and ignore 359 // the changes recorded from the refresh. 360 pendingPlan.PriorState = refreshPlan.PriorState.DeepCopy() 361 pendingPlan.PrevRunState = refreshPlan.PrevRunState.DeepCopy() 362 log.Printf("[TRACE] Context.destroyPlan: now _really_ creating a destroy plan") 363 364 // We'll use the refreshed state -- which is the "prior state" from 365 // the perspective of this "pending plan" -- as the starting state 366 // for our destroy-plan walk, so it can take into account if we 367 // detected during refreshing that anything was already deleted outside 368 // of Terraform. 369 priorState = pendingPlan.PriorState 370 } 371 372 destroyPlan, walkDiags := c.planWalk(config, priorState, opts) 373 diags = diags.Append(walkDiags) 374 if walkDiags.HasErrors() { 375 return nil, diags 376 } 377 378 if !opts.SkipRefresh { 379 // If we didn't skip refreshing then we want the previous run state 380 // prior state to be the one we originally fed into the c.plan call 381 // above, not the refreshed version we used for the destroy walk. 382 destroyPlan.PrevRunState = pendingPlan.PrevRunState 383 } 384 385 relevantAttrs, rDiags := c.relevantResourceAttrsForPlan(config, destroyPlan) 386 diags = diags.Append(rDiags) 387 388 destroyPlan.RelevantAttributes = relevantAttrs 389 return destroyPlan, diags 390 } 391 392 func (c *Context) prePlanFindAndApplyMoves(config *configs.Config, prevRunState *states.State, targets []addrs.Targetable) ([]refactoring.MoveStatement, refactoring.MoveResults) { 393 explicitMoveStmts := refactoring.FindMoveStatements(config) 394 implicitMoveStmts := refactoring.ImpliedMoveStatements(config, prevRunState, explicitMoveStmts) 395 var moveStmts []refactoring.MoveStatement 396 if stmtsLen := len(explicitMoveStmts) + len(implicitMoveStmts); stmtsLen > 0 { 397 moveStmts = make([]refactoring.MoveStatement, 0, stmtsLen) 398 moveStmts = append(moveStmts, explicitMoveStmts...) 399 moveStmts = append(moveStmts, implicitMoveStmts...) 400 } 401 moveResults := refactoring.ApplyMoves(moveStmts, prevRunState) 402 return moveStmts, moveResults 403 } 404 405 func (c *Context) prePlanVerifyTargetedMoves(moveResults refactoring.MoveResults, targets []addrs.Targetable) tfdiags.Diagnostics { 406 if len(targets) < 1 { 407 return nil // the following only matters when targeting 408 } 409 410 var diags tfdiags.Diagnostics 411 412 var excluded []addrs.AbsResourceInstance 413 for _, result := range moveResults.Changes.Values() { 414 fromMatchesTarget := false 415 toMatchesTarget := false 416 for _, targetAddr := range targets { 417 if targetAddr.TargetContains(result.From) { 418 fromMatchesTarget = true 419 } 420 if targetAddr.TargetContains(result.To) { 421 toMatchesTarget = true 422 } 423 } 424 if !fromMatchesTarget { 425 excluded = append(excluded, result.From) 426 } 427 if !toMatchesTarget { 428 excluded = append(excluded, result.To) 429 } 430 } 431 if len(excluded) > 0 { 432 sort.Slice(excluded, func(i, j int) bool { 433 return excluded[i].Less(excluded[j]) 434 }) 435 436 var listBuf strings.Builder 437 var prevResourceAddr addrs.AbsResource 438 for _, instAddr := range excluded { 439 // Targeting generally ends up selecting whole resources rather 440 // than individual instances, because we don't factor in 441 // individual instances until DynamicExpand, so we're going to 442 // always show whole resource addresses here, excluding any 443 // instance keys. (This also neatly avoids dealing with the 444 // different quoting styles required for string instance keys 445 // on different shells, which is handy.) 446 // 447 // To avoid showing duplicates when we have multiple instances 448 // of the same resource, we'll remember the most recent 449 // resource we rendered in prevResource, which is sufficient 450 // because we sorted the list of instance addresses above, and 451 // our sort order always groups together instances of the same 452 // resource. 453 resourceAddr := instAddr.ContainingResource() 454 if resourceAddr.Equal(prevResourceAddr) { 455 continue 456 } 457 fmt.Fprintf(&listBuf, "\n -target=%q", resourceAddr.String()) 458 prevResourceAddr = resourceAddr 459 } 460 diags = diags.Append(tfdiags.Sourceless( 461 tfdiags.Error, 462 "Moved resource instances excluded by targeting", 463 fmt.Sprintf( 464 "Resource instances in your current state have moved to new addresses in the latest configuration. Terraform must include those resource instances while planning in order to ensure a correct result, but your -target=... options to not fully cover all of those resource instances.\n\nTo create a valid plan, either remove your -target=... options altogether or add the following additional target options:%s\n\nNote that adding these options may include further additional resource instances in your plan, in order to respect object dependencies.", 465 listBuf.String(), 466 ), 467 )) 468 } 469 470 return diags 471 } 472 473 func (c *Context) postPlanValidateMoves(config *configs.Config, stmts []refactoring.MoveStatement, allInsts instances.Set) tfdiags.Diagnostics { 474 return refactoring.ValidateMoves(stmts, config, allInsts) 475 } 476 477 func (c *Context) planWalk(config *configs.Config, prevRunState *states.State, opts *PlanOpts) (*plans.Plan, tfdiags.Diagnostics) { 478 var diags tfdiags.Diagnostics 479 log.Printf("[DEBUG] Building and walking plan graph for %s", opts.Mode) 480 481 prevRunState = prevRunState.DeepCopy() // don't modify the caller's object when we process the moves 482 moveStmts, moveResults := c.prePlanFindAndApplyMoves(config, prevRunState, opts.Targets) 483 484 // If resource targeting is in effect then it might conflict with the 485 // move result. 486 diags = diags.Append(c.prePlanVerifyTargetedMoves(moveResults, opts.Targets)) 487 if diags.HasErrors() { 488 // We'll return early here, because if we have any moved resource 489 // instances excluded by targeting then planning is likely to encounter 490 // strange problems that may lead to confusing error messages. 491 return nil, diags 492 } 493 494 graph, walkOp, moreDiags := c.planGraph(config, prevRunState, opts) 495 diags = diags.Append(moreDiags) 496 if diags.HasErrors() { 497 return nil, diags 498 } 499 500 // If we get here then we should definitely have a non-nil "graph", which 501 // we can now walk. 502 changes := plans.NewChanges() 503 walker, walkDiags := c.walk(graph, walkOp, &graphWalkOpts{ 504 Config: config, 505 InputState: prevRunState, 506 Changes: changes, 507 MoveResults: moveResults, 508 }) 509 diags = diags.Append(walker.NonFatalDiagnostics) 510 diags = diags.Append(walkDiags) 511 moveValidateDiags := c.postPlanValidateMoves(config, moveStmts, walker.InstanceExpander.AllInstances()) 512 if moveValidateDiags.HasErrors() { 513 // If any of the move statements are invalid then those errors take 514 // precedence over any other errors because an incomplete move graph 515 // is quite likely to be the _cause_ of various errors. This oddity 516 // comes from the fact that we need to apply the moves before we 517 // actually validate them, because validation depends on the result 518 // of first trying to plan. 519 return nil, moveValidateDiags 520 } 521 diags = diags.Append(moveValidateDiags) // might just contain warnings 522 523 if moveResults.Blocked.Len() > 0 && !diags.HasErrors() { 524 // If we had blocked moves and we're not going to be returning errors 525 // then we'll report the blockers as a warning. We do this only in the 526 // absense of errors because invalid move statements might well be 527 // the root cause of the blockers, and so better to give an actionable 528 // error message than a less-actionable warning. 529 diags = diags.Append(blockedMovesWarningDiag(moveResults)) 530 } 531 532 prevRunState = walker.PrevRunState.Close() 533 priorState := walker.RefreshState.Close() 534 driftedResources, driftDiags := c.driftedResources(config, prevRunState, priorState, moveResults) 535 diags = diags.Append(driftDiags) 536 537 plan := &plans.Plan{ 538 UIMode: opts.Mode, 539 Changes: changes, 540 DriftedResources: driftedResources, 541 PrevRunState: prevRunState, 542 PriorState: priorState, 543 Checks: states.NewCheckResults(walker.Checks), 544 545 // Other fields get populated by Context.Plan after we return 546 } 547 return plan, diags 548 } 549 550 func (c *Context) planGraph(config *configs.Config, prevRunState *states.State, opts *PlanOpts) (*Graph, walkOperation, tfdiags.Diagnostics) { 551 switch mode := opts.Mode; mode { 552 case plans.NormalMode: 553 graph, diags := (&PlanGraphBuilder{ 554 Config: config, 555 State: prevRunState, 556 RootVariableValues: opts.SetVariables, 557 Plugins: c.plugins, 558 Targets: opts.Targets, 559 ForceReplace: opts.ForceReplace, 560 skipRefresh: opts.SkipRefresh, 561 Operation: walkPlan, 562 }).Build(addrs.RootModuleInstance) 563 return graph, walkPlan, diags 564 case plans.RefreshOnlyMode: 565 graph, diags := (&PlanGraphBuilder{ 566 Config: config, 567 State: prevRunState, 568 RootVariableValues: opts.SetVariables, 569 Plugins: c.plugins, 570 Targets: opts.Targets, 571 skipRefresh: opts.SkipRefresh, 572 skipPlanChanges: true, // this activates "refresh only" mode. 573 Operation: walkPlan, 574 }).Build(addrs.RootModuleInstance) 575 return graph, walkPlan, diags 576 case plans.DestroyMode: 577 graph, diags := (&PlanGraphBuilder{ 578 Config: config, 579 State: prevRunState, 580 RootVariableValues: opts.SetVariables, 581 Plugins: c.plugins, 582 Targets: opts.Targets, 583 skipRefresh: opts.SkipRefresh, 584 Operation: walkPlanDestroy, 585 }).Build(addrs.RootModuleInstance) 586 return graph, walkPlanDestroy, diags 587 default: 588 // The above should cover all plans.Mode values 589 panic(fmt.Sprintf("unsupported plan mode %s", mode)) 590 } 591 } 592 593 func (c *Context) planFlatEarthGraph(config *configs.Config, prevRunState *states.State, opts *PlanOpts) map[string]*configs.Resource { 594 // TODO: consider if we need to alter behaviour based on plan modes 595 return (&PlanGraphBuilder{ 596 Config: config, 597 State: prevRunState, 598 RootVariableValues: opts.SetVariables, 599 Plugins: c.plugins, 600 Targets: opts.Targets, 601 ForceReplace: opts.ForceReplace, 602 skipRefresh: opts.SkipRefresh, 603 Operation: walkPlan, 604 }).BuildFlatEarthGraph(addrs.RootModuleInstance) 605 } 606 607 func (c *Context) driftedResources(config *configs.Config, oldState, newState *states.State, moves refactoring.MoveResults) ([]*plans.ResourceInstanceChangeSrc, tfdiags.Diagnostics) { 608 var diags tfdiags.Diagnostics 609 610 if newState.ManagedResourcesEqual(oldState) && moves.Changes.Len() == 0 { 611 // Nothing to do, because we only detect and report drift for managed 612 // resource instances. 613 return nil, diags 614 } 615 616 schemas, schemaDiags := c.Schemas(config, newState) 617 diags = diags.Append(schemaDiags) 618 if diags.HasErrors() { 619 return nil, diags 620 } 621 622 var drs []*plans.ResourceInstanceChangeSrc 623 624 for _, ms := range oldState.Modules { 625 for _, rs := range ms.Resources { 626 if rs.Addr.Resource.Mode != addrs.ManagedResourceMode { 627 // Drift reporting is only for managed resources 628 continue 629 } 630 631 provider := rs.ProviderConfig.Provider 632 for key, oldIS := range rs.Instances { 633 if oldIS.Current == nil { 634 // Not interested in instances that only have deposed objects 635 continue 636 } 637 addr := rs.Addr.Instance(key) 638 639 // Previous run address defaults to the current address, but 640 // can differ if the resource moved before refreshing 641 prevRunAddr := addr 642 if move, ok := moves.Changes.GetOk(addr); ok { 643 prevRunAddr = move.From 644 } 645 646 newIS := newState.ResourceInstance(addr) 647 648 schema, _ := schemas.ResourceTypeConfig( 649 provider, 650 addr.Resource.Resource.Mode, 651 addr.Resource.Resource.Type, 652 ) 653 if schema == nil { 654 // This should never happen, but just in case 655 return nil, diags.Append(tfdiags.Sourceless( 656 tfdiags.Error, 657 "Missing resource schema from provider", 658 fmt.Sprintf("No resource schema found for %s.", addr.Resource.Resource.Type), 659 )) 660 } 661 ty := schema.ImpliedType() 662 663 oldObj, err := oldIS.Current.Decode(ty) 664 if err != nil { 665 // This should also never happen 666 return nil, diags.Append(tfdiags.Sourceless( 667 tfdiags.Error, 668 "Failed to decode resource from state", 669 fmt.Sprintf("Error decoding %q from previous state: %s", addr.String(), err), 670 )) 671 } 672 673 var newObj *states.ResourceInstanceObject 674 if newIS != nil && newIS.Current != nil { 675 newObj, err = newIS.Current.Decode(ty) 676 if err != nil { 677 // This should also never happen 678 return nil, diags.Append(tfdiags.Sourceless( 679 tfdiags.Error, 680 "Failed to decode resource from state", 681 fmt.Sprintf("Error decoding %q from prior state: %s", addr.String(), err), 682 )) 683 } 684 } 685 686 var oldVal, newVal cty.Value 687 oldVal = oldObj.Value 688 if newObj != nil { 689 newVal = newObj.Value 690 } else { 691 newVal = cty.NullVal(ty) 692 } 693 694 if oldVal.RawEquals(newVal) && addr.Equal(prevRunAddr) { 695 // No drift if the two values are semantically equivalent 696 // and no move has happened 697 continue 698 } 699 700 // We can detect three types of changes after refreshing state, 701 // only two of which are easily understood as "drift": 702 // 703 // - Resources which were deleted outside of Terraform; 704 // - Resources where the object value has changed outside of 705 // Terraform; 706 // - Resources which have been moved without other changes. 707 // 708 // All of these are returned as drift, to allow refresh-only plans 709 // to present a full set of changes which will be applied. 710 var action plans.Action 711 switch { 712 case newVal.IsNull(): 713 action = plans.Delete 714 case !oldVal.RawEquals(newVal): 715 action = plans.Update 716 default: 717 action = plans.NoOp 718 } 719 720 change := &plans.ResourceInstanceChange{ 721 Addr: addr, 722 PrevRunAddr: prevRunAddr, 723 ProviderAddr: rs.ProviderConfig, 724 Change: plans.Change{ 725 Action: action, 726 Before: oldVal, 727 After: newVal, 728 }, 729 } 730 731 changeSrc, err := change.Encode(ty) 732 if err != nil { 733 diags = diags.Append(err) 734 return nil, diags 735 } 736 737 drs = append(drs, changeSrc) 738 } 739 } 740 } 741 742 return drs, diags 743 } 744 745 // PlanGraphForUI is a last vestage of graphs in the public interface of Context 746 // (as opposed to graphs as an implementation detail) intended only for use 747 // by the "terraform graph" command when asked to render a plan-time graph. 748 // 749 // The result of this is intended only for rendering ot the user as a dot 750 // graph, and so may change in future in order to make the result more useful 751 // in that context, even if drifts away from the physical graph that Terraform 752 // Core currently uses as an implementation detail of planning. 753 func (c *Context) PlanGraphForUI(config *configs.Config, prevRunState *states.State, mode plans.Mode) (*Graph, tfdiags.Diagnostics) { 754 // For now though, this really is just the internal graph, confusing 755 // implementation details and all. 756 757 var diags tfdiags.Diagnostics 758 759 opts := &PlanOpts{Mode: mode} 760 761 graph, _, moreDiags := c.planGraph(config, prevRunState, opts) 762 diags = diags.Append(moreDiags) 763 return graph, diags 764 } 765 766 // Same as PlanGraphForUI, but modified rather naively for getting a map of managed resources 767 func (c *Context) PlanFlatEarthGraph(config *configs.Config, prevRunState *states.State, mode plans.Mode) map[string]*configs.Resource { 768 opts := &PlanOpts{Mode: mode} 769 770 return c.planFlatEarthGraph(config, prevRunState, opts) 771 } 772 773 func blockedMovesWarningDiag(results refactoring.MoveResults) tfdiags.Diagnostic { 774 if results.Blocked.Len() < 1 { 775 // Caller should check first 776 panic("request to render blocked moves warning without any blocked moves") 777 } 778 779 var itemsBuf bytes.Buffer 780 for _, blocked := range results.Blocked.Values() { 781 fmt.Fprintf(&itemsBuf, "\n - %s could not move to %s", blocked.Actual, blocked.Wanted) 782 } 783 784 return tfdiags.Sourceless( 785 tfdiags.Warning, 786 "Unresolved resource instance address changes", 787 fmt.Sprintf( 788 "Terraform tried to adjust resource instance addresses in the prior state based on change information recorded in the configuration, but some adjustments did not succeed due to existing objects already at the intended addresses:%s\n\nTerraform has planned to destroy these objects. If Terraform's proposed changes aren't appropriate, you must first resolve the conflicts using the \"terraform state\" subcommands and then create a new plan.", 789 itemsBuf.String(), 790 ), 791 ) 792 } 793 794 // referenceAnalyzer returns a globalref.Analyzer object to help with 795 // global analysis of references within the configuration that's attached 796 // to the receiving context. 797 func (c *Context) referenceAnalyzer(config *configs.Config, state *states.State) (*globalref.Analyzer, tfdiags.Diagnostics) { 798 schemas, diags := c.Schemas(config, state) 799 if diags.HasErrors() { 800 return nil, diags 801 } 802 return globalref.NewAnalyzer(config, schemas.Providers), diags 803 } 804 805 // relevantResourcesForPlan implements the heuristic we use to populate the 806 // RelevantResources field of returned plans. 807 func (c *Context) relevantResourceAttrsForPlan(config *configs.Config, plan *plans.Plan) ([]globalref.ResourceAttr, tfdiags.Diagnostics) { 808 azr, diags := c.referenceAnalyzer(config, plan.PriorState) 809 if diags.HasErrors() { 810 return nil, diags 811 } 812 813 var refs []globalref.Reference 814 for _, change := range plan.Changes.Resources { 815 if change.Action == plans.NoOp { 816 continue 817 } 818 819 moreRefs := azr.ReferencesFromResourceInstance(change.Addr) 820 refs = append(refs, moreRefs...) 821 } 822 823 for _, change := range plan.Changes.Outputs { 824 if change.Action == plans.NoOp { 825 continue 826 } 827 828 moreRefs := azr.ReferencesFromOutputValue(change.Addr) 829 refs = append(refs, moreRefs...) 830 } 831 832 var contributors []globalref.ResourceAttr 833 834 for _, ref := range azr.ContributingResourceReferences(refs...) { 835 if res, ok := ref.ResourceAttr(); ok { 836 contributors = append(contributors, res) 837 } 838 } 839 840 return contributors, diags 841 }