github.com/opentofu/opentofu@v1.7.1/internal/tofu/context_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 "bytes" 10 "fmt" 11 "log" 12 "sort" 13 "strings" 14 "time" 15 16 "github.com/hashicorp/hcl/v2" 17 18 "github.com/zclconf/go-cty/cty" 19 20 "github.com/opentofu/opentofu/internal/addrs" 21 "github.com/opentofu/opentofu/internal/configs" 22 "github.com/opentofu/opentofu/internal/instances" 23 "github.com/opentofu/opentofu/internal/lang/globalref" 24 "github.com/opentofu/opentofu/internal/plans" 25 "github.com/opentofu/opentofu/internal/refactoring" 26 "github.com/opentofu/opentofu/internal/states" 27 "github.com/opentofu/opentofu/internal/tfdiags" 28 ) 29 30 // PlanOpts are the various options that affect the details of how OpenTofu 31 // will build a plan. 32 type PlanOpts struct { 33 // Mode defines what variety of plan the caller wishes to create. 34 // Refer to the documentation of the plans.Mode type and its values 35 // for more information. 36 Mode plans.Mode 37 38 // SkipRefresh specifies to trust that the current values for managed 39 // resource instances in the prior state are accurate and to therefore 40 // disable the usual step of fetching updated values for each resource 41 // instance using its corresponding provider. 42 SkipRefresh bool 43 44 // PreDestroyRefresh indicated that this is being passed to a plan used to 45 // refresh the state immediately before a destroy plan. 46 // FIXME: This is a temporary fix to allow the pre-destroy refresh to 47 // succeed. The refreshing operation during destroy must be a special case, 48 // which can allow for missing instances in the state, and avoid blocking 49 // on failing condition tests. The destroy plan itself should be 50 // responsible for this special case of refreshing, and the separate 51 // pre-destroy plan removed entirely. 52 PreDestroyRefresh bool 53 54 // SetVariables are the raw values for root module variables as provided 55 // by the user who is requesting the run, prior to any normalization or 56 // substitution of defaults. See the documentation for the InputValue 57 // type for more information on how to correctly populate this. 58 SetVariables InputValues 59 60 // If Targets has a non-zero length then it activates targeted planning 61 // mode, where OpenTofu will take actions only for resource instances 62 // mentioned in this set and any other objects those resource instances 63 // depend on. 64 // 65 // Targeted planning mode is intended for exceptional use only, 66 // and so populating this field will cause OpenTofu to generate extra 67 // warnings as part of the planning result. 68 Targets []addrs.Targetable 69 70 // ForceReplace is a set of resource instance addresses whose corresponding 71 // objects should be forced planned for replacement if the provider's 72 // plan would otherwise have been to either update the object in-place or 73 // to take no action on it at all. 74 // 75 // A typical use of this argument is to ask OpenTofu to replace an object 76 // which the user has determined is somehow degraded (via information from 77 // outside of OpenTofu), thereby hopefully replacing it with a 78 // fully-functional new object. 79 ForceReplace []addrs.AbsResourceInstance 80 81 // ExternalReferences allows the external caller to pass in references to 82 // nodes that should not be pruned even if they are not referenced within 83 // the actual graph. 84 ExternalReferences []*addrs.Reference 85 86 // ImportTargets is a list of target resources to import. These resources 87 // will be added to the plan graph. 88 ImportTargets []*ImportTarget 89 90 // EndpointsToRemove are the list of resources and modules to forget from 91 // the state. 92 EndpointsToRemove []addrs.ConfigRemovable 93 94 // GenerateConfig tells OpenTofu where to write any generated configuration 95 // for any ImportTargets that do not have configuration already. 96 // 97 // If empty, then no config will be generated. 98 GenerateConfigPath string 99 } 100 101 // Plan generates an execution plan by comparing the given configuration 102 // with the given previous run state. 103 // 104 // The given planning options allow control of various other details of the 105 // planning process that are not represented directly in the configuration. 106 // You can use tofu.DefaultPlanOpts to generate a normal plan with no 107 // special options. 108 // 109 // If the returned diagnostics contains no errors then the returned plan is 110 // applyable, although OpenTofu cannot guarantee that applying it will fully 111 // succeed. If the returned diagnostics contains errors but this method 112 // still returns a non-nil Plan then the plan describes the subset of actions 113 // planned so far, which is not safe to apply but could potentially be used 114 // by the UI layer to give extra context to support understanding of the 115 // returned error messages. 116 func (c *Context) Plan(config *configs.Config, prevRunState *states.State, opts *PlanOpts) (*plans.Plan, tfdiags.Diagnostics) { 117 defer c.acquireRun("plan")() 118 var diags tfdiags.Diagnostics 119 120 // Save the downstream functions from needing to deal with these broken situations. 121 // No real callers should rely on these, but we have a bunch of old and 122 // sloppy tests that don't always populate arguments properly. 123 if config == nil { 124 config = configs.NewEmptyConfig() 125 } 126 if prevRunState == nil { 127 prevRunState = states.NewState() 128 } 129 if opts == nil { 130 opts = &PlanOpts{ 131 Mode: plans.NormalMode, 132 } 133 } 134 135 moreDiags := c.checkConfigDependencies(config) 136 diags = diags.Append(moreDiags) 137 // If required dependencies are not available then we'll bail early since 138 // otherwise we're likely to just see a bunch of other errors related to 139 // incompatibilities, which could be overwhelming for the user. 140 if diags.HasErrors() { 141 return nil, diags 142 } 143 144 switch opts.Mode { 145 case plans.NormalMode, plans.DestroyMode: 146 // OK 147 case plans.RefreshOnlyMode: 148 if opts.SkipRefresh { 149 // The CLI layer (and other similar callers) should prevent this 150 // combination of options. 151 diags = diags.Append(tfdiags.Sourceless( 152 tfdiags.Error, 153 "Incompatible plan options", 154 "Cannot skip refreshing in refresh-only mode. This is a bug in OpenTofu.", 155 )) 156 return nil, diags 157 } 158 default: 159 // The CLI layer (and other similar callers) should not try to 160 // create a context for a mode that OpenTofu Core doesn't support. 161 diags = diags.Append(tfdiags.Sourceless( 162 tfdiags.Error, 163 "Unsupported plan mode", 164 fmt.Sprintf("OpenTofu Core doesn't know how to handle plan mode %s. This is a bug in OpenTofu.", opts.Mode), 165 )) 166 return nil, diags 167 } 168 if len(opts.ForceReplace) > 0 && opts.Mode != plans.NormalMode { 169 // The other modes don't generate no-op or update actions that we might 170 // upgrade to be "replace", so doesn't make sense to combine those. 171 diags = diags.Append(tfdiags.Sourceless( 172 tfdiags.Error, 173 "Unsupported plan mode", 174 "Forcing resource instance replacement (with -replace=...) is allowed only in normal planning mode.", 175 )) 176 return nil, diags 177 } 178 179 // By the time we get here, we should have values defined for all of 180 // the root module variables, even if some of them are "unknown". It's the 181 // caller's responsibility to have already handled the decoding of these 182 // from the various ways the CLI allows them to be set and to produce 183 // user-friendly error messages if they are not all present, and so 184 // the error message from checkInputVariables should never be seen and 185 // includes language asking the user to report a bug. 186 varDiags := checkInputVariables(config.Module.Variables, opts.SetVariables) 187 diags = diags.Append(varDiags) 188 189 if len(opts.Targets) > 0 { 190 diags = diags.Append(tfdiags.Sourceless( 191 tfdiags.Warning, 192 "Resource targeting is in effect", 193 `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. 194 195 The -target option is not for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when OpenTofu specifically suggests to use it as part of an error message.`, 196 )) 197 } 198 199 var plan *plans.Plan 200 var planDiags tfdiags.Diagnostics 201 switch opts.Mode { 202 case plans.NormalMode: 203 plan, planDiags = c.plan(config, prevRunState, opts) 204 case plans.DestroyMode: 205 plan, planDiags = c.destroyPlan(config, prevRunState, opts) 206 case plans.RefreshOnlyMode: 207 plan, planDiags = c.refreshOnlyPlan(config, prevRunState, opts) 208 default: 209 panic(fmt.Sprintf("unsupported plan mode %s", opts.Mode)) 210 } 211 diags = diags.Append(planDiags) 212 // NOTE: We're intentionally not returning early when diags.HasErrors 213 // here because we'll still populate other metadata below on a best-effort 214 // basis to try to give the UI some extra context to return alongside the 215 // error messages. 216 217 // convert the variables into the format expected for the plan 218 varVals := make(map[string]plans.DynamicValue, len(opts.SetVariables)) 219 for k, iv := range opts.SetVariables { 220 if iv.Value == cty.NilVal { 221 continue // We only record values that the caller actually set 222 } 223 224 // We use cty.DynamicPseudoType here so that we'll save both the 225 // value _and_ its dynamic type in the plan, so we can recover 226 // exactly the same value later. 227 dv, err := plans.NewDynamicValue(iv.Value, cty.DynamicPseudoType) 228 if err != nil { 229 diags = diags.Append(tfdiags.Sourceless( 230 tfdiags.Error, 231 "Failed to prepare variable value for plan", 232 fmt.Sprintf("The value for variable %q could not be serialized to store in the plan: %s.", k, err), 233 )) 234 continue 235 } 236 varVals[k] = dv 237 } 238 239 // insert the run-specific data from the context into the plan; variables, 240 // targets and provider SHAs. 241 if plan != nil { 242 plan.VariableValues = varVals 243 plan.TargetAddrs = opts.Targets 244 } else if !diags.HasErrors() { 245 panic("nil plan but no errors") 246 } 247 248 if plan != nil { 249 relevantAttrs, rDiags := c.relevantResourceAttrsForPlan(config, plan) 250 diags = diags.Append(rDiags) 251 plan.RelevantAttributes = relevantAttrs 252 } 253 254 if diags.HasErrors() { 255 // We can't proceed further with an invalid plan, because an invalid 256 // plan isn't applyable by definition. 257 if plan != nil { 258 // We'll explicitly mark our plan as errored so that it can't 259 // be accidentally applied even though it's incomplete. 260 plan.Errored = true 261 } 262 return plan, diags 263 } 264 265 diags = diags.Append(c.checkApplyGraph(plan, config)) 266 267 return plan, diags 268 } 269 270 // checkApplyGraph builds the apply graph out of the current plan to 271 // check for any errors that may arise once the planned changes are added to 272 // the graph. This allows tofu to report errors (mostly cycles) during 273 // plan that would otherwise only crop up during apply 274 func (c *Context) checkApplyGraph(plan *plans.Plan, config *configs.Config) tfdiags.Diagnostics { 275 if plan.Changes.Empty() { 276 log.Println("[DEBUG] no planned changes, skipping apply graph check") 277 return nil 278 } 279 log.Println("[DEBUG] building apply graph to check for errors") 280 _, _, diags := c.applyGraph(plan, config, true) 281 return diags 282 } 283 284 var DefaultPlanOpts = &PlanOpts{ 285 Mode: plans.NormalMode, 286 } 287 288 // SimplePlanOpts is a constructor to help with creating "simple" values of 289 // PlanOpts which only specify a mode and input variables. 290 // 291 // This helper function is primarily intended for use in straightforward 292 // tests that don't need any of the more "esoteric" planning options. For 293 // handling real user requests to run OpenTofu, it'd probably be better 294 // to construct a *PlanOpts value directly and provide a way for the user 295 // to set values for all of its fields. 296 // 297 // The "mode" and "setVariables" arguments become the values of the "Mode" 298 // and "SetVariables" fields in the result. Refer to the PlanOpts type 299 // documentation to learn about the meanings of those fields. 300 func SimplePlanOpts(mode plans.Mode, setVariables InputValues) *PlanOpts { 301 return &PlanOpts{ 302 Mode: mode, 303 SetVariables: setVariables, 304 } 305 } 306 307 func (c *Context) plan(config *configs.Config, prevRunState *states.State, opts *PlanOpts) (*plans.Plan, tfdiags.Diagnostics) { 308 var diags tfdiags.Diagnostics 309 310 if opts.Mode != plans.NormalMode { 311 panic(fmt.Sprintf("called Context.plan with %s", opts.Mode)) 312 } 313 314 opts.ImportTargets = c.findImportTargets(config) 315 importTargetDiags := c.validateImportTargets(config, opts.ImportTargets, opts.GenerateConfigPath) 316 diags = diags.Append(importTargetDiags) 317 if diags.HasErrors() { 318 return nil, diags 319 } 320 321 var endpointsToRemoveDiags tfdiags.Diagnostics 322 opts.EndpointsToRemove, endpointsToRemoveDiags = refactoring.GetEndpointsToRemove(config) 323 diags = diags.Append(endpointsToRemoveDiags) 324 325 if diags.HasErrors() { 326 return nil, diags 327 } 328 329 plan, walkDiags := c.planWalk(config, prevRunState, opts) 330 diags = diags.Append(walkDiags) 331 332 return plan, diags 333 } 334 335 func (c *Context) refreshOnlyPlan(config *configs.Config, prevRunState *states.State, opts *PlanOpts) (*plans.Plan, tfdiags.Diagnostics) { 336 var diags tfdiags.Diagnostics 337 338 if opts.Mode != plans.RefreshOnlyMode { 339 panic(fmt.Sprintf("called Context.refreshOnlyPlan with %s", opts.Mode)) 340 } 341 342 plan, walkDiags := c.planWalk(config, prevRunState, opts) 343 diags = diags.Append(walkDiags) 344 if diags.HasErrors() { 345 // Non-nil plan along with errors indicates a non-applyable partial 346 // plan that's only suitable to be shown to the user as extra context 347 // to help understand the errors. 348 return plan, diags 349 } 350 351 // If the graph builder and graph nodes correctly obeyed our directive 352 // to refresh only, the set of resource changes should always be empty. 353 // We'll safety-check that here so we can return a clear message about it, 354 // rather than probably just generating confusing output at the UI layer. 355 if len(plan.Changes.Resources) != 0 { 356 // Some extra context in the logs in case the user reports this message 357 // as a bug, as a starting point for debugging. 358 for _, rc := range plan.Changes.Resources { 359 if depKey := rc.DeposedKey; depKey == states.NotDeposed { 360 log.Printf("[DEBUG] Refresh-only plan includes %s change for %s", rc.Action, rc.Addr) 361 } else { 362 log.Printf("[DEBUG] Refresh-only plan includes %s change for %s deposed object %s", rc.Action, rc.Addr, depKey) 363 } 364 } 365 diags = diags.Append(tfdiags.Sourceless( 366 tfdiags.Error, 367 "Invalid refresh-only plan", 368 "OpenTofu generated planned resource changes in a refresh-only plan. This is a bug in OpenTofu.", 369 )) 370 } 371 372 // We don't populate RelevantResources for a refresh-only plan, because 373 // they never have any planned actions and so no resource can ever be 374 // "relevant" per the intended meaning of that field. 375 376 return plan, diags 377 } 378 379 func (c *Context) destroyPlan(config *configs.Config, prevRunState *states.State, opts *PlanOpts) (*plans.Plan, tfdiags.Diagnostics) { 380 var diags tfdiags.Diagnostics 381 382 if opts.Mode != plans.DestroyMode { 383 panic(fmt.Sprintf("called Context.destroyPlan with %s", opts.Mode)) 384 } 385 386 priorState := prevRunState 387 388 // A destroy plan starts by running Refresh to read any pending data 389 // sources, and remove missing managed resources. This is required because 390 // a "destroy plan" is only creating delete changes, and is essentially a 391 // local operation. 392 // 393 // NOTE: if skipRefresh _is_ set then we'll rely on the destroy-plan walk 394 // below to upgrade the prevRunState and priorState both to the latest 395 // resource type schemas, so NodePlanDestroyableResourceInstance.Execute 396 // must coordinate with this by taking that action only when c.skipRefresh 397 // _is_ set. This coupling between the two is unfortunate but necessary 398 // to work within our current structure. 399 if !opts.SkipRefresh && !prevRunState.Empty() { 400 log.Printf("[TRACE] Context.destroyPlan: calling Context.plan to get the effect of refreshing the prior state") 401 refreshOpts := *opts 402 refreshOpts.Mode = plans.NormalMode 403 refreshOpts.PreDestroyRefresh = true 404 405 // FIXME: A normal plan is required here to refresh the state, because 406 // the state and configuration may not match during a destroy, and a 407 // normal refresh plan can fail with evaluation errors. In the future 408 // the destroy plan should take care of refreshing instances itself, 409 // where the special cases of evaluation and skipping condition checks 410 // can be done. 411 refreshPlan, refreshDiags := c.plan(config, prevRunState, &refreshOpts) 412 if refreshDiags.HasErrors() { 413 // NOTE: Normally we'd append diagnostics regardless of whether 414 // there are errors, just in case there are warnings we'd want to 415 // preserve, but we're intentionally _not_ doing that here because 416 // if the first plan succeeded then we'll be running another plan 417 // in DestroyMode below, and we don't want to double-up any 418 // warnings that both plan walks would generate. 419 // (This does mean we won't show any warnings that would've been 420 // unique to only this walk, but we're assuming here that if the 421 // warnings aren't also applicable to a destroy plan then we'd 422 // rather not show them here, because this non-destroy plan for 423 // refreshing is largely an implementation detail.) 424 diags = diags.Append(refreshDiags) 425 return nil, diags 426 } 427 428 // We'll use the refreshed state -- which is the "prior state" from 429 // the perspective of this "destroy plan" -- as the starting state 430 // for our destroy-plan walk, so it can take into account if we 431 // detected during refreshing that anything was already deleted outside OpenTofu. 432 priorState = refreshPlan.PriorState.DeepCopy() 433 434 // The refresh plan may have upgraded state for some resources, make 435 // sure we store the new version. 436 prevRunState = refreshPlan.PrevRunState.DeepCopy() 437 log.Printf("[TRACE] Context.destroyPlan: now _really_ creating a destroy plan") 438 } 439 440 destroyPlan, walkDiags := c.planWalk(config, priorState, opts) 441 diags = diags.Append(walkDiags) 442 if walkDiags.HasErrors() { 443 // Non-nil plan along with errors indicates a non-applyable partial 444 // plan that's only suitable to be shown to the user as extra context 445 // to help understand the errors. 446 return destroyPlan, diags 447 } 448 449 if !opts.SkipRefresh { 450 // If we didn't skip refreshing then we want the previous run state to 451 // be the one we originally fed into the c.refreshOnlyPlan call above, 452 // not the refreshed version we used for the destroy planWalk. 453 destroyPlan.PrevRunState = prevRunState 454 } 455 456 relevantAttrs, rDiags := c.relevantResourceAttrsForPlan(config, destroyPlan) 457 diags = diags.Append(rDiags) 458 459 destroyPlan.RelevantAttributes = relevantAttrs 460 return destroyPlan, diags 461 } 462 463 func (c *Context) prePlanFindAndApplyMoves(config *configs.Config, prevRunState *states.State, targets []addrs.Targetable) ([]refactoring.MoveStatement, refactoring.MoveResults) { 464 explicitMoveStmts := refactoring.FindMoveStatements(config) 465 implicitMoveStmts := refactoring.ImpliedMoveStatements(config, prevRunState, explicitMoveStmts) 466 var moveStmts []refactoring.MoveStatement 467 if stmtsLen := len(explicitMoveStmts) + len(implicitMoveStmts); stmtsLen > 0 { 468 moveStmts = make([]refactoring.MoveStatement, 0, stmtsLen) 469 moveStmts = append(moveStmts, explicitMoveStmts...) 470 moveStmts = append(moveStmts, implicitMoveStmts...) 471 } 472 moveResults := refactoring.ApplyMoves(moveStmts, prevRunState) 473 return moveStmts, moveResults 474 } 475 476 func (c *Context) prePlanVerifyTargetedMoves(moveResults refactoring.MoveResults, targets []addrs.Targetable) tfdiags.Diagnostics { 477 if len(targets) < 1 { 478 return nil // the following only matters when targeting 479 } 480 481 var diags tfdiags.Diagnostics 482 483 var excluded []addrs.AbsResourceInstance 484 for _, result := range moveResults.Changes.Values() { 485 fromMatchesTarget := false 486 toMatchesTarget := false 487 for _, targetAddr := range targets { 488 if targetAddr.TargetContains(result.From) { 489 fromMatchesTarget = true 490 } 491 if targetAddr.TargetContains(result.To) { 492 toMatchesTarget = true 493 } 494 } 495 if !fromMatchesTarget { 496 excluded = append(excluded, result.From) 497 } 498 if !toMatchesTarget { 499 excluded = append(excluded, result.To) 500 } 501 } 502 if len(excluded) > 0 { 503 sort.Slice(excluded, func(i, j int) bool { 504 return excluded[i].Less(excluded[j]) 505 }) 506 507 var listBuf strings.Builder 508 var prevResourceAddr addrs.AbsResource 509 for _, instAddr := range excluded { 510 // Targeting generally ends up selecting whole resources rather 511 // than individual instances, because we don't factor in 512 // individual instances until DynamicExpand, so we're going to 513 // always show whole resource addresses here, excluding any 514 // instance keys. (This also neatly avoids dealing with the 515 // different quoting styles required for string instance keys 516 // on different shells, which is handy.) 517 // 518 // To avoid showing duplicates when we have multiple instances 519 // of the same resource, we'll remember the most recent 520 // resource we rendered in prevResource, which is sufficient 521 // because we sorted the list of instance addresses above, and 522 // our sort order always groups together instances of the same 523 // resource. 524 resourceAddr := instAddr.ContainingResource() 525 if resourceAddr.Equal(prevResourceAddr) { 526 continue 527 } 528 fmt.Fprintf(&listBuf, "\n -target=%q", resourceAddr.String()) 529 prevResourceAddr = resourceAddr 530 } 531 diags = diags.Append(tfdiags.Sourceless( 532 tfdiags.Error, 533 "Moved resource instances excluded by targeting", 534 fmt.Sprintf( 535 "Resource instances in your current state have moved to new addresses in the latest configuration. OpenTofu must include those resource instances while planning in order to ensure a correct result, but your -target=... options do 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.", 536 listBuf.String(), 537 ), 538 )) 539 } 540 541 return diags 542 } 543 544 func (c *Context) postPlanValidateMoves(config *configs.Config, stmts []refactoring.MoveStatement, allInsts instances.Set) tfdiags.Diagnostics { 545 return refactoring.ValidateMoves(stmts, config, allInsts) 546 } 547 548 // All import target addresses with a key must already exist in config. 549 // When we are able to generate config for expanded resources, this rule can be 550 // relaxed. 551 func (c *Context) postPlanValidateImports(importResolver *ImportResolver, allInst instances.Set) tfdiags.Diagnostics { 552 var diags tfdiags.Diagnostics 553 for _, importTarget := range importResolver.GetAllImports() { 554 if !allInst.HasResourceInstance(importTarget.Addr) { 555 diags = diags.Append(importResourceWithoutConfigDiags(importTarget.Addr.String(), nil)) 556 } 557 } 558 return diags 559 } 560 561 // findImportTargets builds a list of import targets by going over the import 562 // blocks in the config. 563 func (c *Context) findImportTargets(config *configs.Config) []*ImportTarget { 564 var importTargets []*ImportTarget 565 for _, ic := range config.Module.Import { 566 importTargets = append(importTargets, &ImportTarget{ 567 Config: ic, 568 }) 569 } 570 return importTargets 571 } 572 573 // validateImportTargets makes sure all import targets are not breaking the following rules: 574 // 1. Imports are attempted into resources that do not exist (if config generation is not enabled). 575 // 2. Config generation is not attempted for resources inside sub-modules 576 // 3. Config generation is not attempted for resources with indexes (for_each/count) - This will always include 577 // resources for which we could not yet resolve the address 578 func (c *Context) validateImportTargets(config *configs.Config, importTargets []*ImportTarget, generateConfigPath string) (diags tfdiags.Diagnostics) { 579 configGeneration := len(generateConfigPath) > 0 580 for _, imp := range importTargets { 581 staticAddress := imp.StaticAddr() 582 descendantConfig := config.Descendent(staticAddress.Module) 583 584 // If import target's module does not exist 585 if descendantConfig == nil { 586 if configGeneration { 587 // Attempted config generation for resource in non-existing module. So error because resource generation 588 // is not allowed in a sub-module 589 diags = diags.Append(importConfigGenerationInModuleDiags(staticAddress.String(), imp.Config)) 590 } else { 591 diags = diags.Append(importResourceWithoutConfigDiags(staticAddress.String(), imp.Config)) 592 } 593 continue 594 } 595 596 if _, exists := descendantConfig.Module.ManagedResources[staticAddress.Resource.String()]; !exists { 597 if configGeneration { 598 if imp.ResolvedAddr() == nil { 599 // If we could not resolve the address of the import target, the address must have contained indexes 600 diags = diags.Append(importConfigGenerationWithIndexDiags(staticAddress.String(), imp.Config)) 601 continue 602 } else if !imp.ResolvedAddr().Module.IsRoot() { 603 diags = diags.Append(importConfigGenerationInModuleDiags(imp.ResolvedAddr().String(), imp.Config)) 604 continue 605 } else if imp.ResolvedAddr().Resource.Key != addrs.NoKey { 606 diags = diags.Append(importConfigGenerationWithIndexDiags(imp.ResolvedAddr().String(), imp.Config)) 607 continue 608 } 609 } else { 610 diags = diags.Append(importResourceWithoutConfigDiags(staticAddress.String(), imp.Config)) 611 continue 612 } 613 } 614 } 615 return 616 } 617 618 func importConfigGenerationInModuleDiags(addressStr string, config *configs.Import) *hcl.Diagnostic { 619 diag := hcl.Diagnostic{ 620 Severity: hcl.DiagError, 621 Summary: "Cannot generate configuration for resource inside sub-module", 622 Detail: fmt.Sprintf("The configuration for the given import %s does not exist. Configuration generation is only possible for resources in the root module, and not possible for resources in sub-modules.", addressStr), 623 } 624 625 if config != nil { 626 diag.Subject = config.DeclRange.Ptr() 627 } 628 629 return &diag 630 } 631 632 func importConfigGenerationWithIndexDiags(addressStr string, config *configs.Import) *hcl.Diagnostic { 633 diag := hcl.Diagnostic{ 634 Severity: hcl.DiagError, 635 Summary: "Configuration generation for count and for_each resources not supported", 636 Detail: fmt.Sprintf("The configuration for the given import %s does not exist. Configuration generation is only possible for resources that do not use count or for_each", addressStr), 637 } 638 639 if config != nil { 640 diag.Subject = config.DeclRange.Ptr() 641 } 642 643 return &diag 644 } 645 646 func importResourceWithoutConfigDiags(addressStr string, config *configs.Import) *hcl.Diagnostic { 647 diag := hcl.Diagnostic{ 648 Severity: hcl.DiagError, 649 Summary: "Configuration for import target does not exist", 650 Detail: fmt.Sprintf("The configuration for the given import %s does not exist. All target instances must have an associated configuration to be imported.", addressStr), 651 } 652 653 if config != nil { 654 diag.Subject = config.DeclRange.Ptr() 655 } 656 657 return &diag 658 } 659 660 func (c *Context) planWalk(config *configs.Config, prevRunState *states.State, opts *PlanOpts) (*plans.Plan, tfdiags.Diagnostics) { 661 var diags tfdiags.Diagnostics 662 log.Printf("[DEBUG] Building and walking plan graph for %s", opts.Mode) 663 664 prevRunState = prevRunState.DeepCopy() // don't modify the caller's object when we process the moves 665 moveStmts, moveResults := c.prePlanFindAndApplyMoves(config, prevRunState, opts.Targets) 666 667 // If resource targeting is in effect then it might conflict with the 668 // move result. 669 diags = diags.Append(c.prePlanVerifyTargetedMoves(moveResults, opts.Targets)) 670 if diags.HasErrors() { 671 // We'll return early here, because if we have any moved resource 672 // instances excluded by targeting then planning is likely to encounter 673 // strange problems that may lead to confusing error messages. 674 return nil, diags 675 } 676 677 graph, walkOp, moreDiags := c.planGraph(config, prevRunState, opts) 678 diags = diags.Append(moreDiags) 679 if diags.HasErrors() { 680 return nil, diags 681 } 682 683 timestamp := time.Now().UTC() 684 685 // If we get here then we should definitely have a non-nil "graph", which 686 // we can now walk. 687 changes := plans.NewChanges() 688 walker, walkDiags := c.walk(graph, walkOp, &graphWalkOpts{ 689 Config: config, 690 InputState: prevRunState, 691 Changes: changes, 692 MoveResults: moveResults, 693 PlanTimeTimestamp: timestamp, 694 }) 695 diags = diags.Append(walker.NonFatalDiagnostics) 696 diags = diags.Append(walkDiags) 697 698 allInsts := walker.InstanceExpander.AllInstances() 699 700 importValidateDiags := c.postPlanValidateImports(walker.ImportResolver, allInsts) 701 if importValidateDiags.HasErrors() { 702 return nil, importValidateDiags 703 } 704 705 moveValidateDiags := c.postPlanValidateMoves(config, moveStmts, allInsts) 706 if moveValidateDiags.HasErrors() { 707 // If any of the move statements are invalid then those errors take 708 // precedence over any other errors because an incomplete move graph 709 // is quite likely to be the _cause_ of various errors. This oddity 710 // comes from the fact that we need to apply the moves before we 711 // actually validate them, because validation depends on the result 712 // of first trying to plan. 713 return nil, moveValidateDiags 714 } 715 diags = diags.Append(moveValidateDiags) // might just contain warnings 716 717 if moveResults.Blocked.Len() > 0 && !diags.HasErrors() { 718 // If we had blocked moves and we're not going to be returning errors 719 // then we'll report the blockers as a warning. We do this only in the 720 // absense of errors because invalid move statements might well be 721 // the root cause of the blockers, and so better to give an actionable 722 // error message than a less-actionable warning. 723 diags = diags.Append(blockedMovesWarningDiag(moveResults)) 724 } 725 726 // If we reach this point with error diagnostics then "changes" is a 727 // representation of the subset of changes we were able to plan before 728 // we encountered errors, which we'll return as part of a non-nil plan 729 // so that e.g. the UI can show what was planned so far in case that extra 730 // context helps the user to understand the error messages we're returning. 731 prevRunState = walker.PrevRunState.Close() 732 733 // The refreshed state may have data resource objects which were deferred 734 // to apply and cannot be serialized. 735 walker.RefreshState.RemovePlannedResourceInstanceObjects() 736 priorState := walker.RefreshState.Close() 737 738 driftedResources, driftDiags := c.driftedResources(config, prevRunState, priorState, moveResults) 739 diags = diags.Append(driftDiags) 740 741 plan := &plans.Plan{ 742 UIMode: opts.Mode, 743 Changes: changes, 744 DriftedResources: driftedResources, 745 PrevRunState: prevRunState, 746 PriorState: priorState, 747 PlannedState: walker.State.Close(), 748 ExternalReferences: opts.ExternalReferences, 749 Checks: states.NewCheckResults(walker.Checks), 750 Timestamp: timestamp, 751 752 // Other fields get populated by Context.Plan after we return 753 } 754 return plan, diags 755 } 756 757 func (c *Context) planGraph(config *configs.Config, prevRunState *states.State, opts *PlanOpts) (*Graph, walkOperation, tfdiags.Diagnostics) { 758 switch mode := opts.Mode; mode { 759 case plans.NormalMode: 760 graph, diags := (&PlanGraphBuilder{ 761 Config: config, 762 State: prevRunState, 763 RootVariableValues: opts.SetVariables, 764 Plugins: c.plugins, 765 Targets: opts.Targets, 766 ForceReplace: opts.ForceReplace, 767 skipRefresh: opts.SkipRefresh, 768 preDestroyRefresh: opts.PreDestroyRefresh, 769 Operation: walkPlan, 770 ExternalReferences: opts.ExternalReferences, 771 ImportTargets: opts.ImportTargets, 772 GenerateConfigPath: opts.GenerateConfigPath, 773 EndpointsToRemove: opts.EndpointsToRemove, 774 }).Build(addrs.RootModuleInstance) 775 return graph, walkPlan, diags 776 case plans.RefreshOnlyMode: 777 graph, diags := (&PlanGraphBuilder{ 778 Config: config, 779 State: prevRunState, 780 RootVariableValues: opts.SetVariables, 781 Plugins: c.plugins, 782 Targets: opts.Targets, 783 skipRefresh: opts.SkipRefresh, 784 skipPlanChanges: true, // this activates "refresh only" mode. 785 Operation: walkPlan, 786 ExternalReferences: opts.ExternalReferences, 787 }).Build(addrs.RootModuleInstance) 788 return graph, walkPlan, diags 789 case plans.DestroyMode: 790 graph, diags := (&PlanGraphBuilder{ 791 Config: config, 792 State: prevRunState, 793 RootVariableValues: opts.SetVariables, 794 Plugins: c.plugins, 795 Targets: opts.Targets, 796 skipRefresh: opts.SkipRefresh, 797 Operation: walkPlanDestroy, 798 }).Build(addrs.RootModuleInstance) 799 return graph, walkPlanDestroy, diags 800 default: 801 // The above should cover all plans.Mode values 802 panic(fmt.Sprintf("unsupported plan mode %s", mode)) 803 } 804 } 805 806 // driftedResources is a best-effort attempt to compare the current and prior 807 // state. If we cannot decode the prior state for some reason, this should only 808 // return warnings to help the user correlate any missing resources in the 809 // report. This is known to happen when targeting a subset of resources, 810 // because the excluded instances will have been removed from the plan and 811 // not upgraded. 812 func (c *Context) driftedResources(config *configs.Config, oldState, newState *states.State, moves refactoring.MoveResults) ([]*plans.ResourceInstanceChangeSrc, tfdiags.Diagnostics) { 813 var diags tfdiags.Diagnostics 814 815 if newState.ManagedResourcesEqual(oldState) && moves.Changes.Len() == 0 { 816 // Nothing to do, because we only detect and report drift for managed 817 // resource instances. 818 return nil, diags 819 } 820 821 schemas, schemaDiags := c.Schemas(config, newState) 822 diags = diags.Append(schemaDiags) 823 if diags.HasErrors() { 824 return nil, diags 825 } 826 827 var drs []*plans.ResourceInstanceChangeSrc 828 829 for _, ms := range oldState.Modules { 830 for _, rs := range ms.Resources { 831 if rs.Addr.Resource.Mode != addrs.ManagedResourceMode { 832 // Drift reporting is only for managed resources 833 continue 834 } 835 836 provider := rs.ProviderConfig.Provider 837 for key, oldIS := range rs.Instances { 838 if oldIS.Current == nil { 839 // Not interested in instances that only have deposed objects 840 continue 841 } 842 addr := rs.Addr.Instance(key) 843 844 // Previous run address defaults to the current address, but 845 // can differ if the resource moved before refreshing 846 prevRunAddr := addr 847 if move, ok := moves.Changes.GetOk(addr); ok { 848 prevRunAddr = move.From 849 } 850 851 newIS := newState.ResourceInstance(addr) 852 853 schema, _ := schemas.ResourceTypeConfig( 854 provider, 855 addr.Resource.Resource.Mode, 856 addr.Resource.Resource.Type, 857 ) 858 if schema == nil { 859 diags = diags.Append(tfdiags.Sourceless( 860 tfdiags.Warning, 861 "Missing resource schema from provider", 862 fmt.Sprintf("No resource schema found for %s when decoding prior state", addr.Resource.Resource.Type), 863 )) 864 continue 865 } 866 ty := schema.ImpliedType() 867 868 oldObj, err := oldIS.Current.Decode(ty) 869 if err != nil { 870 diags = diags.Append(tfdiags.Sourceless( 871 tfdiags.Warning, 872 "Failed to decode resource from state", 873 fmt.Sprintf("Error decoding %q from prior state: %s", addr.String(), err), 874 )) 875 continue 876 } 877 878 var newObj *states.ResourceInstanceObject 879 if newIS != nil && newIS.Current != nil { 880 newObj, err = newIS.Current.Decode(ty) 881 if err != nil { 882 diags = diags.Append(tfdiags.Sourceless( 883 tfdiags.Warning, 884 "Failed to decode resource from state", 885 fmt.Sprintf("Error decoding %q from prior state: %s", addr.String(), err), 886 )) 887 continue 888 } 889 } 890 891 var oldVal, newVal cty.Value 892 oldVal = oldObj.Value 893 if newObj != nil { 894 newVal = newObj.Value 895 } else { 896 newVal = cty.NullVal(ty) 897 } 898 899 if oldVal.RawEquals(newVal) && addr.Equal(prevRunAddr) { 900 // No drift if the two values are semantically equivalent 901 // and no move has happened 902 continue 903 } 904 905 // We can detect three types of changes after refreshing state, 906 // only two of which are easily understood as "drift": 907 // 908 // - Resources which were deleted outside OpenTofu; 909 // - Resources where the object value has changed outside OpenTofu; 910 // - Resources which have been moved without other changes. 911 // 912 // All of these are returned as drift, to allow refresh-only plans 913 // to present a full set of changes which will be applied. 914 var action plans.Action 915 switch { 916 case newVal.IsNull(): 917 action = plans.Delete 918 case !oldVal.RawEquals(newVal): 919 action = plans.Update 920 default: 921 action = plans.NoOp 922 } 923 924 change := &plans.ResourceInstanceChange{ 925 Addr: addr, 926 PrevRunAddr: prevRunAddr, 927 ProviderAddr: rs.ProviderConfig, 928 Change: plans.Change{ 929 Action: action, 930 Before: oldVal, 931 After: newVal, 932 }, 933 } 934 935 changeSrc, err := change.Encode(ty) 936 if err != nil { 937 diags = diags.Append(err) 938 return nil, diags 939 } 940 941 drs = append(drs, changeSrc) 942 } 943 } 944 } 945 946 return drs, diags 947 } 948 949 // PlanGraphForUI is a last vestage of graphs in the public interface of Context 950 // (as opposed to graphs as an implementation detail) intended only for use 951 // by the "tofu graph" command when asked to render a plan-time graph. 952 // 953 // The result of this is intended only for rendering to the user as a dot 954 // graph, and so may change in future in order to make the result more useful 955 // in that context, even if drifts away from the physical graph that OpenTofu 956 // Core currently uses as an implementation detail of planning. 957 func (c *Context) PlanGraphForUI(config *configs.Config, prevRunState *states.State, mode plans.Mode) (*Graph, tfdiags.Diagnostics) { 958 // For now though, this really is just the internal graph, confusing 959 // implementation details and all. 960 961 var diags tfdiags.Diagnostics 962 963 opts := &PlanOpts{Mode: mode} 964 965 graph, _, moreDiags := c.planGraph(config, prevRunState, opts) 966 diags = diags.Append(moreDiags) 967 return graph, diags 968 } 969 970 func blockedMovesWarningDiag(results refactoring.MoveResults) tfdiags.Diagnostic { 971 if results.Blocked.Len() < 1 { 972 // Caller should check first 973 panic("request to render blocked moves warning without any blocked moves") 974 } 975 976 var itemsBuf bytes.Buffer 977 for _, blocked := range results.Blocked.Values() { 978 fmt.Fprintf(&itemsBuf, "\n - %s could not move to %s", blocked.Actual, blocked.Wanted) 979 } 980 981 return tfdiags.Sourceless( 982 tfdiags.Warning, 983 "Unresolved resource instance address changes", 984 fmt.Sprintf( 985 "OpenTofu 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\nOpenTofu has planned to destroy these objects. If OpenTofu's proposed changes aren't appropriate, you must first resolve the conflicts using the \"tofu state\" subcommands and then create a new plan.", 986 itemsBuf.String(), 987 ), 988 ) 989 } 990 991 // referenceAnalyzer returns a globalref.Analyzer object to help with 992 // global analysis of references within the configuration that's attached 993 // to the receiving context. 994 func (c *Context) referenceAnalyzer(config *configs.Config, state *states.State) (*globalref.Analyzer, tfdiags.Diagnostics) { 995 schemas, diags := c.Schemas(config, state) 996 if diags.HasErrors() { 997 return nil, diags 998 } 999 return globalref.NewAnalyzer(config, schemas.Providers), diags 1000 } 1001 1002 // relevantResourcesForPlan implements the heuristic we use to populate the 1003 // RelevantResources field of returned plans. 1004 func (c *Context) relevantResourceAttrsForPlan(config *configs.Config, plan *plans.Plan) ([]globalref.ResourceAttr, tfdiags.Diagnostics) { 1005 azr, diags := c.referenceAnalyzer(config, plan.PriorState) 1006 if diags.HasErrors() { 1007 return nil, diags 1008 } 1009 1010 var refs []globalref.Reference 1011 for _, change := range plan.Changes.Resources { 1012 if change.Action == plans.NoOp { 1013 continue 1014 } 1015 1016 moreRefs := azr.ReferencesFromResourceInstance(change.Addr) 1017 refs = append(refs, moreRefs...) 1018 } 1019 1020 for _, change := range plan.Changes.Outputs { 1021 if change.Action == plans.NoOp { 1022 continue 1023 } 1024 1025 moreRefs := azr.ReferencesFromOutputValue(change.Addr) 1026 refs = append(refs, moreRefs...) 1027 } 1028 1029 var contributors []globalref.ResourceAttr 1030 1031 for _, ref := range azr.ContributingResourceReferences(refs...) { 1032 if res, ok := ref.ResourceAttr(); ok { 1033 contributors = append(contributors, res) 1034 } 1035 } 1036 1037 return contributors, diags 1038 }