github.com/opentofu/opentofu@v1.7.1/internal/plans/changes.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 plans 7 8 import ( 9 "github.com/zclconf/go-cty/cty" 10 11 "github.com/opentofu/opentofu/internal/addrs" 12 "github.com/opentofu/opentofu/internal/states" 13 ) 14 15 // Changes describes various actions that OpenTofu will attempt to take if 16 // the corresponding plan is applied. 17 // 18 // A Changes object can be rendered into a visual diff (by the caller, using 19 // code in another package) for display to the user. 20 type Changes struct { 21 // Resources tracks planned changes to resource instance objects. 22 Resources []*ResourceInstanceChangeSrc 23 24 // Outputs tracks planned changes output values. 25 // 26 // Note that although an in-memory plan contains planned changes for 27 // outputs throughout the configuration, a plan serialized 28 // to disk retains only the root outputs because they are 29 // externally-visible, while other outputs are implementation details and 30 // can be easily re-calculated during the apply phase. Therefore only root 31 // module outputs will survive a round-trip through a plan file. 32 Outputs []*OutputChangeSrc 33 } 34 35 // NewChanges returns a valid Changes object that describes no changes. 36 func NewChanges() *Changes { 37 return &Changes{} 38 } 39 40 func (c *Changes) Empty() bool { 41 for _, res := range c.Resources { 42 if res.Action != NoOp || res.Moved() { 43 return false 44 } 45 46 if res.Importing != nil { 47 return false 48 } 49 } 50 51 for _, out := range c.Outputs { 52 if out.Addr.Module.IsRoot() && out.Action != NoOp { 53 return false 54 } 55 } 56 57 return true 58 } 59 60 // ResourceInstance returns the planned change for the current object of the 61 // resource instance of the given address, if any. Returns nil if no change is 62 // planned. 63 func (c *Changes) ResourceInstance(addr addrs.AbsResourceInstance) *ResourceInstanceChangeSrc { 64 for _, rc := range c.Resources { 65 if rc.Addr.Equal(addr) && rc.DeposedKey == states.NotDeposed { 66 return rc 67 } 68 } 69 70 return nil 71 72 } 73 74 // InstancesForAbsResource returns the planned change for the current objects 75 // of the resource instances of the given address, if any. Returns nil if no 76 // changes are planned. 77 func (c *Changes) InstancesForAbsResource(addr addrs.AbsResource) []*ResourceInstanceChangeSrc { 78 var changes []*ResourceInstanceChangeSrc 79 for _, rc := range c.Resources { 80 resAddr := rc.Addr.ContainingResource() 81 if resAddr.Equal(addr) && rc.DeposedKey == states.NotDeposed { 82 changes = append(changes, rc) 83 } 84 } 85 86 return changes 87 } 88 89 // InstancesForConfigResource returns the planned change for the current objects 90 // of the resource instances of the given address, if any. Returns nil if no 91 // changes are planned. 92 func (c *Changes) InstancesForConfigResource(addr addrs.ConfigResource) []*ResourceInstanceChangeSrc { 93 var changes []*ResourceInstanceChangeSrc 94 for _, rc := range c.Resources { 95 resAddr := rc.Addr.ContainingResource().Config() 96 if resAddr.Equal(addr) && rc.DeposedKey == states.NotDeposed { 97 changes = append(changes, rc) 98 } 99 } 100 101 return changes 102 } 103 104 // ResourceInstanceDeposed returns the plan change of a deposed object of 105 // the resource instance of the given address, if any. Returns nil if no change 106 // is planned. 107 func (c *Changes) ResourceInstanceDeposed(addr addrs.AbsResourceInstance, key states.DeposedKey) *ResourceInstanceChangeSrc { 108 for _, rc := range c.Resources { 109 if rc.Addr.Equal(addr) && rc.DeposedKey == key { 110 return rc 111 } 112 } 113 114 return nil 115 } 116 117 // OutputValue returns the planned change for the output value with the 118 // 119 // given address, if any. Returns nil if no change is planned. 120 func (c *Changes) OutputValue(addr addrs.AbsOutputValue) *OutputChangeSrc { 121 for _, oc := range c.Outputs { 122 if oc.Addr.Equal(addr) { 123 return oc 124 } 125 } 126 127 return nil 128 } 129 130 // RootOutputValues returns planned changes for all outputs of the root module. 131 func (c *Changes) RootOutputValues() []*OutputChangeSrc { 132 var res []*OutputChangeSrc 133 134 for _, oc := range c.Outputs { 135 // we can't evaluate root module outputs 136 if !oc.Addr.Module.Equal(addrs.RootModuleInstance) { 137 continue 138 } 139 140 res = append(res, oc) 141 142 } 143 144 return res 145 } 146 147 // OutputValues returns planned changes for all outputs for all module 148 // instances that reside in the parent path. Returns nil if no changes are 149 // planned. 150 func (c *Changes) OutputValues(parent addrs.ModuleInstance, module addrs.ModuleCall) []*OutputChangeSrc { 151 var res []*OutputChangeSrc 152 153 for _, oc := range c.Outputs { 154 // we can't evaluate root module outputs 155 if oc.Addr.Module.Equal(addrs.RootModuleInstance) { 156 continue 157 } 158 159 changeMod, changeCall := oc.Addr.Module.Call() 160 // this does not reside on our parent instance path 161 if !changeMod.Equal(parent) { 162 continue 163 } 164 165 // this is not the module you're looking for 166 if changeCall.Name != module.Name { 167 continue 168 } 169 170 res = append(res, oc) 171 172 } 173 174 return res 175 } 176 177 // SyncWrapper returns a wrapper object around the receiver that can be used 178 // to make certain changes to the receiver in a concurrency-safe way, as long 179 // as all callers share the same wrapper object. 180 func (c *Changes) SyncWrapper() *ChangesSync { 181 return &ChangesSync{ 182 changes: c, 183 } 184 } 185 186 // ResourceInstanceChange describes a change to a particular resource instance 187 // object. 188 type ResourceInstanceChange struct { 189 // Addr is the absolute address of the resource instance that the change 190 // will apply to. 191 Addr addrs.AbsResourceInstance 192 193 // PrevRunAddr is the absolute address that this resource instance had at 194 // the conclusion of a previous run. 195 // 196 // This will typically be the same as Addr, but can be different if the 197 // previous resource instance was subject to a "moved" block that we 198 // handled in the process of creating this plan. 199 // 200 // For the initial creation of a resource instance there isn't really any 201 // meaningful "previous run address", but PrevRunAddr will still be set 202 // equal to Addr in that case in order to simplify logic elsewhere which 203 // aims to detect and react to the movement of instances between addresses. 204 PrevRunAddr addrs.AbsResourceInstance 205 206 // DeposedKey is the identifier for a deposed object associated with the 207 // given instance, or states.NotDeposed if this change applies to the 208 // current object. 209 // 210 // A Replace change for a resource with create_before_destroy set will 211 // create a new DeposedKey temporarily during replacement. In that case, 212 // DeposedKey in the plan is always states.NotDeposed, representing that 213 // the current object is being replaced with the deposed. 214 DeposedKey states.DeposedKey 215 216 // Provider is the address of the provider configuration that was used 217 // to plan this change, and thus the configuration that must also be 218 // used to apply it. 219 ProviderAddr addrs.AbsProviderConfig 220 221 // Change is an embedded description of the change. 222 Change 223 224 // ActionReason is an optional extra indication of why we chose the 225 // action recorded in Change.Action for this particular resource instance. 226 // 227 // This is an approximate mechanism only for the purpose of explaining the 228 // plan to end-users in the UI and is not to be used for any 229 // decision-making during the apply step; if apply behavior needs to vary 230 // depending on the "action reason" then the information for that decision 231 // must be recorded more precisely elsewhere for that purpose. 232 // 233 // Sometimes there might be more than one reason for choosing a particular 234 // action. In that case, it's up to the codepath making that decision to 235 // decide which value would provide the most relevant explanation to the 236 // end-user and return that. It's not a goal of this field to represent 237 // fine details about the planning process. 238 ActionReason ResourceInstanceChangeActionReason 239 240 // RequiredReplace is a set of paths that caused the change action to be 241 // Replace rather than Update. Always nil if the change action is not 242 // Replace. 243 // 244 // This is retained only for UI-plan-rendering purposes and so it does not 245 // currently survive a round-trip through a saved plan file. 246 RequiredReplace cty.PathSet 247 248 // Private allows a provider to stash any extra data that is opaque to 249 // OpenTofu that relates to this change. OpenTofu will save this 250 // byte-for-byte and return it to the provider in the apply call. 251 Private []byte 252 } 253 254 // Encode produces a variant of the reciever that has its change values 255 // serialized so it can be written to a plan file. Pass the implied type of the 256 // corresponding resource type schema for correct operation. 257 func (rc *ResourceInstanceChange) Encode(ty cty.Type) (*ResourceInstanceChangeSrc, error) { 258 cs, err := rc.Change.Encode(ty) 259 if err != nil { 260 return nil, err 261 } 262 prevRunAddr := rc.PrevRunAddr 263 if prevRunAddr.Resource.Resource.Type == "" { 264 // Suggests an old caller that hasn't been properly updated to 265 // populate this yet. 266 prevRunAddr = rc.Addr 267 } 268 return &ResourceInstanceChangeSrc{ 269 Addr: rc.Addr, 270 PrevRunAddr: prevRunAddr, 271 DeposedKey: rc.DeposedKey, 272 ProviderAddr: rc.ProviderAddr, 273 ChangeSrc: *cs, 274 ActionReason: rc.ActionReason, 275 RequiredReplace: rc.RequiredReplace, 276 Private: rc.Private, 277 }, err 278 } 279 280 func (rc *ResourceInstanceChange) Moved() bool { 281 return !rc.Addr.Equal(rc.PrevRunAddr) 282 } 283 284 // Simplify will, where possible, produce a change with a simpler action than 285 // the receiever given a flag indicating whether the caller is dealing with 286 // a normal apply or a destroy. This flag deals with the fact that OpenTofu 287 // Core uses a specialized graph node type for destroying; only that 288 // specialized node should set "destroying" to true. 289 // 290 // The following table shows the simplification behavior: 291 // 292 // Action Destroying? New Action 293 // --------+-------------+----------- 294 // Create true NoOp 295 // Delete false NoOp 296 // Replace true Delete 297 // Replace false Create 298 // 299 // For any combination not in the above table, the Simplify just returns the 300 // receiver as-is. 301 func (rc *ResourceInstanceChange) Simplify(destroying bool) *ResourceInstanceChange { 302 if destroying { 303 switch rc.Action { 304 case Delete: 305 // We'll fall out and just return rc verbatim, then. 306 case CreateThenDelete, DeleteThenCreate: 307 return &ResourceInstanceChange{ 308 Addr: rc.Addr, 309 DeposedKey: rc.DeposedKey, 310 Private: rc.Private, 311 ProviderAddr: rc.ProviderAddr, 312 Change: Change{ 313 Action: Delete, 314 Before: rc.Before, 315 After: cty.NullVal(rc.Before.Type()), 316 Importing: rc.Importing, 317 GeneratedConfig: rc.GeneratedConfig, 318 }, 319 } 320 default: 321 return &ResourceInstanceChange{ 322 Addr: rc.Addr, 323 DeposedKey: rc.DeposedKey, 324 Private: rc.Private, 325 ProviderAddr: rc.ProviderAddr, 326 Change: Change{ 327 Action: NoOp, 328 Before: rc.Before, 329 After: rc.Before, 330 Importing: rc.Importing, 331 GeneratedConfig: rc.GeneratedConfig, 332 }, 333 } 334 } 335 } else { 336 switch rc.Action { 337 case Delete: 338 return &ResourceInstanceChange{ 339 Addr: rc.Addr, 340 DeposedKey: rc.DeposedKey, 341 Private: rc.Private, 342 ProviderAddr: rc.ProviderAddr, 343 Change: Change{ 344 Action: NoOp, 345 Before: rc.Before, 346 After: rc.Before, 347 Importing: rc.Importing, 348 GeneratedConfig: rc.GeneratedConfig, 349 }, 350 } 351 case CreateThenDelete, DeleteThenCreate: 352 return &ResourceInstanceChange{ 353 Addr: rc.Addr, 354 DeposedKey: rc.DeposedKey, 355 Private: rc.Private, 356 ProviderAddr: rc.ProviderAddr, 357 Change: Change{ 358 Action: Create, 359 Before: cty.NullVal(rc.After.Type()), 360 After: rc.After, 361 Importing: rc.Importing, 362 GeneratedConfig: rc.GeneratedConfig, 363 }, 364 } 365 } 366 } 367 368 // If we fall out here then our change is already simple enough. 369 return rc 370 } 371 372 // ResourceInstanceChangeActionReason allows for some extra user-facing 373 // reasoning for why a particular change action was chosen for a particular 374 // resource instance. 375 // 376 // This only represents sufficient detail to give a suitable explanation to 377 // an end-user, and mustn't be used for any real decision-making during the 378 // apply step. 379 type ResourceInstanceChangeActionReason rune 380 381 //go:generate go run golang.org/x/tools/cmd/stringer -type=ResourceInstanceChangeActionReason changes.go 382 383 const ( 384 // In most cases there's no special reason for choosing a particular 385 // action, which is represented by ResourceInstanceChangeNoReason. 386 ResourceInstanceChangeNoReason ResourceInstanceChangeActionReason = 0 387 388 // ResourceInstanceReplaceBecauseTainted indicates that the resource 389 // instance must be replaced because its existing current object is 390 // marked as "tainted". 391 ResourceInstanceReplaceBecauseTainted ResourceInstanceChangeActionReason = 'T' 392 393 // ResourceInstanceReplaceByRequest indicates that the resource instance 394 // is planned to be replaced because a caller specifically asked for it 395 // to be using ReplaceAddrs. (On the command line, the -replace=... 396 // planning option.) 397 ResourceInstanceReplaceByRequest ResourceInstanceChangeActionReason = 'R' 398 399 // ResourceInstanceReplaceByTriggers indicates that the resource instance 400 // is planned to be replaced because of a corresponding change in a 401 // replace_triggered_by reference. 402 ResourceInstanceReplaceByTriggers ResourceInstanceChangeActionReason = 'D' 403 404 // ResourceInstanceReplaceBecauseCannotUpdate indicates that the resource 405 // instance is planned to be replaced because the provider has indicated 406 // that a requested change cannot be applied as an update. 407 // 408 // In this case, the RequiredReplace field will typically be populated on 409 // the ResourceInstanceChange object to give information about specifically 410 // which arguments changed in a non-updatable way. 411 ResourceInstanceReplaceBecauseCannotUpdate ResourceInstanceChangeActionReason = 'F' 412 413 // ResourceInstanceDeleteBecauseNoResourceConfig indicates that the 414 // resource instance is planned to be deleted because there's no 415 // corresponding resource configuration block in the configuration. 416 ResourceInstanceDeleteBecauseNoResourceConfig ResourceInstanceChangeActionReason = 'N' 417 418 // ResourceInstanceDeleteBecauseWrongRepetition indicates that the 419 // resource instance is planned to be deleted because the instance key 420 // type isn't consistent with the repetition mode selected in the 421 // resource configuration. 422 ResourceInstanceDeleteBecauseWrongRepetition ResourceInstanceChangeActionReason = 'W' 423 424 // ResourceInstanceDeleteBecauseCountIndex indicates that the resource 425 // instance is planned to be deleted because its integer instance key 426 // is out of range for the current configured resource "count" value. 427 ResourceInstanceDeleteBecauseCountIndex ResourceInstanceChangeActionReason = 'C' 428 429 // ResourceInstanceDeleteBecauseEachKey indicates that the resource 430 // instance is planned to be deleted because its string instance key 431 // isn't one of the keys included in the current configured resource 432 // "for_each" value. 433 ResourceInstanceDeleteBecauseEachKey ResourceInstanceChangeActionReason = 'E' 434 435 // ResourceInstanceDeleteBecauseNoModule indicates that the resource 436 // instance is planned to be deleted because it belongs to a module 437 // instance that's no longer declared in the configuration. 438 // 439 // This is less specific than the reasons we return for the various ways 440 // a resource instance itself can be no longer declared, including both 441 // the total removal of a module block and changes to its count/for_each 442 // arguments. This difference in detail is out of pragmatism, because 443 // potentially multiple nested modules could all contribute conflicting 444 // specific reasons for a particular instance to no longer be declared. 445 ResourceInstanceDeleteBecauseNoModule ResourceInstanceChangeActionReason = 'M' 446 447 // ResourceInstanceDeleteBecauseNoMoveTarget indicates that the resource 448 // address appears as the target ("to") in a moved block, but no 449 // configuration exists for that resource. According to our move rules, 450 // this combination evaluates to a deletion of the "new" resource. 451 ResourceInstanceDeleteBecauseNoMoveTarget ResourceInstanceChangeActionReason = 'A' 452 453 // ResourceInstanceReadBecauseConfigUnknown indicates that the resource 454 // must be read during apply (rather than during planning) because its 455 // configuration contains unknown values. This reason applies only to 456 // data resources. 457 ResourceInstanceReadBecauseConfigUnknown ResourceInstanceChangeActionReason = '?' 458 459 // ResourceInstanceReadBecauseDependencyPending indicates that the resource 460 // must be read during apply (rather than during planning) because it 461 // depends on a managed resource instance which has its own changes 462 // pending. 463 ResourceInstanceReadBecauseDependencyPending ResourceInstanceChangeActionReason = '!' 464 465 // ResourceInstanceReadBecauseCheckNested indicates that the resource must 466 // be read during apply (as well as during planning) because it is inside 467 // a check block and when the check assertions execute we want them to use 468 // the most up-to-date data. 469 ResourceInstanceReadBecauseCheckNested ResourceInstanceChangeActionReason = '#' 470 ) 471 472 // OutputChange describes a change to an output value. 473 type OutputChange struct { 474 // Addr is the absolute address of the output value that the change 475 // will apply to. 476 Addr addrs.AbsOutputValue 477 478 // Change is an embedded description of the change. 479 // 480 // For output value changes, the type constraint for the DynamicValue 481 // instances is always cty.DynamicPseudoType. 482 Change 483 484 // Sensitive, if true, indicates that either the old or new value in the 485 // change is sensitive and so a rendered version of the plan in the UI 486 // should elide the actual values while still indicating the action of the 487 // change. 488 Sensitive bool 489 } 490 491 // Encode produces a variant of the reciever that has its change values 492 // serialized so it can be written to a plan file. 493 func (oc *OutputChange) Encode() (*OutputChangeSrc, error) { 494 cs, err := oc.Change.Encode(cty.DynamicPseudoType) 495 if err != nil { 496 return nil, err 497 } 498 return &OutputChangeSrc{ 499 Addr: oc.Addr, 500 ChangeSrc: *cs, 501 Sensitive: oc.Sensitive, 502 }, err 503 } 504 505 // Importing is the part of a ChangeSrc that describes the embedded import 506 // action. 507 // 508 // The fields in here are subject to change, so downstream consumers should be 509 // prepared for backwards compatibility in case the contents changes. 510 type Importing struct { 511 // ID is the original ID of the imported resource. 512 ID string 513 } 514 515 // Change describes a single change with a given action. 516 type Change struct { 517 // Action defines what kind of change is being made. 518 Action Action 519 520 // Interpretation of Before and After depend on Action: 521 // 522 // NoOp Before and After are the same, unchanged value 523 // Create Before is nil, and After is the expected value after create. 524 // Read Before is any prior value (nil if no prior), and After is the 525 // value that was or will be read. 526 // Update Before is the value prior to update, and After is the expected 527 // value after update. 528 // Replace As with Update. 529 // Delete Before is the value prior to delete, and After is always nil. 530 // 531 // Unknown values may appear anywhere within the Before and After values, 532 // either as the values themselves or as nested elements within known 533 // collections/structures. 534 Before, After cty.Value 535 536 // Importing is present if the resource is being imported as part of this 537 // change. 538 // 539 // Use the simple presence of this field to detect if a ChangeSrc is to be 540 // imported, the contents of this structure may be modified going forward. 541 Importing *Importing 542 543 // GeneratedConfig contains any HCL config generated for this resource 544 // during planning, as a string. If GeneratedConfig is populated, Importing 545 // should be true. However, not all Importing changes contain generated 546 // config. 547 GeneratedConfig string 548 } 549 550 // Encode produces a variant of the reciever that has its change values 551 // serialized so it can be written to a plan file. Pass the type constraint 552 // that the values are expected to conform to; to properly decode the values 553 // later an identical type constraint must be provided at that time. 554 // 555 // Where a Change is embedded in some other struct, it's generally better 556 // to call the corresponding Encode method of that struct rather than working 557 // directly with its embedded Change. 558 func (c *Change) Encode(ty cty.Type) (*ChangeSrc, error) { 559 // Storing unmarked values so that we can encode unmarked values 560 // and save the PathValueMarks for re-marking the values later 561 var beforeVM, afterVM []cty.PathValueMarks 562 unmarkedBefore := c.Before 563 unmarkedAfter := c.After 564 565 if c.Before.ContainsMarked() { 566 unmarkedBefore, beforeVM = c.Before.UnmarkDeepWithPaths() 567 } 568 beforeDV, err := NewDynamicValue(unmarkedBefore, ty) 569 if err != nil { 570 return nil, err 571 } 572 573 if c.After.ContainsMarked() { 574 unmarkedAfter, afterVM = c.After.UnmarkDeepWithPaths() 575 } 576 afterDV, err := NewDynamicValue(unmarkedAfter, ty) 577 if err != nil { 578 return nil, err 579 } 580 581 var importing *ImportingSrc 582 if c.Importing != nil { 583 importing = &ImportingSrc{ID: c.Importing.ID} 584 } 585 586 return &ChangeSrc{ 587 Action: c.Action, 588 Before: beforeDV, 589 After: afterDV, 590 BeforeValMarks: beforeVM, 591 AfterValMarks: afterVM, 592 Importing: importing, 593 GeneratedConfig: c.GeneratedConfig, 594 }, nil 595 }