github.com/opentofu/opentofu@v1.7.1/internal/tofu/node_resource_abstract_instance.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 "fmt" 10 "log" 11 "strings" 12 13 "github.com/hashicorp/hcl/v2" 14 "github.com/zclconf/go-cty/cty" 15 16 "github.com/opentofu/opentofu/internal/addrs" 17 "github.com/opentofu/opentofu/internal/checks" 18 "github.com/opentofu/opentofu/internal/configs" 19 "github.com/opentofu/opentofu/internal/configs/configschema" 20 "github.com/opentofu/opentofu/internal/encryption" 21 "github.com/opentofu/opentofu/internal/instances" 22 "github.com/opentofu/opentofu/internal/plans" 23 "github.com/opentofu/opentofu/internal/plans/objchange" 24 "github.com/opentofu/opentofu/internal/providers" 25 "github.com/opentofu/opentofu/internal/provisioners" 26 "github.com/opentofu/opentofu/internal/states" 27 "github.com/opentofu/opentofu/internal/tfdiags" 28 ) 29 30 // NodeAbstractResourceInstance represents a resource instance with no 31 // associated operations. It embeds NodeAbstractResource but additionally 32 // contains an instance key, used to identify one of potentially many 33 // instances that were created from a resource in configuration, e.g. using 34 // the "count" or "for_each" arguments. 35 type NodeAbstractResourceInstance struct { 36 NodeAbstractResource 37 Addr addrs.AbsResourceInstance 38 39 // These are set via the AttachState method. 40 instanceState *states.ResourceInstance 41 42 Dependencies []addrs.ConfigResource 43 44 preDestroyRefresh bool 45 46 // During import we may generate configuration for a resource, which needs 47 // to be stored in the final change. 48 generatedConfigHCL string 49 } 50 51 // NewNodeAbstractResourceInstance creates an abstract resource instance graph 52 // node for the given absolute resource instance address. 53 func NewNodeAbstractResourceInstance(addr addrs.AbsResourceInstance) *NodeAbstractResourceInstance { 54 // Due to the fact that we embed NodeAbstractResource, the given address 55 // actually ends up split between the resource address in the embedded 56 // object and the InstanceKey field in our own struct. The 57 // ResourceInstanceAddr method will stick these back together again on 58 // request. 59 r := NewNodeAbstractResource(addr.ContainingResource().Config()) 60 return &NodeAbstractResourceInstance{ 61 NodeAbstractResource: *r, 62 Addr: addr, 63 } 64 } 65 66 func (n *NodeAbstractResourceInstance) Name() string { 67 return n.ResourceInstanceAddr().String() 68 } 69 70 func (n *NodeAbstractResourceInstance) Path() addrs.ModuleInstance { 71 return n.Addr.Module 72 } 73 74 // GraphNodeReferenceable 75 func (n *NodeAbstractResourceInstance) ReferenceableAddrs() []addrs.Referenceable { 76 addr := n.ResourceInstanceAddr() 77 return []addrs.Referenceable{ 78 addr.Resource, 79 80 // A resource instance can also be referenced by the address of its 81 // containing resource, so that e.g. a reference to aws_instance.foo 82 // would match both aws_instance.foo[0] and aws_instance.foo[1]. 83 addr.ContainingResource().Resource, 84 } 85 } 86 87 // GraphNodeReferencer 88 func (n *NodeAbstractResourceInstance) References() []*addrs.Reference { 89 // If we have a configuration attached then we'll delegate to our 90 // embedded abstract resource, which knows how to extract dependencies 91 // from configuration. If there is no config, then the dependencies will 92 // be connected during destroy from those stored in the state. 93 if n.Config != nil { 94 if n.Schema == nil { 95 // We'll produce a log message about this out here so that 96 // we can include the full instance address, since the equivalent 97 // message in NodeAbstractResource.References cannot see it. 98 log.Printf("[WARN] no schema is attached to %s, so config references cannot be detected", n.Name()) 99 return nil 100 } 101 return n.NodeAbstractResource.References() 102 } 103 104 // If we have neither config nor state then we have no references. 105 return nil 106 } 107 108 // StateDependencies returns the dependencies which will be saved in the state 109 // for managed resources, or the most current dependencies for data resources. 110 func (n *NodeAbstractResourceInstance) StateDependencies() []addrs.ConfigResource { 111 // Managed resources prefer the stored dependencies, to avoid possible 112 // conflicts in ordering when refactoring configuration. 113 if s := n.instanceState; s != nil { 114 if s.Current != nil { 115 return s.Current.Dependencies 116 } 117 } 118 119 // If there are no stored dependencies, this is either a newly created 120 // managed resource, or a data source, and we can use the most recently 121 // calculated dependencies. 122 return n.Dependencies 123 } 124 125 // GraphNodeResourceInstance 126 func (n *NodeAbstractResourceInstance) ResourceInstanceAddr() addrs.AbsResourceInstance { 127 return n.Addr 128 } 129 130 // GraphNodeAttachResourceState 131 func (n *NodeAbstractResourceInstance) AttachResourceState(s *states.Resource) { 132 if s == nil { 133 log.Printf("[WARN] attaching nil state to %s", n.Addr) 134 return 135 } 136 log.Printf("[TRACE] NodeAbstractResourceInstance.AttachResourceState for %s", n.Addr) 137 n.instanceState = s.Instance(n.Addr.Resource.Key) 138 n.storedProviderConfig = s.ProviderConfig 139 } 140 141 // readDiff returns the planned change for a particular resource instance 142 // object. 143 func (n *NodeAbstractResourceInstance) readDiff(ctx EvalContext, providerSchema providers.ProviderSchema) (*plans.ResourceInstanceChange, error) { 144 changes := ctx.Changes() 145 addr := n.ResourceInstanceAddr() 146 147 schema, _ := providerSchema.SchemaForResourceAddr(addr.Resource.Resource) 148 if schema == nil { 149 // Should be caught during validation, so we don't bother with a pretty error here 150 return nil, fmt.Errorf("provider does not support resource type %q", addr.Resource.Resource.Type) 151 } 152 153 gen := states.CurrentGen 154 csrc := changes.GetResourceInstanceChange(addr, gen) 155 if csrc == nil { 156 log.Printf("[TRACE] readDiff: No planned change recorded for %s", n.Addr) 157 return nil, nil 158 } 159 160 change, err := csrc.Decode(schema.ImpliedType()) 161 if err != nil { 162 return nil, fmt.Errorf("failed to decode planned changes for %s: %w", n.Addr, err) 163 } 164 165 log.Printf("[TRACE] readDiff: Read %s change from plan for %s", change.Action, n.Addr) 166 167 return change, nil 168 } 169 170 func (n *NodeAbstractResourceInstance) checkPreventDestroy(change *plans.ResourceInstanceChange) error { 171 if change == nil || n.Config == nil || n.Config.Managed == nil { 172 return nil 173 } 174 175 preventDestroy := n.Config.Managed.PreventDestroy 176 177 if (change.Action == plans.Delete || change.Action.IsReplace()) && preventDestroy { 178 var diags tfdiags.Diagnostics 179 diags = diags.Append(&hcl.Diagnostic{ 180 Severity: hcl.DiagError, 181 Summary: "Instance cannot be destroyed", 182 Detail: fmt.Sprintf( 183 "Resource %s has lifecycle.prevent_destroy set, but the plan calls for this resource to be destroyed. To avoid this error and continue with the plan, either disable lifecycle.prevent_destroy or reduce the scope of the plan using the -target flag.", 184 n.Addr.String(), 185 ), 186 Subject: &n.Config.DeclRange, 187 }) 188 return diags.Err() 189 } 190 191 return nil 192 } 193 194 // preApplyHook calls the pre-Apply hook 195 func (n *NodeAbstractResourceInstance) preApplyHook(ctx EvalContext, change *plans.ResourceInstanceChange) tfdiags.Diagnostics { 196 var diags tfdiags.Diagnostics 197 198 if change == nil { 199 panic(fmt.Sprintf("preApplyHook for %s called with nil Change", n.Addr)) 200 } 201 202 // Only managed resources have user-visible apply actions. 203 if n.Addr.Resource.Resource.Mode == addrs.ManagedResourceMode { 204 priorState := change.Before 205 plannedNewState := change.After 206 207 diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) { 208 return h.PreApply(n.Addr, change.DeposedKey.Generation(), change.Action, priorState, plannedNewState) 209 })) 210 if diags.HasErrors() { 211 return diags 212 } 213 } 214 215 return nil 216 } 217 218 // postApplyHook calls the post-Apply hook 219 func (n *NodeAbstractResourceInstance) postApplyHook(ctx EvalContext, state *states.ResourceInstanceObject, err error) tfdiags.Diagnostics { 220 var diags tfdiags.Diagnostics 221 222 // Only managed resources have user-visible apply actions. 223 if n.Addr.Resource.Resource.Mode == addrs.ManagedResourceMode { 224 var newState cty.Value 225 if state != nil { 226 newState = state.Value 227 } else { 228 newState = cty.NullVal(cty.DynamicPseudoType) 229 } 230 diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) { 231 return h.PostApply(n.Addr, nil, newState, err) 232 })) 233 } 234 235 return diags 236 } 237 238 type phaseState int 239 240 const ( 241 workingState phaseState = iota 242 refreshState 243 prevRunState 244 ) 245 246 //go:generate go run golang.org/x/tools/cmd/stringer -type phaseState 247 248 // writeResourceInstanceState saves the given object as the current object for 249 // the selected resource instance. 250 // 251 // dependencies is a parameter, instead of those directly attacted to the 252 // NodeAbstractResourceInstance, because we don't write dependencies for 253 // datasources. 254 // 255 // targetState determines which context state we're writing to during plan. The 256 // default is the global working state. 257 func (n *NodeAbstractResourceInstance) writeResourceInstanceState(ctx EvalContext, obj *states.ResourceInstanceObject, targetState phaseState) error { 258 return n.writeResourceInstanceStateImpl(ctx, states.NotDeposed, obj, targetState) 259 } 260 261 func (n *NodeAbstractResourceInstance) writeResourceInstanceStateDeposed(ctx EvalContext, deposedKey states.DeposedKey, obj *states.ResourceInstanceObject, targetState phaseState) error { 262 if deposedKey == states.NotDeposed { 263 // Bail out to avoid silently doing something other than what the 264 // caller seems to have intended. 265 panic("trying to write current state object using writeResourceInstanceStateDeposed") 266 } 267 return n.writeResourceInstanceStateImpl(ctx, deposedKey, obj, targetState) 268 } 269 270 // (this is the private common body of both writeResourceInstanceState and 271 // writeResourceInstanceStateDeposed. Don't call it directly; instead, use 272 // one of the two wrappers to be explicit about which of the instance's 273 // objects you are intending to write. 274 func (n *NodeAbstractResourceInstance) writeResourceInstanceStateImpl(ctx EvalContext, deposedKey states.DeposedKey, obj *states.ResourceInstanceObject, targetState phaseState) error { 275 absAddr := n.Addr 276 _, providerSchema, err := getProvider(ctx, n.ResolvedProvider) 277 if err != nil { 278 return err 279 } 280 logFuncName := "NodeAbstractResouceInstance.writeResourceInstanceState" 281 if deposedKey == states.NotDeposed { 282 log.Printf("[TRACE] %s to %s for %s", logFuncName, targetState, absAddr) 283 } else { 284 logFuncName = "NodeAbstractResouceInstance.writeResourceInstanceStateDeposed" 285 log.Printf("[TRACE] %s to %s for %s (deposed key %s)", logFuncName, targetState, absAddr, deposedKey) 286 } 287 288 var state *states.SyncState 289 switch targetState { 290 case workingState: 291 state = ctx.State() 292 case refreshState: 293 state = ctx.RefreshState() 294 case prevRunState: 295 state = ctx.PrevRunState() 296 default: 297 panic(fmt.Sprintf("unsupported phaseState value %#v", targetState)) 298 } 299 if state == nil { 300 // Should not happen, because we shouldn't ever try to write to 301 // a state that isn't applicable to the current operation. 302 // (We can also get in here for unit tests which are using 303 // EvalContextMock but not populating PrevRunStateState with 304 // a suitable state object.) 305 return fmt.Errorf("state of type %s is not applicable to the current operation; this is a bug in OpenTofu", targetState) 306 } 307 308 // In spite of the name, this function also handles the non-deposed case 309 // via the writeResourceInstanceState wrapper, by setting deposedKey to 310 // the NotDeposed value (the zero value of DeposedKey). 311 var write func(src *states.ResourceInstanceObjectSrc) 312 if deposedKey == states.NotDeposed { 313 write = func(src *states.ResourceInstanceObjectSrc) { 314 state.SetResourceInstanceCurrent(absAddr, src, n.ResolvedProvider) 315 } 316 } else { 317 write = func(src *states.ResourceInstanceObjectSrc) { 318 state.SetResourceInstanceDeposed(absAddr, deposedKey, src, n.ResolvedProvider) 319 } 320 } 321 322 if obj == nil || obj.Value.IsNull() { 323 // No need to encode anything: we'll just write it directly. 324 write(nil) 325 log.Printf("[TRACE] %s: removing state object for %s", logFuncName, absAddr) 326 return nil 327 } 328 329 if obj != nil { 330 log.Printf("[TRACE] %s: writing state object for %s", logFuncName, absAddr) 331 } else { 332 log.Printf("[TRACE] %s: removing state object for %s", logFuncName, absAddr) 333 } 334 335 schema, currentVersion := providerSchema.SchemaForResourceAddr(absAddr.ContainingResource().Resource) 336 if schema == nil { 337 // It shouldn't be possible to get this far in any real scenario 338 // without a schema, but we might end up here in contrived tests that 339 // fail to set up their world properly. 340 return fmt.Errorf("failed to encode %s in state: no resource type schema available", absAddr) 341 } 342 343 src, err := obj.Encode(schema.ImpliedType(), currentVersion) 344 if err != nil { 345 return fmt.Errorf("failed to encode %s in state: %w", absAddr, err) 346 } 347 348 write(src) 349 return nil 350 } 351 352 // planForget returns a removed from state diff. 353 func (n *NodeAbstractResourceInstance) planForget(ctx EvalContext, currentState *states.ResourceInstanceObject, deposedKey states.DeposedKey) *plans.ResourceInstanceChange { 354 var plan *plans.ResourceInstanceChange 355 356 unmarkedPriorVal, _ := currentState.Value.UnmarkDeep() 357 358 // The config and new value are null to signify that this is a forget 359 // operation. 360 nullVal := cty.NullVal(unmarkedPriorVal.Type()) 361 362 plan = &plans.ResourceInstanceChange{ 363 Addr: n.Addr, 364 PrevRunAddr: n.prevRunAddr(ctx), 365 DeposedKey: deposedKey, 366 Change: plans.Change{ 367 Action: plans.Forget, 368 Before: currentState.Value, 369 After: nullVal, 370 }, 371 ProviderAddr: n.ResolvedProvider, 372 } 373 374 return plan 375 } 376 377 // planDestroy returns a plain destroy diff. 378 func (n *NodeAbstractResourceInstance) planDestroy(ctx EvalContext, currentState *states.ResourceInstanceObject, deposedKey states.DeposedKey) (*plans.ResourceInstanceChange, tfdiags.Diagnostics) { 379 var diags tfdiags.Diagnostics 380 var plan *plans.ResourceInstanceChange 381 382 absAddr := n.Addr 383 384 if n.ResolvedProvider.Provider.Type == "" { 385 if deposedKey == "" { 386 panic(fmt.Sprintf("planDestroy for %s does not have ProviderAddr set", absAddr)) 387 } else { 388 panic(fmt.Sprintf("planDestroy for %s (deposed %s) does not have ProviderAddr set", absAddr, deposedKey)) 389 } 390 } 391 392 // If there is no state or our attributes object is null then we're already 393 // destroyed. 394 if currentState == nil || currentState.Value.IsNull() { 395 // We still need to generate a NoOp change, because that allows 396 // outside consumers of the plan to distinguish between us affirming 397 // that we checked something and concluded no changes were needed 398 // vs. that something being entirely excluded e.g. due to -target. 399 noop := &plans.ResourceInstanceChange{ 400 Addr: absAddr, 401 PrevRunAddr: n.prevRunAddr(ctx), 402 DeposedKey: deposedKey, 403 Change: plans.Change{ 404 Action: plans.NoOp, 405 Before: cty.NullVal(cty.DynamicPseudoType), 406 After: cty.NullVal(cty.DynamicPseudoType), 407 }, 408 ProviderAddr: n.ResolvedProvider, 409 } 410 return noop, nil 411 } 412 413 unmarkedPriorVal, _ := currentState.Value.UnmarkDeep() 414 415 // The config and new value are null to signify that this is a destroy 416 // operation. 417 nullVal := cty.NullVal(unmarkedPriorVal.Type()) 418 419 provider, _, err := getProvider(ctx, n.ResolvedProvider) 420 if err != nil { 421 return plan, diags.Append(err) 422 } 423 424 metaConfigVal, metaDiags := n.providerMetas(ctx) 425 diags = diags.Append(metaDiags) 426 if diags.HasErrors() { 427 return plan, diags 428 } 429 430 // Allow the provider to check the destroy plan, and insert any necessary 431 // private data. 432 resp := provider.PlanResourceChange(providers.PlanResourceChangeRequest{ 433 TypeName: n.Addr.Resource.Resource.Type, 434 Config: nullVal, 435 PriorState: unmarkedPriorVal, 436 ProposedNewState: nullVal, 437 PriorPrivate: currentState.Private, 438 ProviderMeta: metaConfigVal, 439 }) 440 441 // We may not have a config for all destroys, but we want to reference it in 442 // the diagnostics if we do. 443 if n.Config != nil { 444 resp.Diagnostics = resp.Diagnostics.InConfigBody(n.Config.Config, n.Addr.String()) 445 } 446 diags = diags.Append(resp.Diagnostics) 447 if diags.HasErrors() { 448 return plan, diags 449 } 450 451 // Check that the provider returned a null value here, since that is the 452 // only valid value for a destroy plan. 453 if !resp.PlannedState.IsNull() { 454 diags = diags.Append(tfdiags.Sourceless( 455 tfdiags.Error, 456 "Provider produced invalid plan", 457 fmt.Sprintf( 458 "Provider %q planned a non-null destroy value for %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", 459 n.ResolvedProvider.Provider, n.Addr), 460 ), 461 ) 462 return plan, diags 463 } 464 465 // Plan is always the same for a destroy. 466 plan = &plans.ResourceInstanceChange{ 467 Addr: absAddr, 468 PrevRunAddr: n.prevRunAddr(ctx), 469 DeposedKey: deposedKey, 470 Change: plans.Change{ 471 Action: plans.Delete, 472 Before: currentState.Value, 473 After: nullVal, 474 }, 475 Private: resp.PlannedPrivate, 476 ProviderAddr: n.ResolvedProvider, 477 } 478 479 return plan, diags 480 } 481 482 // writeChange saves a planned change for an instance object into the set of 483 // global planned changes. 484 func (n *NodeAbstractResourceInstance) writeChange(ctx EvalContext, change *plans.ResourceInstanceChange, deposedKey states.DeposedKey) error { 485 changes := ctx.Changes() 486 487 if change == nil { 488 // Caller sets nil to indicate that we need to remove a change from 489 // the set of changes. 490 gen := states.CurrentGen 491 if deposedKey != states.NotDeposed { 492 gen = deposedKey 493 } 494 changes.RemoveResourceInstanceChange(n.Addr, gen) 495 return nil 496 } 497 498 _, providerSchema, err := getProvider(ctx, n.ResolvedProvider) 499 if err != nil { 500 return err 501 } 502 503 if change.Addr.String() != n.Addr.String() || change.DeposedKey != deposedKey { 504 // Should never happen, and indicates a bug in the caller. 505 panic("inconsistent address and/or deposed key in writeChange") 506 } 507 if change.PrevRunAddr.Resource.Resource.Type == "" { 508 // Should never happen, and indicates a bug in the caller. 509 // (The change.Encode function actually has its own fixup to just 510 // quietly make this match change.Addr in the incorrect case, but we 511 // intentionally panic here in order to catch incorrect callers where 512 // the stack trace will hopefully be actually useful. The tolerance 513 // at the next layer down is mainly to accommodate sloppy input in 514 // older tests.) 515 panic("unpopulated ResourceInstanceChange.PrevRunAddr in writeChange") 516 } 517 518 ri := n.Addr.Resource 519 schema, _ := providerSchema.SchemaForResourceAddr(ri.Resource) 520 if schema == nil { 521 // Should be caught during validation, so we don't bother with a pretty error here 522 return fmt.Errorf("provider does not support resource type %q", ri.Resource.Type) 523 } 524 525 csrc, err := change.Encode(schema.ImpliedType()) 526 if err != nil { 527 return fmt.Errorf("failed to encode planned changes for %s: %w", n.Addr, err) 528 } 529 530 changes.AppendResourceInstanceChange(csrc) 531 if deposedKey == states.NotDeposed { 532 log.Printf("[TRACE] writeChange: recorded %s change for %s", change.Action, n.Addr) 533 } else { 534 log.Printf("[TRACE] writeChange: recorded %s change for %s deposed object %s", change.Action, n.Addr, deposedKey) 535 } 536 537 return nil 538 } 539 540 // refresh does a refresh for a resource 541 func (n *NodeAbstractResourceInstance) refresh(ctx EvalContext, deposedKey states.DeposedKey, state *states.ResourceInstanceObject) (*states.ResourceInstanceObject, tfdiags.Diagnostics) { 542 var diags tfdiags.Diagnostics 543 absAddr := n.Addr 544 if deposedKey == states.NotDeposed { 545 log.Printf("[TRACE] NodeAbstractResourceInstance.refresh for %s", absAddr) 546 } else { 547 log.Printf("[TRACE] NodeAbstractResourceInstance.refresh for %s (deposed object %s)", absAddr, deposedKey) 548 } 549 provider, providerSchema, err := getProvider(ctx, n.ResolvedProvider) 550 if err != nil { 551 return state, diags.Append(err) 552 } 553 // If we have no state, we don't do any refreshing 554 if state == nil { 555 log.Printf("[DEBUG] refresh: %s: no state, so not refreshing", absAddr) 556 return state, diags 557 } 558 559 schema, _ := providerSchema.SchemaForResourceAddr(n.Addr.Resource.ContainingResource()) 560 if schema == nil { 561 // Should be caught during validation, so we don't bother with a pretty error here 562 diags = diags.Append(fmt.Errorf("provider does not support resource type %q", n.Addr.Resource.Resource.Type)) 563 return state, diags 564 } 565 566 metaConfigVal, metaDiags := n.providerMetas(ctx) 567 diags = diags.Append(metaDiags) 568 if diags.HasErrors() { 569 return state, diags 570 } 571 572 hookGen := states.CurrentGen 573 if deposedKey != states.NotDeposed { 574 hookGen = deposedKey 575 } 576 577 // Call pre-refresh hook 578 diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) { 579 return h.PreRefresh(absAddr, hookGen, state.Value) 580 })) 581 if diags.HasErrors() { 582 return state, diags 583 } 584 585 // Refresh! 586 priorVal := state.Value 587 588 // Unmarked before sending to provider 589 var priorPaths []cty.PathValueMarks 590 if priorVal.ContainsMarked() { 591 priorVal, priorPaths = priorVal.UnmarkDeepWithPaths() 592 } 593 594 providerReq := providers.ReadResourceRequest{ 595 TypeName: n.Addr.Resource.Resource.Type, 596 PriorState: priorVal, 597 Private: state.Private, 598 ProviderMeta: metaConfigVal, 599 } 600 601 resp := provider.ReadResource(providerReq) 602 if n.Config != nil { 603 resp.Diagnostics = resp.Diagnostics.InConfigBody(n.Config.Config, n.Addr.String()) 604 } 605 606 diags = diags.Append(resp.Diagnostics) 607 if diags.HasErrors() { 608 return state, diags 609 } 610 611 if resp.NewState == cty.NilVal { 612 // This ought not to happen in real cases since it's not possible to 613 // send NilVal over the plugin RPC channel, but it can come up in 614 // tests due to sloppy mocking. 615 panic("new state is cty.NilVal") 616 } 617 618 for _, err := range resp.NewState.Type().TestConformance(schema.ImpliedType()) { 619 diags = diags.Append(tfdiags.Sourceless( 620 tfdiags.Error, 621 "Provider produced invalid object", 622 fmt.Sprintf( 623 "Provider %q planned an invalid value for %s during refresh: %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", 624 n.ResolvedProvider.Provider.String(), absAddr, tfdiags.FormatError(err), 625 ), 626 )) 627 } 628 if diags.HasErrors() { 629 return state, diags 630 } 631 632 newState := objchange.NormalizeObjectFromLegacySDK(resp.NewState, schema) 633 if !newState.RawEquals(resp.NewState) { 634 // We had to fix up this object in some way, and we still need to 635 // accept any changes for compatibility, so all we can do is log a 636 // warning about the change. 637 log.Printf("[WARN] Provider %q produced an invalid new value containing null blocks for %q during refresh\n", n.ResolvedProvider.Provider, n.Addr) 638 } 639 640 ret := state.DeepCopy() 641 ret.Value = newState 642 ret.Private = resp.Private 643 644 // We have no way to exempt provider using the legacy SDK from this check, 645 // so we can only log inconsistencies with the updated state values. 646 // In most cases these are not errors anyway, and represent "drift" from 647 // external changes which will be handled by the subsequent plan. 648 if errs := objchange.AssertObjectCompatible(schema, priorVal, ret.Value); len(errs) > 0 { 649 var buf strings.Builder 650 fmt.Fprintf(&buf, "[WARN] Provider %q produced an unexpected new value for %s during refresh.", n.ResolvedProvider.Provider.String(), absAddr) 651 for _, err := range errs { 652 fmt.Fprintf(&buf, "\n - %s", tfdiags.FormatError(err)) 653 } 654 log.Print(buf.String()) 655 } 656 657 // Call post-refresh hook 658 diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) { 659 return h.PostRefresh(absAddr, hookGen, priorVal, ret.Value) 660 })) 661 if diags.HasErrors() { 662 return ret, diags 663 } 664 665 // Mark the value if necessary 666 if len(priorPaths) > 0 { 667 ret.Value = ret.Value.MarkWithPaths(priorPaths) 668 } 669 670 return ret, diags 671 } 672 673 func (n *NodeAbstractResourceInstance) plan( 674 ctx EvalContext, 675 plannedChange *plans.ResourceInstanceChange, 676 currentState *states.ResourceInstanceObject, 677 createBeforeDestroy bool, 678 forceReplace []addrs.AbsResourceInstance, 679 ) (*plans.ResourceInstanceChange, *states.ResourceInstanceObject, instances.RepetitionData, tfdiags.Diagnostics) { 680 var diags tfdiags.Diagnostics 681 var keyData instances.RepetitionData 682 683 resource := n.Addr.Resource.Resource 684 provider, providerSchema, err := getProvider(ctx, n.ResolvedProvider) 685 if err != nil { 686 return nil, nil, keyData, diags.Append(err) 687 } 688 689 schema, _ := providerSchema.SchemaForResourceAddr(resource) 690 if schema == nil { 691 // Should be caught during validation, so we don't bother with a pretty error here 692 diags = diags.Append(fmt.Errorf("provider does not support resource type %q", resource.Type)) 693 return nil, nil, keyData, diags 694 } 695 696 // If we're importing and generating config, generate it now. 697 if n.Config == nil { 698 // This shouldn't happen. A node that isn't generating config should 699 // have embedded config, and the rest of OpenTofu should enforce this. 700 // If, however, we didn't do things correctly the next line will panic, 701 // so let's not do that and return an error message with more context. 702 703 diags = diags.Append(tfdiags.Sourceless( 704 tfdiags.Error, 705 "Resource has no configuration", 706 fmt.Sprintf("OpenTofu attempted to process a resource at %s that has no configuration. This is a bug in OpenTofu; please report it!", n.Addr.String()))) 707 return nil, nil, keyData, diags 708 } 709 710 config := *n.Config 711 712 checkRuleSeverity := tfdiags.Error 713 if n.preDestroyRefresh { 714 checkRuleSeverity = tfdiags.Warning 715 } 716 717 if plannedChange != nil { 718 // If we already planned the action, we stick to that plan 719 createBeforeDestroy = plannedChange.Action == plans.CreateThenDelete 720 } 721 722 // Evaluate the configuration 723 forEach, _ := evaluateForEachExpression(n.Config.ForEach, ctx) 724 725 keyData = EvalDataForInstanceKey(n.ResourceInstanceAddr().Resource.Key, forEach) 726 727 checkDiags := evalCheckRules( 728 addrs.ResourcePrecondition, 729 n.Config.Preconditions, 730 ctx, n.Addr, keyData, 731 checkRuleSeverity, 732 ) 733 diags = diags.Append(checkDiags) 734 if diags.HasErrors() { 735 return nil, nil, keyData, diags // failed preconditions prevent further evaluation 736 } 737 738 // If we have a previous plan and the action was a noop, then the only 739 // reason we're in this method was to evaluate the preconditions. There's 740 // no need to re-plan this resource. 741 if plannedChange != nil && plannedChange.Action == plans.NoOp { 742 return plannedChange, currentState.DeepCopy(), keyData, diags 743 } 744 745 origConfigVal, _, configDiags := ctx.EvaluateBlock(config.Config, schema, nil, keyData) 746 diags = diags.Append(configDiags) 747 if configDiags.HasErrors() { 748 return nil, nil, keyData, diags 749 } 750 751 metaConfigVal, metaDiags := n.providerMetas(ctx) 752 diags = diags.Append(metaDiags) 753 if diags.HasErrors() { 754 return nil, nil, keyData, diags 755 } 756 757 var priorVal cty.Value 758 var priorValTainted cty.Value 759 var priorPrivate []byte 760 if currentState != nil { 761 if currentState.Status != states.ObjectTainted { 762 priorVal = currentState.Value 763 priorPrivate = currentState.Private 764 } else { 765 // If the prior state is tainted then we'll proceed below like 766 // we're creating an entirely new object, but then turn it into 767 // a synthetic "Replace" change at the end, creating the same 768 // result as if the provider had marked at least one argument 769 // change as "requires replacement". 770 priorValTainted = currentState.Value 771 priorVal = cty.NullVal(schema.ImpliedType()) 772 } 773 } else { 774 priorVal = cty.NullVal(schema.ImpliedType()) 775 } 776 777 log.Printf("[TRACE] Re-validating config for %q", n.Addr) 778 // Allow the provider to validate the final set of values. The config was 779 // statically validated early on, but there may have been unknown values 780 // which the provider could not validate at the time. 781 // 782 // TODO: It would be more correct to validate the config after 783 // ignore_changes has been applied, but the current implementation cannot 784 // exclude computed-only attributes when given the `all` option. 785 786 // we must unmark and use the original config, since the ignore_changes 787 // handling below needs access to the marks. 788 unmarkedConfigVal, _ := origConfigVal.UnmarkDeep() 789 validateResp := provider.ValidateResourceConfig( 790 providers.ValidateResourceConfigRequest{ 791 TypeName: n.Addr.Resource.Resource.Type, 792 Config: unmarkedConfigVal, 793 }, 794 ) 795 diags = diags.Append(validateResp.Diagnostics.InConfigBody(config.Config, n.Addr.String())) 796 if diags.HasErrors() { 797 return nil, nil, keyData, diags 798 } 799 800 // ignore_changes is meant to only apply to the configuration, so it must 801 // be applied before we generate a plan. This ensures the config used for 802 // the proposed value, the proposed value itself, and the config presented 803 // to the provider in the PlanResourceChange request all agree on the 804 // starting values. 805 // Here we operate on the marked values, so as to revert any changes to the 806 // marks as well as the value. 807 configValIgnored, ignoreChangeDiags := n.processIgnoreChanges(priorVal, origConfigVal, schema) 808 diags = diags.Append(ignoreChangeDiags) 809 if ignoreChangeDiags.HasErrors() { 810 return nil, nil, keyData, diags 811 } 812 813 // Create an unmarked version of our config val and our prior val. 814 // Store the paths for the config val to re-mark after we've sent things 815 // over the wire. 816 unmarkedConfigVal, unmarkedPaths := configValIgnored.UnmarkDeepWithPaths() 817 unmarkedPriorVal, priorPaths := priorVal.UnmarkDeepWithPaths() 818 819 proposedNewVal := objchange.ProposedNew(schema, unmarkedPriorVal, unmarkedConfigVal) 820 821 // Call pre-diff hook 822 diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) { 823 return h.PreDiff(n.Addr, states.CurrentGen, priorVal, proposedNewVal) 824 })) 825 if diags.HasErrors() { 826 return nil, nil, keyData, diags 827 } 828 829 resp := provider.PlanResourceChange(providers.PlanResourceChangeRequest{ 830 TypeName: n.Addr.Resource.Resource.Type, 831 Config: unmarkedConfigVal, 832 PriorState: unmarkedPriorVal, 833 ProposedNewState: proposedNewVal, 834 PriorPrivate: priorPrivate, 835 ProviderMeta: metaConfigVal, 836 }) 837 diags = diags.Append(resp.Diagnostics.InConfigBody(config.Config, n.Addr.String())) 838 if diags.HasErrors() { 839 return nil, nil, keyData, diags 840 } 841 842 plannedNewVal := resp.PlannedState 843 plannedPrivate := resp.PlannedPrivate 844 845 if plannedNewVal == cty.NilVal { 846 // Should never happen. Since real-world providers return via RPC a nil 847 // is always a bug in the client-side stub. This is more likely caused 848 // by an incompletely-configured mock provider in tests, though. 849 panic(fmt.Sprintf("PlanResourceChange of %s produced nil value", n.Addr)) 850 } 851 852 // We allow the planned new value to disagree with configuration _values_ 853 // here, since that allows the provider to do special logic like a 854 // DiffSuppressFunc, but we still require that the provider produces 855 // a value whose type conforms to the schema. 856 for _, err := range plannedNewVal.Type().TestConformance(schema.ImpliedType()) { 857 diags = diags.Append(tfdiags.Sourceless( 858 tfdiags.Error, 859 "Provider produced invalid plan", 860 fmt.Sprintf( 861 "Provider %q planned an invalid value for %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", 862 n.ResolvedProvider.Provider, tfdiags.FormatErrorPrefixed(err, n.Addr.String()), 863 ), 864 )) 865 } 866 if diags.HasErrors() { 867 return nil, nil, keyData, diags 868 } 869 870 if errs := objchange.AssertPlanValid(schema, unmarkedPriorVal, unmarkedConfigVal, plannedNewVal); len(errs) > 0 { 871 if resp.LegacyTypeSystem { 872 // The shimming of the old type system in the legacy SDK is not precise 873 // enough to pass this consistency check, so we'll give it a pass here, 874 // but we will generate a warning about it so that we are more likely 875 // to notice in the logs if an inconsistency beyond the type system 876 // leads to a downstream provider failure. 877 var buf strings.Builder 878 fmt.Fprintf(&buf, 879 "[WARN] Provider %q produced an invalid plan for %s, but we are tolerating it because it is using the legacy plugin SDK.\n The following problems may be the cause of any confusing errors from downstream operations:", 880 n.ResolvedProvider.Provider, n.Addr, 881 ) 882 for _, err := range errs { 883 fmt.Fprintf(&buf, "\n - %s", tfdiags.FormatError(err)) 884 } 885 log.Print(buf.String()) 886 } else { 887 for _, err := range errs { 888 diags = diags.Append(tfdiags.Sourceless( 889 tfdiags.Error, 890 "Provider produced invalid plan", 891 fmt.Sprintf( 892 "Provider %q planned an invalid value for %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", 893 n.ResolvedProvider.Provider, tfdiags.FormatErrorPrefixed(err, n.Addr.String()), 894 ), 895 )) 896 } 897 return nil, nil, keyData, diags 898 } 899 } 900 901 if resp.LegacyTypeSystem { 902 // Because we allow legacy providers to depart from the contract and 903 // return changes to non-computed values, the plan response may have 904 // altered values that were already suppressed with ignore_changes. 905 // A prime example of this is where providers attempt to obfuscate 906 // config data by turning the config value into a hash and storing the 907 // hash value in the state. There are enough cases of this in existing 908 // providers that we must accommodate the behavior for now, so for 909 // ignore_changes to work at all on these values, we will revert the 910 // ignored values once more. 911 // A nil schema is passed to processIgnoreChanges to indicate that we 912 // don't want to fixup a config value according to the schema when 913 // ignoring "all", rather we are reverting provider imposed changes. 914 plannedNewVal, ignoreChangeDiags = n.processIgnoreChanges(unmarkedPriorVal, plannedNewVal, nil) 915 diags = diags.Append(ignoreChangeDiags) 916 if ignoreChangeDiags.HasErrors() { 917 return nil, nil, keyData, diags 918 } 919 } 920 921 // Add the marks back to the planned new value -- this must happen after ignore changes 922 // have been processed 923 unmarkedPlannedNewVal := plannedNewVal 924 if len(unmarkedPaths) > 0 { 925 plannedNewVal = plannedNewVal.MarkWithPaths(unmarkedPaths) 926 } 927 928 // The provider produces a list of paths to attributes whose changes mean 929 // that we must replace rather than update an existing remote object. 930 // However, we only need to do that if the identified attributes _have_ 931 // actually changed -- particularly after we may have undone some of the 932 // changes in processIgnoreChanges -- so now we'll filter that list to 933 // include only where changes are detected. 934 reqRep := cty.NewPathSet() 935 if len(resp.RequiresReplace) > 0 { 936 for _, path := range resp.RequiresReplace { 937 if priorVal.IsNull() { 938 // If prior is null then we don't expect any RequiresReplace at all, 939 // because this is a Create action. 940 continue 941 } 942 943 priorChangedVal, priorPathDiags := hcl.ApplyPath(unmarkedPriorVal, path, nil) 944 plannedChangedVal, plannedPathDiags := hcl.ApplyPath(plannedNewVal, path, nil) 945 if plannedPathDiags.HasErrors() && priorPathDiags.HasErrors() { 946 // This means the path was invalid in both the prior and new 947 // values, which is an error with the provider itself. 948 diags = diags.Append(tfdiags.Sourceless( 949 tfdiags.Error, 950 "Provider produced invalid plan", 951 fmt.Sprintf( 952 "Provider %q has indicated \"requires replacement\" on %s for a non-existent attribute path %#v.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", 953 n.ResolvedProvider.Provider, n.Addr, path, 954 ), 955 )) 956 continue 957 } 958 959 // Make sure we have valid Values for both values. 960 // Note: if the opposing value was of the type 961 // cty.DynamicPseudoType, the type assigned here may not exactly 962 // match the schema. This is fine here, since we're only going to 963 // check for equality, but if the NullVal is to be used, we need to 964 // check the schema for th true type. 965 switch { 966 case priorChangedVal == cty.NilVal && plannedChangedVal == cty.NilVal: 967 // this should never happen without ApplyPath errors above 968 panic("requires replace path returned 2 nil values") 969 case priorChangedVal == cty.NilVal: 970 priorChangedVal = cty.NullVal(plannedChangedVal.Type()) 971 case plannedChangedVal == cty.NilVal: 972 plannedChangedVal = cty.NullVal(priorChangedVal.Type()) 973 } 974 975 // Unmark for this value for the equality test. If only sensitivity has changed, 976 // this does not require an Update or Replace 977 unmarkedPlannedChangedVal, _ := plannedChangedVal.UnmarkDeep() 978 eqV := unmarkedPlannedChangedVal.Equals(priorChangedVal) 979 if !eqV.IsKnown() || eqV.False() { 980 reqRep.Add(path) 981 } 982 } 983 if diags.HasErrors() { 984 return nil, nil, keyData, diags 985 } 986 } 987 988 // The user might also ask us to force replacing a particular resource 989 // instance, regardless of whether the provider thinks it needs replacing. 990 // For example, users typically do this if they learn a particular object 991 // has become degraded in an immutable infrastructure scenario and so 992 // replacing it with a new object is a viable repair path. 993 matchedForceReplace := false 994 for _, candidateAddr := range forceReplace { 995 if candidateAddr.Equal(n.Addr) { 996 matchedForceReplace = true 997 break 998 } 999 1000 // For "force replace" purposes we require an exact resource instance 1001 // address to match. If a user forgets to include the instance key 1002 // for a multi-instance resource then it won't match here, but we 1003 // have an earlier check in NodePlannableResource.Execute that should 1004 // prevent us from getting here in that case. 1005 } 1006 1007 // Unmark for this test for value equality. 1008 eqV := unmarkedPlannedNewVal.Equals(unmarkedPriorVal) 1009 eq := eqV.IsKnown() && eqV.True() 1010 1011 var action plans.Action 1012 var actionReason plans.ResourceInstanceChangeActionReason 1013 switch { 1014 case priorVal.IsNull(): 1015 action = plans.Create 1016 case eq && !matchedForceReplace: 1017 action = plans.NoOp 1018 case matchedForceReplace || !reqRep.Empty(): 1019 // If the user "forced replace" of this instance of if there are any 1020 // "requires replace" paths left _after our filtering above_ then this 1021 // is a replace action. 1022 if createBeforeDestroy { 1023 action = plans.CreateThenDelete 1024 } else { 1025 action = plans.DeleteThenCreate 1026 } 1027 switch { 1028 case matchedForceReplace: 1029 actionReason = plans.ResourceInstanceReplaceByRequest 1030 case !reqRep.Empty(): 1031 actionReason = plans.ResourceInstanceReplaceBecauseCannotUpdate 1032 } 1033 default: 1034 action = plans.Update 1035 // "Delete" is never chosen here, because deletion plans are always 1036 // created more directly elsewhere, such as in "orphan" handling. 1037 } 1038 1039 if action.IsReplace() { 1040 // In this strange situation we want to produce a change object that 1041 // shows our real prior object but has a _new_ object that is built 1042 // from a null prior object, since we're going to delete the one 1043 // that has all the computed values on it. 1044 // 1045 // Therefore we'll ask the provider to plan again here, giving it 1046 // a null object for the prior, and then we'll meld that with the 1047 // _actual_ prior state to produce a correctly-shaped replace change. 1048 // The resulting change should show any computed attributes changing 1049 // from known prior values to unknown values, unless the provider is 1050 // able to predict new values for any of these computed attributes. 1051 nullPriorVal := cty.NullVal(schema.ImpliedType()) 1052 1053 // Since there is no prior state to compare after replacement, we need 1054 // a new unmarked config from our original with no ignored values. 1055 unmarkedConfigVal := origConfigVal 1056 if origConfigVal.ContainsMarked() { 1057 unmarkedConfigVal, _ = origConfigVal.UnmarkDeep() 1058 } 1059 1060 // create a new proposed value from the null state and the config 1061 proposedNewVal = objchange.ProposedNew(schema, nullPriorVal, unmarkedConfigVal) 1062 1063 resp = provider.PlanResourceChange(providers.PlanResourceChangeRequest{ 1064 TypeName: n.Addr.Resource.Resource.Type, 1065 Config: unmarkedConfigVal, 1066 PriorState: nullPriorVal, 1067 ProposedNewState: proposedNewVal, 1068 PriorPrivate: plannedPrivate, 1069 ProviderMeta: metaConfigVal, 1070 }) 1071 // We need to tread carefully here, since if there are any warnings 1072 // in here they probably also came out of our previous call to 1073 // PlanResourceChange above, and so we don't want to repeat them. 1074 // Consequently, we break from the usual pattern here and only 1075 // append these new diagnostics if there's at least one error inside. 1076 if resp.Diagnostics.HasErrors() { 1077 diags = diags.Append(resp.Diagnostics.InConfigBody(config.Config, n.Addr.String())) 1078 return nil, nil, keyData, diags 1079 } 1080 plannedNewVal = resp.PlannedState 1081 plannedPrivate = resp.PlannedPrivate 1082 1083 if len(unmarkedPaths) > 0 { 1084 plannedNewVal = plannedNewVal.MarkWithPaths(unmarkedPaths) 1085 } 1086 1087 for _, err := range plannedNewVal.Type().TestConformance(schema.ImpliedType()) { 1088 diags = diags.Append(tfdiags.Sourceless( 1089 tfdiags.Error, 1090 "Provider produced invalid plan", 1091 fmt.Sprintf( 1092 "Provider %q planned an invalid value for %s%s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", 1093 n.ResolvedProvider.Provider, n.Addr, tfdiags.FormatError(err), 1094 ), 1095 )) 1096 } 1097 if diags.HasErrors() { 1098 return nil, nil, keyData, diags 1099 } 1100 } 1101 1102 // If our prior value was tainted then we actually want this to appear 1103 // as a replace change, even though so far we've been treating it as a 1104 // create. 1105 if action == plans.Create && !priorValTainted.IsNull() { 1106 if createBeforeDestroy { 1107 action = plans.CreateThenDelete 1108 } else { 1109 action = plans.DeleteThenCreate 1110 } 1111 priorVal = priorValTainted 1112 actionReason = plans.ResourceInstanceReplaceBecauseTainted 1113 } 1114 1115 // If we plan to write or delete sensitive paths from state, 1116 // this is an Update action. 1117 // 1118 // We need to filter out any marks which may not apply to the new planned 1119 // value before comparison. The one case where a provider is allowed to 1120 // return a different value from the configuration is when a config change 1121 // is not functionally significant and the prior state can be returned. If a 1122 // new mark was also discarded from that config change, it needs to be 1123 // ignored here to prevent an errant update action. 1124 if action == plans.NoOp && !marksEqual(filterMarks(plannedNewVal, unmarkedPaths), priorPaths) { 1125 action = plans.Update 1126 } 1127 1128 // As a special case, if we have a previous diff (presumably from the plan 1129 // phases, whereas we're now in the apply phase) and it was for a replace, 1130 // we've already deleted the original object from state by the time we 1131 // get here and so we would've ended up with a _create_ action this time, 1132 // which we now need to paper over to get a result consistent with what 1133 // we originally intended. 1134 if plannedChange != nil { 1135 prevChange := *plannedChange 1136 if prevChange.Action.IsReplace() && action == plans.Create { 1137 log.Printf("[TRACE] plan: %s treating Create change as %s change to match with earlier plan", n.Addr, prevChange.Action) 1138 action = prevChange.Action 1139 priorVal = prevChange.Before 1140 } 1141 } 1142 1143 // Call post-refresh hook 1144 diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) { 1145 return h.PostDiff(n.Addr, states.CurrentGen, action, priorVal, plannedNewVal) 1146 })) 1147 if diags.HasErrors() { 1148 return nil, nil, keyData, diags 1149 } 1150 1151 // Update our return plan 1152 plan := &plans.ResourceInstanceChange{ 1153 Addr: n.Addr, 1154 PrevRunAddr: n.prevRunAddr(ctx), 1155 Private: plannedPrivate, 1156 ProviderAddr: n.ResolvedProvider, 1157 Change: plans.Change{ 1158 Action: action, 1159 Before: priorVal, 1160 // Pass the marked planned value through in our change 1161 // to propogate through evaluation. 1162 // Marks will be removed when encoding. 1163 After: plannedNewVal, 1164 GeneratedConfig: n.generatedConfigHCL, 1165 }, 1166 ActionReason: actionReason, 1167 RequiredReplace: reqRep, 1168 } 1169 1170 // Update our return state 1171 state := &states.ResourceInstanceObject{ 1172 // We use the special "planned" status here to note that this 1173 // object's value is not yet complete. Objects with this status 1174 // cannot be used during expression evaluation, so the caller 1175 // must _also_ record the returned change in the active plan, 1176 // which the expression evaluator will use in preference to this 1177 // incomplete value recorded in the state. 1178 Status: states.ObjectPlanned, 1179 Value: plannedNewVal, 1180 Private: plannedPrivate, 1181 } 1182 1183 return plan, state, keyData, diags 1184 } 1185 1186 func (n *NodeAbstractResource) processIgnoreChanges(prior, config cty.Value, schema *configschema.Block) (cty.Value, tfdiags.Diagnostics) { 1187 // ignore_changes only applies when an object already exists, since we 1188 // can't ignore changes to a thing we've not created yet. 1189 if prior.IsNull() { 1190 return config, nil 1191 } 1192 1193 ignoreChanges := traversalsToPaths(n.Config.Managed.IgnoreChanges) 1194 ignoreAll := n.Config.Managed.IgnoreAllChanges 1195 1196 if len(ignoreChanges) == 0 && !ignoreAll { 1197 return config, nil 1198 } 1199 1200 if ignoreAll { 1201 // Legacy providers need up to clean up their invalid plans and ensure 1202 // no changes are passed though, but that also means making an invalid 1203 // config with computed values. In that case we just don't supply a 1204 // schema and return the prior val directly. 1205 if schema == nil { 1206 return prior, nil 1207 } 1208 1209 // If we are trying to ignore all attribute changes, we must filter 1210 // computed attributes out from the prior state to avoid sending them 1211 // to the provider as if they were included in the configuration. 1212 ret, _ := cty.Transform(prior, func(path cty.Path, v cty.Value) (cty.Value, error) { 1213 attr := schema.AttributeByPath(path) 1214 if attr != nil && attr.Computed && !attr.Optional { 1215 return cty.NullVal(v.Type()), nil 1216 } 1217 1218 return v, nil 1219 }) 1220 1221 return ret, nil 1222 } 1223 1224 if prior.IsNull() || config.IsNull() { 1225 // Ignore changes doesn't apply when we're creating for the first time. 1226 // Proposed should never be null here, but if it is then we'll just let it be. 1227 return config, nil 1228 } 1229 1230 ret, diags := processIgnoreChangesIndividual(prior, config, ignoreChanges) 1231 1232 return ret, diags 1233 } 1234 1235 // Convert the hcl.Traversal values we get form the configuration to the 1236 // cty.Path values we need to operate on the cty.Values 1237 func traversalsToPaths(traversals []hcl.Traversal) []cty.Path { 1238 paths := make([]cty.Path, len(traversals)) 1239 for i, traversal := range traversals { 1240 path := traversalToPath(traversal) 1241 paths[i] = path 1242 } 1243 return paths 1244 } 1245 1246 func traversalToPath(traversal hcl.Traversal) cty.Path { 1247 path := make(cty.Path, len(traversal)) 1248 for si, step := range traversal { 1249 switch ts := step.(type) { 1250 case hcl.TraverseRoot: 1251 path[si] = cty.GetAttrStep{ 1252 Name: ts.Name, 1253 } 1254 case hcl.TraverseAttr: 1255 path[si] = cty.GetAttrStep{ 1256 Name: ts.Name, 1257 } 1258 case hcl.TraverseIndex: 1259 path[si] = cty.IndexStep{ 1260 Key: ts.Key, 1261 } 1262 default: 1263 panic(fmt.Sprintf("unsupported traversal step %#v", step)) 1264 } 1265 } 1266 return path 1267 } 1268 1269 func processIgnoreChangesIndividual(prior, config cty.Value, ignoreChangesPath []cty.Path) (cty.Value, tfdiags.Diagnostics) { 1270 type ignoreChange struct { 1271 // Path is the full path, minus any trailing map index 1272 path cty.Path 1273 // Value is the value we are to retain at the above path. If there is a 1274 // key value, this must be a map and the desired value will be at the 1275 // key index. 1276 value cty.Value 1277 // Key is the index key if the ignored path ends in a map index. 1278 key cty.Value 1279 } 1280 var ignoredValues []ignoreChange 1281 1282 // Find the actual changes first and store them in the ignoreChange struct. 1283 // If the change was to a map value, and the key doesn't exist in the 1284 // config, it would never be visited in the transform walk. 1285 for _, icPath := range ignoreChangesPath { 1286 key := cty.NullVal(cty.String) 1287 // check for a map index, since maps are the only structure where we 1288 // could have invalid path steps. 1289 last, ok := icPath[len(icPath)-1].(cty.IndexStep) 1290 if ok { 1291 if last.Key.Type() == cty.String { 1292 icPath = icPath[:len(icPath)-1] 1293 key = last.Key 1294 } 1295 } 1296 1297 // The structure should have been validated already, and we already 1298 // trimmed the trailing map index. Any other intermediate index error 1299 // means we wouldn't be able to apply the value below, so no need to 1300 // record this. 1301 p, err := icPath.Apply(prior) 1302 if err != nil { 1303 continue 1304 } 1305 c, err := icPath.Apply(config) 1306 if err != nil { 1307 continue 1308 } 1309 1310 // If this is a map, it is checking the entire map value for equality 1311 // rather than the individual key. This means that the change is stored 1312 // here even if our ignored key doesn't change. That is OK since it 1313 // won't cause any changes in the transformation, but allows us to skip 1314 // breaking up the maps and checking for key existence here too. 1315 if !p.RawEquals(c) { 1316 // there a change to ignore at this path, store the prior value 1317 ignoredValues = append(ignoredValues, ignoreChange{icPath, p, key}) 1318 } 1319 } 1320 1321 if len(ignoredValues) == 0 { 1322 return config, nil 1323 } 1324 1325 ret, _ := cty.Transform(config, func(path cty.Path, v cty.Value) (cty.Value, error) { 1326 // Easy path for when we are only matching the entire value. The only 1327 // values we break up for inspection are maps. 1328 if !v.Type().IsMapType() { 1329 for _, ignored := range ignoredValues { 1330 if path.Equals(ignored.path) { 1331 return ignored.value, nil 1332 } 1333 } 1334 return v, nil 1335 } 1336 // We now know this must be a map, so we need to accumulate the values 1337 // key-by-key. 1338 1339 if !v.IsNull() && !v.IsKnown() { 1340 // since v is not known, we cannot ignore individual keys 1341 return v, nil 1342 } 1343 1344 // The map values will remain as cty values, so we only need to store 1345 // the marks from the outer map itself 1346 v, vMarks := v.Unmark() 1347 1348 // The configMap is the current configuration value, which we will 1349 // mutate based on the ignored paths and the prior map value. 1350 var configMap map[string]cty.Value 1351 switch { 1352 case v.IsNull() || v.LengthInt() == 0: 1353 configMap = map[string]cty.Value{} 1354 default: 1355 configMap = v.AsValueMap() 1356 } 1357 1358 for _, ignored := range ignoredValues { 1359 if !path.Equals(ignored.path) { 1360 continue 1361 } 1362 1363 if ignored.key.IsNull() { 1364 // The map address is confirmed to match at this point, 1365 // so if there is no key, we want the entire map and can 1366 // stop accumulating values. 1367 return ignored.value, nil 1368 } 1369 // Now we know we are ignoring a specific index of this map, so get 1370 // the config map and modify, add, or remove the desired key. 1371 1372 // We also need to create a prior map, so we can check for 1373 // existence while getting the value, because Value.Index will 1374 // return null for a key with a null value and for a non-existent 1375 // key. 1376 var priorMap map[string]cty.Value 1377 1378 // We need to drop the marks from the ignored map for handling. We 1379 // don't need to store these, as we now know the ignored value is 1380 // only within the map, not the map itself. 1381 ignoredVal, _ := ignored.value.Unmark() 1382 1383 switch { 1384 case ignored.value.IsNull() || ignoredVal.LengthInt() == 0: 1385 priorMap = map[string]cty.Value{} 1386 default: 1387 priorMap = ignoredVal.AsValueMap() 1388 } 1389 1390 key := ignored.key.AsString() 1391 priorElem, keep := priorMap[key] 1392 1393 switch { 1394 case !keep: 1395 // this didn't exist in the old map value, so we're keeping the 1396 // "absence" of the key by removing it from the config 1397 delete(configMap, key) 1398 default: 1399 configMap[key] = priorElem 1400 } 1401 } 1402 1403 var newVal cty.Value 1404 switch { 1405 case len(configMap) > 0: 1406 newVal = cty.MapVal(configMap) 1407 case v.IsNull(): 1408 // if the config value was null, and no values remain in the map, 1409 // reset the value to null. 1410 newVal = v 1411 default: 1412 newVal = cty.MapValEmpty(v.Type().ElementType()) 1413 } 1414 1415 if len(vMarks) > 0 { 1416 newVal = newVal.WithMarks(vMarks) 1417 } 1418 1419 return newVal, nil 1420 }) 1421 return ret, nil 1422 } 1423 1424 type ProviderWithEncryption interface { 1425 ReadDataSourceEncrypted(req providers.ReadDataSourceRequest, path addrs.AbsResourceInstance, enc encryption.Encryption) providers.ReadDataSourceResponse 1426 } 1427 1428 // readDataSource handles everything needed to call ReadDataSource on the provider. 1429 // A previously evaluated configVal can be passed in, or a new one is generated 1430 // from the resource configuration. 1431 func (n *NodeAbstractResourceInstance) readDataSource(ctx EvalContext, configVal cty.Value) (cty.Value, tfdiags.Diagnostics) { 1432 var diags tfdiags.Diagnostics 1433 var newVal cty.Value 1434 1435 config := *n.Config 1436 1437 provider, providerSchema, err := getProvider(ctx, n.ResolvedProvider) 1438 diags = diags.Append(err) 1439 if diags.HasErrors() { 1440 return newVal, diags 1441 } 1442 schema, _ := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource().Resource) 1443 if schema == nil { 1444 // Should be caught during validation, so we don't bother with a pretty error here 1445 diags = diags.Append(fmt.Errorf("provider %q does not support data source %q", n.ResolvedProvider, n.Addr.ContainingResource().Resource.Type)) 1446 return newVal, diags 1447 } 1448 1449 metaConfigVal, metaDiags := n.providerMetas(ctx) 1450 diags = diags.Append(metaDiags) 1451 if diags.HasErrors() { 1452 return newVal, diags 1453 } 1454 1455 // Unmark before sending to provider, will re-mark before returning 1456 var pvm []cty.PathValueMarks 1457 configVal, pvm = configVal.UnmarkDeepWithPaths() 1458 1459 log.Printf("[TRACE] readDataSource: Re-validating config for %s", n.Addr) 1460 validateResp := provider.ValidateDataResourceConfig( 1461 providers.ValidateDataResourceConfigRequest{ 1462 TypeName: n.Addr.ContainingResource().Resource.Type, 1463 Config: configVal, 1464 }, 1465 ) 1466 diags = diags.Append(validateResp.Diagnostics.InConfigBody(config.Config, n.Addr.String())) 1467 if diags.HasErrors() { 1468 return newVal, diags 1469 } 1470 1471 // If we get down here then our configuration is complete and we're read 1472 // to actually call the provider to read the data. 1473 log.Printf("[TRACE] readDataSource: %s configuration is complete, so reading from provider", n.Addr) 1474 1475 diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) { 1476 return h.PreApply(n.Addr, states.CurrentGen, plans.Read, cty.NullVal(configVal.Type()), configVal) 1477 })) 1478 if diags.HasErrors() { 1479 return newVal, diags 1480 } 1481 1482 req := providers.ReadDataSourceRequest{ 1483 TypeName: n.Addr.ContainingResource().Resource.Type, 1484 Config: configVal, 1485 ProviderMeta: metaConfigVal, 1486 } 1487 var resp providers.ReadDataSourceResponse 1488 if tfp, ok := provider.(ProviderWithEncryption); ok { 1489 // Special case for terraform_remote_state 1490 resp = tfp.ReadDataSourceEncrypted(req, n.Addr, ctx.GetEncryption()) 1491 } else { 1492 resp = provider.ReadDataSource(req) 1493 } 1494 diags = diags.Append(resp.Diagnostics.InConfigBody(config.Config, n.Addr.String())) 1495 if diags.HasErrors() { 1496 return newVal, diags 1497 } 1498 newVal = resp.State 1499 if newVal == cty.NilVal { 1500 // This can happen with incompletely-configured mocks. We'll allow it 1501 // and treat it as an alias for a properly-typed null value. 1502 newVal = cty.NullVal(schema.ImpliedType()) 1503 } 1504 1505 for _, err := range newVal.Type().TestConformance(schema.ImpliedType()) { 1506 diags = diags.Append(tfdiags.Sourceless( 1507 tfdiags.Error, 1508 "Provider produced invalid object", 1509 fmt.Sprintf( 1510 "Provider %q produced an invalid value for %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", 1511 n.ResolvedProvider, tfdiags.FormatErrorPrefixed(err, n.Addr.String()), 1512 ), 1513 )) 1514 } 1515 if diags.HasErrors() { 1516 return newVal, diags 1517 } 1518 1519 if newVal.IsNull() { 1520 diags = diags.Append(tfdiags.Sourceless( 1521 tfdiags.Error, 1522 "Provider produced null object", 1523 fmt.Sprintf( 1524 "Provider %q produced a null value for %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", 1525 n.ResolvedProvider, n.Addr, 1526 ), 1527 )) 1528 } 1529 1530 if !newVal.IsNull() && !newVal.IsWhollyKnown() { 1531 diags = diags.Append(tfdiags.Sourceless( 1532 tfdiags.Error, 1533 "Provider produced invalid object", 1534 fmt.Sprintf( 1535 "Provider %q produced a value for %s that is not wholly known.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", 1536 n.ResolvedProvider, n.Addr, 1537 ), 1538 )) 1539 1540 // We'll still save the object, but we need to eliminate any unknown 1541 // values first because we can't serialize them in the state file. 1542 // Note that this may cause set elements to be coalesced if they 1543 // differed only by having unknown values, but we don't worry about 1544 // that here because we're saving the value only for inspection 1545 // purposes; the error we added above will halt the graph walk. 1546 newVal = cty.UnknownAsNull(newVal) 1547 } 1548 1549 if len(pvm) > 0 { 1550 newVal = newVal.MarkWithPaths(pvm) 1551 } 1552 1553 diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) { 1554 return h.PostApply(n.Addr, states.CurrentGen, newVal, diags.Err()) 1555 })) 1556 1557 return newVal, diags 1558 } 1559 1560 func (n *NodeAbstractResourceInstance) providerMetas(ctx EvalContext) (cty.Value, tfdiags.Diagnostics) { 1561 var diags tfdiags.Diagnostics 1562 metaConfigVal := cty.NullVal(cty.DynamicPseudoType) 1563 1564 _, providerSchema, err := getProvider(ctx, n.ResolvedProvider) 1565 if err != nil { 1566 return metaConfigVal, diags.Append(err) 1567 } 1568 if n.ProviderMetas != nil { 1569 if m, ok := n.ProviderMetas[n.ResolvedProvider.Provider]; ok && m != nil { 1570 // if the provider doesn't support this feature, throw an error 1571 if providerSchema.ProviderMeta.Block == nil { 1572 diags = diags.Append(&hcl.Diagnostic{ 1573 Severity: hcl.DiagError, 1574 Summary: fmt.Sprintf("Provider %s doesn't support provider_meta", n.ResolvedProvider.Provider.String()), 1575 Detail: fmt.Sprintf("The resource %s belongs to a provider that doesn't support provider_meta blocks", n.Addr.Resource), 1576 Subject: &m.ProviderRange, 1577 }) 1578 } else { 1579 var configDiags tfdiags.Diagnostics 1580 metaConfigVal, _, configDiags = ctx.EvaluateBlock(m.Config, providerSchema.ProviderMeta.Block, nil, EvalDataForNoInstanceKey) 1581 diags = diags.Append(configDiags) 1582 } 1583 } 1584 } 1585 return metaConfigVal, diags 1586 } 1587 1588 // planDataSource deals with the main part of the data resource lifecycle: 1589 // either actually reading from the data source or generating a plan to do so. 1590 // 1591 // currentState is the current state for the data source, and the new state is 1592 // returned. While data sources are read-only, we need to start with the prior 1593 // state to determine if we have a change or not. If we needed to read a new 1594 // value, but it still matches the previous state, then we can record a NoNop 1595 // change. If the states don't match then we record a Read change so that the 1596 // new value is applied to the state. 1597 func (n *NodeAbstractResourceInstance) planDataSource(ctx EvalContext, checkRuleSeverity tfdiags.Severity, skipPlanChanges bool) (*plans.ResourceInstanceChange, *states.ResourceInstanceObject, instances.RepetitionData, tfdiags.Diagnostics) { 1598 var diags tfdiags.Diagnostics 1599 var keyData instances.RepetitionData 1600 var configVal cty.Value 1601 1602 _, providerSchema, err := getProvider(ctx, n.ResolvedProvider) 1603 if err != nil { 1604 return nil, nil, keyData, diags.Append(err) 1605 } 1606 1607 config := *n.Config 1608 schema, _ := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource().Resource) 1609 if schema == nil { 1610 // Should be caught during validation, so we don't bother with a pretty error here 1611 diags = diags.Append(fmt.Errorf("provider %q does not support data source %q", n.ResolvedProvider, n.Addr.ContainingResource().Resource.Type)) 1612 return nil, nil, keyData, diags 1613 } 1614 1615 objTy := schema.ImpliedType() 1616 priorVal := cty.NullVal(objTy) 1617 1618 forEach, _ := evaluateForEachExpression(config.ForEach, ctx) 1619 keyData = EvalDataForInstanceKey(n.ResourceInstanceAddr().Resource.Key, forEach) 1620 1621 checkDiags := evalCheckRules( 1622 addrs.ResourcePrecondition, 1623 n.Config.Preconditions, 1624 ctx, n.Addr, keyData, 1625 checkRuleSeverity, 1626 ) 1627 diags = diags.Append(checkDiags) 1628 if diags.HasErrors() { 1629 return nil, nil, keyData, diags // failed preconditions prevent further evaluation 1630 } 1631 1632 var configDiags tfdiags.Diagnostics 1633 configVal, _, configDiags = ctx.EvaluateBlock(config.Config, schema, nil, keyData) 1634 diags = diags.Append(configDiags) 1635 if configDiags.HasErrors() { 1636 return nil, nil, keyData, diags 1637 } 1638 1639 check, nested := n.nestedInCheckBlock() 1640 if nested { 1641 // Going forward from this point, the only reason we will fail is 1642 // that the data source fails to load its data. Normally, this would 1643 // cancel the entire plan and this error message would bubble its way 1644 // back up to the user. 1645 // 1646 // But, if we are in a check block then we don't want this data block to 1647 // cause the plan to fail. We also need to report a status on the data 1648 // block so the check processing later on knows whether to attempt to 1649 // process the checks. Either we'll report the data block as failed 1650 // if/when we load the data block later, or we want to report it as a 1651 // success overall. 1652 // 1653 // Therefore, we create a deferred function here that will check if the 1654 // status for the check has been updated yet, and if not we will set it 1655 // to be StatusPass. The rest of this function will only update the 1656 // status if it should be StatusFail. 1657 defer func() { 1658 status := ctx.Checks().ObjectCheckStatus(check.Addr().Absolute(n.Addr.Module)) 1659 if status == checks.StatusUnknown { 1660 ctx.Checks().ReportCheckResult(check.Addr().Absolute(n.Addr.Module), addrs.CheckDataResource, 0, checks.StatusPass) 1661 } 1662 }() 1663 } 1664 1665 configKnown := configVal.IsWhollyKnown() 1666 depsPending := n.dependenciesHavePendingChanges(ctx) 1667 // If our configuration contains any unknown values, or we depend on any 1668 // unknown values then we must defer the read to the apply phase by 1669 // producing a "Read" change for this resource, and a placeholder value for 1670 // it in the state. 1671 if depsPending || !configKnown { 1672 // We can't plan any changes if we're only refreshing, so the only 1673 // value we can set here is whatever was in state previously. 1674 if skipPlanChanges { 1675 plannedNewState := &states.ResourceInstanceObject{ 1676 Value: priorVal, 1677 Status: states.ObjectReady, 1678 } 1679 1680 return nil, plannedNewState, keyData, diags 1681 } 1682 1683 var reason plans.ResourceInstanceChangeActionReason 1684 switch { 1685 case !configKnown: 1686 log.Printf("[TRACE] planDataSource: %s configuration not fully known yet, so deferring to apply phase", n.Addr) 1687 reason = plans.ResourceInstanceReadBecauseConfigUnknown 1688 case depsPending: 1689 // NOTE: depsPending can be true at the same time as configKnown 1690 // is false; configKnown takes precedence because it's more 1691 // specific. 1692 log.Printf("[TRACE] planDataSource: %s configuration is fully known, at least one dependency has changes pending", n.Addr) 1693 reason = plans.ResourceInstanceReadBecauseDependencyPending 1694 } 1695 1696 unmarkedConfigVal, configMarkPaths := configVal.UnmarkDeepWithPaths() 1697 proposedNewVal := objchange.PlannedDataResourceObject(schema, unmarkedConfigVal) 1698 proposedNewVal = proposedNewVal.MarkWithPaths(configMarkPaths) 1699 1700 // Apply detects that the data source will need to be read by the After 1701 // value containing unknowns from PlanDataResourceObject. 1702 plannedChange := &plans.ResourceInstanceChange{ 1703 Addr: n.Addr, 1704 PrevRunAddr: n.prevRunAddr(ctx), 1705 ProviderAddr: n.ResolvedProvider, 1706 Change: plans.Change{ 1707 Action: plans.Read, 1708 Before: priorVal, 1709 After: proposedNewVal, 1710 }, 1711 ActionReason: reason, 1712 } 1713 1714 plannedNewState := &states.ResourceInstanceObject{ 1715 Value: proposedNewVal, 1716 Status: states.ObjectPlanned, 1717 } 1718 1719 diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) { 1720 return h.PostDiff(n.Addr, states.CurrentGen, plans.Read, priorVal, proposedNewVal) 1721 })) 1722 1723 return plannedChange, plannedNewState, keyData, diags 1724 } 1725 1726 // We have a complete configuration with no dependencies to wait on, so we 1727 // can read the data source into the state. 1728 newVal, readDiags := n.readDataSource(ctx, configVal) 1729 1730 // Now we've loaded the data, and diags tells us whether we were successful 1731 // or not, we are going to create our plannedChange and our 1732 // proposedNewState. 1733 var plannedChange *plans.ResourceInstanceChange 1734 var plannedNewState *states.ResourceInstanceObject 1735 1736 // If we are a nested block, then we want to create a plannedChange that 1737 // tells OpenTofu to reload the data block during the apply stage even if 1738 // we managed to get the data now. 1739 // Another consideration is that if we failed to load the data, we need to 1740 // disguise that for a nested block. Nested blocks will report the overall 1741 // check as failed but won't affect the rest of the plan operation or block 1742 // an apply operation. 1743 1744 if nested { 1745 addr := check.Addr().Absolute(n.Addr.Module) 1746 1747 // Let's fix things up for a nested data block. 1748 // 1749 // A nested data block doesn't error, and creates a planned change. So, 1750 // if we encountered an error we'll tidy up newVal so it makes sense 1751 // and handle the error. We'll also create the plannedChange if 1752 // appropriate. 1753 1754 if readDiags.HasErrors() { 1755 // If we had errors, then we can cover that up by marking the new 1756 // state as unknown. 1757 unmarkedConfigVal, configMarkPaths := configVal.UnmarkDeepWithPaths() 1758 newVal = objchange.PlannedDataResourceObject(schema, unmarkedConfigVal) 1759 newVal = newVal.MarkWithPaths(configMarkPaths) 1760 1761 // We still want to report the check as failed even if we are still 1762 // letting it run again during the apply stage. 1763 ctx.Checks().ReportCheckFailure(addr, addrs.CheckDataResource, 0, readDiags.Err().Error()) 1764 } 1765 1766 // Any warning or error diagnostics we'll wrap with some special checks 1767 // diagnostics. This is so we can identify them later, and so they'll 1768 // only report as warnings. 1769 readDiags = tfdiags.OverrideAll(readDiags, tfdiags.Warning, func() tfdiags.DiagnosticExtraWrapper { 1770 return &addrs.CheckRuleDiagnosticExtra{ 1771 CheckRule: addrs.NewCheckRule(addr, addrs.CheckDataResource, 0), 1772 } 1773 }) 1774 1775 if !skipPlanChanges { 1776 // refreshOnly plans cannot produce planned changes, so we only do 1777 // this if skipPlanChanges is false. 1778 plannedChange = &plans.ResourceInstanceChange{ 1779 Addr: n.Addr, 1780 PrevRunAddr: n.prevRunAddr(ctx), 1781 ProviderAddr: n.ResolvedProvider, 1782 Change: plans.Change{ 1783 Action: plans.Read, 1784 Before: priorVal, 1785 After: newVal, 1786 }, 1787 ActionReason: plans.ResourceInstanceReadBecauseCheckNested, 1788 } 1789 } 1790 } 1791 1792 diags = diags.Append(readDiags) 1793 if !diags.HasErrors() { 1794 // Finally, let's make our new state. 1795 plannedNewState = &states.ResourceInstanceObject{ 1796 Value: newVal, 1797 Status: states.ObjectReady, 1798 } 1799 } 1800 1801 return plannedChange, plannedNewState, keyData, diags 1802 } 1803 1804 // nestedInCheckBlock determines if this resource is nested in a Check config 1805 // block. If so, this resource will be loaded during both plan and apply 1806 // operations to make sure the check is always giving the latest information. 1807 func (n *NodeAbstractResourceInstance) nestedInCheckBlock() (*configs.Check, bool) { 1808 if n.Config.Container != nil { 1809 check, ok := n.Config.Container.(*configs.Check) 1810 return check, ok 1811 } 1812 return nil, false 1813 } 1814 1815 // dependenciesHavePendingChanges determines whether any managed resource the 1816 // receiver depends on has a change pending in the plan, in which case we'd 1817 // need to override the usual behavior of immediately reading from the data 1818 // source where possible, and instead defer the read until the apply step. 1819 func (n *NodeAbstractResourceInstance) dependenciesHavePendingChanges(ctx EvalContext) bool { 1820 nModInst := n.Addr.Module 1821 nMod := nModInst.Module() 1822 1823 // Check and see if any depends_on dependencies have 1824 // changes, since they won't show up as changes in the 1825 // configuration. 1826 changes := ctx.Changes() 1827 1828 depsToUse := n.dependsOn 1829 1830 if n.Addr.Resource.Resource.Mode == addrs.DataResourceMode { 1831 if n.Config.HasCustomConditions() { 1832 // For a data resource with custom conditions we need to look at 1833 // the full set of resource dependencies -- both direct and 1834 // indirect -- because an upstream update might be what's needed 1835 // in order to make a condition pass. 1836 depsToUse = n.Dependencies 1837 } 1838 } 1839 1840 for _, d := range depsToUse { 1841 if d.Resource.Mode == addrs.DataResourceMode { 1842 // Data sources have no external side effects, so they pose a need 1843 // to delay this read. If they do have a change planned, it must be 1844 // because of a dependency on a managed resource, in which case 1845 // we'll also encounter it in this list of dependencies. 1846 continue 1847 } 1848 1849 for _, change := range changes.GetChangesForConfigResource(d) { 1850 changeModInst := change.Addr.Module 1851 changeMod := changeModInst.Module() 1852 1853 if changeMod.Equal(nMod) && !changeModInst.Equal(nModInst) { 1854 // Dependencies are tracked by configuration address, which 1855 // means we may have changes from other instances of parent 1856 // modules. The actual reference can only take effect within 1857 // the same module instance, so skip any that aren't an exact 1858 // match 1859 continue 1860 } 1861 1862 if change != nil && change.Action != plans.NoOp { 1863 return true 1864 } 1865 } 1866 } 1867 return false 1868 } 1869 1870 // apply deals with the main part of the data resource lifecycle: either 1871 // actually reading from the data source or generating a plan to do so. 1872 func (n *NodeAbstractResourceInstance) applyDataSource(ctx EvalContext, planned *plans.ResourceInstanceChange) (*states.ResourceInstanceObject, instances.RepetitionData, tfdiags.Diagnostics) { 1873 var diags tfdiags.Diagnostics 1874 var keyData instances.RepetitionData 1875 1876 _, providerSchema, err := getProvider(ctx, n.ResolvedProvider) 1877 if err != nil { 1878 return nil, keyData, diags.Append(err) 1879 } 1880 if planned != nil && planned.Action != plans.Read && planned.Action != plans.NoOp { 1881 // If any other action gets in here then that's always a bug; this 1882 // EvalNode only deals with reading. 1883 diags = diags.Append(fmt.Errorf( 1884 "invalid action %s for %s: only Read is supported (this is a bug in OpenTofu; please report it!)", 1885 planned.Action, n.Addr, 1886 )) 1887 return nil, keyData, diags 1888 } 1889 1890 config := *n.Config 1891 schema, _ := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource().Resource) 1892 if schema == nil { 1893 // Should be caught during validation, so we don't bother with a pretty error here 1894 diags = diags.Append(fmt.Errorf("provider %q does not support data source %q", n.ResolvedProvider, n.Addr.ContainingResource().Resource.Type)) 1895 return nil, keyData, diags 1896 } 1897 1898 forEach, _ := evaluateForEachExpression(config.ForEach, ctx) 1899 keyData = EvalDataForInstanceKey(n.Addr.Resource.Key, forEach) 1900 1901 checkDiags := evalCheckRules( 1902 addrs.ResourcePrecondition, 1903 n.Config.Preconditions, 1904 ctx, n.Addr, keyData, 1905 tfdiags.Error, 1906 ) 1907 diags = diags.Append(checkDiags) 1908 if diags.HasErrors() { 1909 diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) { 1910 return h.PostApply(n.Addr, states.CurrentGen, planned.Before, diags.Err()) 1911 })) 1912 return nil, keyData, diags // failed preconditions prevent further evaluation 1913 } 1914 1915 if planned.Action == plans.NoOp { 1916 // If we didn't actually plan to read this then we have nothing more 1917 // to do; we're evaluating this only for incidentals like the 1918 // precondition/postcondition checks. 1919 return nil, keyData, diags 1920 } 1921 1922 configVal, _, configDiags := ctx.EvaluateBlock(config.Config, schema, nil, keyData) 1923 diags = diags.Append(configDiags) 1924 if configDiags.HasErrors() { 1925 return nil, keyData, diags 1926 } 1927 1928 newVal, readDiags := n.readDataSource(ctx, configVal) 1929 if check, nested := n.nestedInCheckBlock(); nested { 1930 addr := check.Addr().Absolute(n.Addr.Module) 1931 1932 // We're just going to jump in here and hide away any errors for nested 1933 // data blocks. 1934 if readDiags.HasErrors() { 1935 ctx.Checks().ReportCheckFailure(addr, addrs.CheckDataResource, 0, readDiags.Err().Error()) 1936 diags = diags.Append(tfdiags.OverrideAll(readDiags, tfdiags.Warning, func() tfdiags.DiagnosticExtraWrapper { 1937 return &addrs.CheckRuleDiagnosticExtra{ 1938 CheckRule: addrs.NewCheckRule(addr, addrs.CheckDataResource, 0), 1939 } 1940 })) 1941 return nil, keyData, diags 1942 } 1943 1944 // Even though we know there are no errors here, we still want to 1945 // identify these diags has having been generated from a check block. 1946 readDiags = tfdiags.OverrideAll(readDiags, tfdiags.Warning, func() tfdiags.DiagnosticExtraWrapper { 1947 return &addrs.CheckRuleDiagnosticExtra{ 1948 CheckRule: addrs.NewCheckRule(addr, addrs.CheckDataResource, 0), 1949 } 1950 }) 1951 1952 // If no errors, just remember to report this as a success and continue 1953 // as normal. 1954 ctx.Checks().ReportCheckResult(addr, addrs.CheckDataResource, 0, checks.StatusPass) 1955 } 1956 1957 diags = diags.Append(readDiags) 1958 if readDiags.HasErrors() { 1959 return nil, keyData, diags 1960 } 1961 1962 state := &states.ResourceInstanceObject{ 1963 Value: newVal, 1964 Status: states.ObjectReady, 1965 } 1966 1967 return state, keyData, diags 1968 } 1969 1970 // evalApplyProvisioners determines if provisioners need to be run, and if so 1971 // executes the provisioners for a resource and returns an updated error if 1972 // provisioning fails. 1973 func (n *NodeAbstractResourceInstance) evalApplyProvisioners(ctx EvalContext, state *states.ResourceInstanceObject, createNew bool, when configs.ProvisionerWhen) tfdiags.Diagnostics { 1974 var diags tfdiags.Diagnostics 1975 1976 if state == nil { 1977 log.Printf("[TRACE] evalApplyProvisioners: %s has no state, so skipping provisioners", n.Addr) 1978 return nil 1979 } 1980 if when == configs.ProvisionerWhenCreate && !createNew { 1981 // If we're not creating a new resource, then don't run provisioners 1982 log.Printf("[TRACE] evalApplyProvisioners: %s is not freshly-created, so no provisioning is required", n.Addr) 1983 return nil 1984 } 1985 if state.Status == states.ObjectTainted { 1986 // No point in provisioning an object that is already tainted, since 1987 // it's going to get recreated on the next apply anyway. 1988 log.Printf("[TRACE] evalApplyProvisioners: %s is tainted, so skipping provisioning", n.Addr) 1989 return nil 1990 } 1991 1992 provs := filterProvisioners(n.Config, when) 1993 if len(provs) == 0 { 1994 // We have no provisioners, so don't do anything 1995 return nil 1996 } 1997 1998 // Call pre hook 1999 diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) { 2000 return h.PreProvisionInstance(n.Addr, state.Value) 2001 })) 2002 if diags.HasErrors() { 2003 return diags 2004 } 2005 2006 // If there are no errors, then we append it to our output error 2007 // if we have one, otherwise we just output it. 2008 diags = diags.Append(n.applyProvisioners(ctx, state, when, provs)) 2009 if diags.HasErrors() { 2010 log.Printf("[TRACE] evalApplyProvisioners: %s provisioning failed, but we will continue anyway at the caller's request", n.Addr) 2011 return diags 2012 } 2013 2014 // Call post hook 2015 return diags.Append(ctx.Hook(func(h Hook) (HookAction, error) { 2016 return h.PostProvisionInstance(n.Addr, state.Value) 2017 })) 2018 } 2019 2020 // filterProvisioners filters the provisioners on the resource to only 2021 // the provisioners specified by the "when" option. 2022 func filterProvisioners(config *configs.Resource, when configs.ProvisionerWhen) []*configs.Provisioner { 2023 // Fast path the zero case 2024 if config == nil || config.Managed == nil { 2025 return nil 2026 } 2027 2028 if len(config.Managed.Provisioners) == 0 { 2029 return nil 2030 } 2031 2032 result := make([]*configs.Provisioner, 0, len(config.Managed.Provisioners)) 2033 for _, p := range config.Managed.Provisioners { 2034 if p.When == when { 2035 result = append(result, p) 2036 } 2037 } 2038 2039 return result 2040 } 2041 2042 // applyProvisioners executes the provisioners for a resource. 2043 func (n *NodeAbstractResourceInstance) applyProvisioners(ctx EvalContext, state *states.ResourceInstanceObject, when configs.ProvisionerWhen, provs []*configs.Provisioner) tfdiags.Diagnostics { 2044 var diags tfdiags.Diagnostics 2045 2046 // this self is only used for destroy provisioner evaluation, and must 2047 // refer to the last known value of the resource. 2048 self := state.Value 2049 2050 var evalScope func(EvalContext, hcl.Body, cty.Value, *configschema.Block) (cty.Value, tfdiags.Diagnostics) 2051 switch when { 2052 case configs.ProvisionerWhenDestroy: 2053 evalScope = n.evalDestroyProvisionerConfig 2054 default: 2055 evalScope = n.evalProvisionerConfig 2056 } 2057 2058 // If there's a connection block defined directly inside the resource block 2059 // then it'll serve as a base connection configuration for all of the 2060 // provisioners. 2061 var baseConn hcl.Body 2062 if n.Config.Managed != nil && n.Config.Managed.Connection != nil { 2063 baseConn = n.Config.Managed.Connection.Config 2064 } 2065 2066 for _, prov := range provs { 2067 log.Printf("[TRACE] applyProvisioners: provisioning %s with %q", n.Addr, prov.Type) 2068 2069 // Get the provisioner 2070 provisioner, err := ctx.Provisioner(prov.Type) 2071 if err != nil { 2072 return diags.Append(err) 2073 } 2074 2075 schema, err := ctx.ProvisionerSchema(prov.Type) 2076 if err != nil { 2077 // This error probably won't be a great diagnostic, but in practice 2078 // we typically catch this problem long before we get here, so 2079 // it should be rare to return via this codepath. 2080 diags = diags.Append(err) 2081 return diags 2082 } 2083 2084 config, configDiags := evalScope(ctx, prov.Config, self, schema) 2085 diags = diags.Append(configDiags) 2086 if diags.HasErrors() { 2087 return diags 2088 } 2089 2090 // If the provisioner block contains a connection block of its own then 2091 // it can override the base connection configuration, if any. 2092 var localConn hcl.Body 2093 if prov.Connection != nil { 2094 localConn = prov.Connection.Config 2095 } 2096 2097 var connBody hcl.Body 2098 switch { 2099 case baseConn != nil && localConn != nil: 2100 // Our standard merging logic applies here, similar to what we do 2101 // with _override.tf configuration files: arguments from the 2102 // base connection block will be masked by any arguments of the 2103 // same name in the local connection block. 2104 connBody = configs.MergeBodies(baseConn, localConn) 2105 case baseConn != nil: 2106 connBody = baseConn 2107 case localConn != nil: 2108 connBody = localConn 2109 } 2110 2111 // start with an empty connInfo 2112 connInfo := cty.NullVal(connectionBlockSupersetSchema.ImpliedType()) 2113 2114 if connBody != nil { 2115 var connInfoDiags tfdiags.Diagnostics 2116 connInfo, connInfoDiags = evalScope(ctx, connBody, self, connectionBlockSupersetSchema) 2117 diags = diags.Append(connInfoDiags) 2118 if diags.HasErrors() { 2119 return diags 2120 } 2121 } 2122 2123 { 2124 // Call pre hook 2125 err := ctx.Hook(func(h Hook) (HookAction, error) { 2126 return h.PreProvisionInstanceStep(n.Addr, prov.Type) 2127 }) 2128 if err != nil { 2129 return diags.Append(err) 2130 } 2131 } 2132 2133 // The output function 2134 outputFn := func(msg string) { 2135 ctx.Hook(func(h Hook) (HookAction, error) { 2136 h.ProvisionOutput(n.Addr, prov.Type, msg) 2137 return HookActionContinue, nil 2138 }) 2139 } 2140 2141 // If our config or connection info contains any marked values, ensure 2142 // those are stripped out before sending to the provisioner. Unlike 2143 // resources, we have no need to capture the marked paths and reapply 2144 // later. 2145 unmarkedConfig, configMarks := config.UnmarkDeep() 2146 unmarkedConnInfo, _ := connInfo.UnmarkDeep() 2147 2148 // Marks on the config might result in leaking sensitive values through 2149 // provisioner logging, so we conservatively suppress all output in 2150 // this case. This should not apply to connection info values, which 2151 // provisioners ought not to be logging anyway. 2152 if len(configMarks) > 0 { 2153 outputFn = func(msg string) { 2154 ctx.Hook(func(h Hook) (HookAction, error) { 2155 h.ProvisionOutput(n.Addr, prov.Type, "(output suppressed due to sensitive value in config)") 2156 return HookActionContinue, nil 2157 }) 2158 } 2159 } 2160 2161 output := CallbackUIOutput{OutputFn: outputFn} 2162 resp := provisioner.ProvisionResource(provisioners.ProvisionResourceRequest{ 2163 Config: unmarkedConfig, 2164 Connection: unmarkedConnInfo, 2165 UIOutput: &output, 2166 }) 2167 applyDiags := resp.Diagnostics.InConfigBody(prov.Config, n.Addr.String()) 2168 2169 // Call post hook 2170 hookErr := ctx.Hook(func(h Hook) (HookAction, error) { 2171 return h.PostProvisionInstanceStep(n.Addr, prov.Type, applyDiags.Err()) 2172 }) 2173 2174 switch prov.OnFailure { 2175 case configs.ProvisionerOnFailureContinue: 2176 if applyDiags.HasErrors() { 2177 log.Printf("[WARN] Errors while provisioning %s with %q, but continuing as requested in configuration", n.Addr, prov.Type) 2178 } else { 2179 // Maybe there are warnings that we still want to see 2180 diags = diags.Append(applyDiags) 2181 } 2182 default: 2183 diags = diags.Append(applyDiags) 2184 if applyDiags.HasErrors() { 2185 log.Printf("[WARN] Errors while provisioning %s with %q, so aborting", n.Addr, prov.Type) 2186 return diags 2187 } 2188 } 2189 2190 // Deal with the hook 2191 if hookErr != nil { 2192 return diags.Append(hookErr) 2193 } 2194 } 2195 2196 return diags 2197 } 2198 2199 func (n *NodeAbstractResourceInstance) evalProvisionerConfig(ctx EvalContext, body hcl.Body, self cty.Value, schema *configschema.Block) (cty.Value, tfdiags.Diagnostics) { 2200 var diags tfdiags.Diagnostics 2201 2202 forEach, forEachDiags := evaluateForEachExpression(n.Config.ForEach, ctx) 2203 diags = diags.Append(forEachDiags) 2204 2205 keyData := EvalDataForInstanceKey(n.ResourceInstanceAddr().Resource.Key, forEach) 2206 2207 config, _, configDiags := ctx.EvaluateBlock(body, schema, n.ResourceInstanceAddr().Resource, keyData) 2208 diags = diags.Append(configDiags) 2209 2210 return config, diags 2211 } 2212 2213 // during destroy a provisioner can only evaluate within the scope of the parent resource 2214 func (n *NodeAbstractResourceInstance) evalDestroyProvisionerConfig(ctx EvalContext, body hcl.Body, self cty.Value, schema *configschema.Block) (cty.Value, tfdiags.Diagnostics) { 2215 var diags tfdiags.Diagnostics 2216 2217 // For a destroy-time provisioner forEach is intentionally nil here, 2218 // which EvalDataForInstanceKey responds to by not populating EachValue 2219 // in its result. That's okay because each.value is prohibited for 2220 // destroy-time provisioners. 2221 keyData := EvalDataForInstanceKey(n.ResourceInstanceAddr().Resource.Key, nil) 2222 2223 evalScope := ctx.EvaluationScope(n.ResourceInstanceAddr().Resource, nil, keyData) 2224 config, evalDiags := evalScope.EvalSelfBlock(body, self, schema, keyData) 2225 diags = diags.Append(evalDiags) 2226 2227 return config, diags 2228 } 2229 2230 // apply accepts an applyConfig, instead of using n.Config, so destroy plans can 2231 // send a nil config. The keyData information can be empty if the config is 2232 // nil, since it is only used to evaluate the configuration. 2233 func (n *NodeAbstractResourceInstance) apply( 2234 ctx EvalContext, 2235 state *states.ResourceInstanceObject, 2236 change *plans.ResourceInstanceChange, 2237 applyConfig *configs.Resource, 2238 keyData instances.RepetitionData, 2239 createBeforeDestroy bool) (*states.ResourceInstanceObject, tfdiags.Diagnostics) { 2240 2241 var diags tfdiags.Diagnostics 2242 if state == nil { 2243 state = &states.ResourceInstanceObject{} 2244 } 2245 2246 if change.Action == plans.NoOp { 2247 // If this is a no-op change then we don't want to actually change 2248 // anything, so we'll just echo back the state we were given and 2249 // let our internal checks and updates proceed. 2250 log.Printf("[TRACE] NodeAbstractResourceInstance.apply: skipping %s because it has no planned action", n.Addr) 2251 return state, diags 2252 } 2253 2254 provider, providerSchema, err := getProvider(ctx, n.ResolvedProvider) 2255 if err != nil { 2256 return nil, diags.Append(err) 2257 } 2258 schema, _ := providerSchema.SchemaForResourceType(n.Addr.Resource.Resource.Mode, n.Addr.Resource.Resource.Type) 2259 if schema == nil { 2260 // Should be caught during validation, so we don't bother with a pretty error here 2261 diags = diags.Append(fmt.Errorf("provider does not support resource type %q", n.Addr.Resource.Resource.Type)) 2262 return nil, diags 2263 } 2264 2265 log.Printf("[INFO] Starting apply for %s", n.Addr) 2266 2267 configVal := cty.NullVal(cty.DynamicPseudoType) 2268 if applyConfig != nil { 2269 var configDiags tfdiags.Diagnostics 2270 configVal, _, configDiags = ctx.EvaluateBlock(applyConfig.Config, schema, nil, keyData) 2271 diags = diags.Append(configDiags) 2272 if configDiags.HasErrors() { 2273 return nil, diags 2274 } 2275 } 2276 2277 if !configVal.IsWhollyKnown() { 2278 // We don't have a pretty format function for a path, but since this is 2279 // such a rare error, we can just drop the raw GoString values in here 2280 // to make sure we have something to debug with. 2281 var unknownPaths []string 2282 cty.Transform(configVal, func(p cty.Path, v cty.Value) (cty.Value, error) { 2283 if !v.IsKnown() { 2284 unknownPaths = append(unknownPaths, fmt.Sprintf("%#v", p)) 2285 } 2286 return v, nil 2287 }) 2288 2289 diags = diags.Append(tfdiags.Sourceless( 2290 tfdiags.Error, 2291 "Configuration contains unknown value", 2292 fmt.Sprintf("configuration for %s still contains unknown values during apply (this is a bug in OpenTofu; please report it!)\n"+ 2293 "The following paths in the resource configuration are unknown:\n%s", 2294 n.Addr, 2295 strings.Join(unknownPaths, "\n"), 2296 ), 2297 )) 2298 return nil, diags 2299 } 2300 2301 metaConfigVal, metaDiags := n.providerMetas(ctx) 2302 diags = diags.Append(metaDiags) 2303 if diags.HasErrors() { 2304 return nil, diags 2305 } 2306 2307 log.Printf("[DEBUG] %s: applying the planned %s change", n.Addr, change.Action) 2308 2309 // If our config, Before or After value contain any marked values, 2310 // ensure those are stripped out before sending 2311 // this to the provider 2312 unmarkedConfigVal, _ := configVal.UnmarkDeep() 2313 unmarkedBefore, beforePaths := change.Before.UnmarkDeepWithPaths() 2314 unmarkedAfter, afterPaths := change.After.UnmarkDeepWithPaths() 2315 2316 // If we have an Update action, our before and after values are equal, 2317 // and only differ on their sensitivity, the newVal is the after val 2318 // and we should not communicate with the provider. We do need to update 2319 // the state with this new value, to ensure the sensitivity change is 2320 // persisted. 2321 eqV := unmarkedBefore.Equals(unmarkedAfter) 2322 eq := eqV.IsKnown() && eqV.True() 2323 if change.Action == plans.Update && eq && !marksEqual(beforePaths, afterPaths) { 2324 // Copy the previous state, changing only the value 2325 newState := &states.ResourceInstanceObject{ 2326 CreateBeforeDestroy: state.CreateBeforeDestroy, 2327 Dependencies: state.Dependencies, 2328 Private: state.Private, 2329 Status: state.Status, 2330 Value: change.After, 2331 } 2332 return newState, diags 2333 } 2334 2335 resp := provider.ApplyResourceChange(providers.ApplyResourceChangeRequest{ 2336 TypeName: n.Addr.Resource.Resource.Type, 2337 PriorState: unmarkedBefore, 2338 Config: unmarkedConfigVal, 2339 PlannedState: unmarkedAfter, 2340 PlannedPrivate: change.Private, 2341 ProviderMeta: metaConfigVal, 2342 }) 2343 applyDiags := resp.Diagnostics 2344 if applyConfig != nil { 2345 applyDiags = applyDiags.InConfigBody(applyConfig.Config, n.Addr.String()) 2346 } 2347 diags = diags.Append(applyDiags) 2348 2349 // Even if there are errors in the returned diagnostics, the provider may 2350 // have returned a _partial_ state for an object that already exists but 2351 // failed to fully configure, and so the remaining code must always run 2352 // to completion but must be defensive against the new value being 2353 // incomplete. 2354 newVal := resp.NewState 2355 2356 // If we have paths to mark, mark those on this new value 2357 if len(afterPaths) > 0 { 2358 newVal = newVal.MarkWithPaths(afterPaths) 2359 } 2360 2361 if newVal == cty.NilVal { 2362 // Providers are supposed to return a partial new value even when errors 2363 // occur, but sometimes they don't and so in that case we'll patch that up 2364 // by just using the prior state, so we'll at least keep track of the 2365 // object for the user to retry. 2366 newVal = change.Before 2367 2368 // As a special case, we'll set the new value to null if it looks like 2369 // we were trying to execute a delete, because the provider in this case 2370 // probably left the newVal unset intending it to be interpreted as "null". 2371 if change.After.IsNull() { 2372 newVal = cty.NullVal(schema.ImpliedType()) 2373 } 2374 2375 if !diags.HasErrors() { 2376 diags = diags.Append(tfdiags.Sourceless( 2377 tfdiags.Error, 2378 "Provider produced invalid object", 2379 fmt.Sprintf( 2380 "Provider %q produced an invalid nil value after apply for %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", 2381 n.ResolvedProvider.String(), n.Addr.String(), 2382 ), 2383 )) 2384 } 2385 } 2386 2387 var conformDiags tfdiags.Diagnostics 2388 for _, err := range newVal.Type().TestConformance(schema.ImpliedType()) { 2389 conformDiags = conformDiags.Append(tfdiags.Sourceless( 2390 tfdiags.Error, 2391 "Provider produced invalid object", 2392 fmt.Sprintf( 2393 "Provider %q produced an invalid value after apply for %s. The result cannot not be saved in the OpenTofu state.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", 2394 n.ResolvedProvider.String(), tfdiags.FormatErrorPrefixed(err, n.Addr.String()), 2395 ), 2396 )) 2397 } 2398 diags = diags.Append(conformDiags) 2399 if conformDiags.HasErrors() { 2400 // Bail early in this particular case, because an object that doesn't 2401 // conform to the schema can't be saved in the state anyway -- the 2402 // serializer will reject it. 2403 return nil, diags 2404 } 2405 2406 // After this point we have a type-conforming result object and so we 2407 // must always run to completion to ensure it can be saved. If n.Error 2408 // is set then we must not return a non-nil error, in order to allow 2409 // evaluation to continue to a later point where our state object will 2410 // be saved. 2411 2412 // By this point there must not be any unknown values remaining in our 2413 // object, because we've applied the change and we can't save unknowns 2414 // in our persistent state. If any are present then we will indicate an 2415 // error (which is always a bug in the provider) but we will also replace 2416 // them with nulls so that we can successfully save the portions of the 2417 // returned value that are known. 2418 if !newVal.IsWhollyKnown() { 2419 // To generate better error messages, we'll go for a walk through the 2420 // value and make a separate diagnostic for each unknown value we 2421 // find. 2422 cty.Walk(newVal, func(path cty.Path, val cty.Value) (bool, error) { 2423 if !val.IsKnown() { 2424 pathStr := tfdiags.FormatCtyPath(path) 2425 diags = diags.Append(tfdiags.Sourceless( 2426 tfdiags.Error, 2427 "Provider returned invalid result object after apply", 2428 fmt.Sprintf( 2429 "After the apply operation, the provider still indicated an unknown value for %s%s. All values must be known after apply, so this is always a bug in the provider and should be reported in the provider's own repository. OpenTofu will still save the other known object values in the state.", 2430 n.Addr, pathStr, 2431 ), 2432 )) 2433 } 2434 return true, nil 2435 }) 2436 2437 // NOTE: This operation can potentially be lossy if there are multiple 2438 // elements in a set that differ only by unknown values: after 2439 // replacing with null these will be merged together into a single set 2440 // element. Since we can only get here in the presence of a provider 2441 // bug, we accept this because storing a result here is always a 2442 // best-effort sort of thing. 2443 newVal = cty.UnknownAsNull(newVal) 2444 } 2445 2446 if change.Action != plans.Delete && !diags.HasErrors() { 2447 // Only values that were marked as unknown in the planned value are allowed 2448 // to change during the apply operation. (We do this after the unknown-ness 2449 // check above so that we also catch anything that became unknown after 2450 // being known during plan.) 2451 // 2452 // If we are returning other errors anyway then we'll give this 2453 // a pass since the other errors are usually the explanation for 2454 // this one and so it's more helpful to let the user focus on the 2455 // root cause rather than distract with this extra problem. 2456 if errs := objchange.AssertObjectCompatible(schema, change.After, newVal); len(errs) > 0 { 2457 if resp.LegacyTypeSystem { 2458 // The shimming of the old type system in the legacy SDK is not precise 2459 // enough to pass this consistency check, so we'll give it a pass here, 2460 // but we will generate a warning about it so that we are more likely 2461 // to notice in the logs if an inconsistency beyond the type system 2462 // leads to a downstream provider failure. 2463 var buf strings.Builder 2464 fmt.Fprintf(&buf, "[WARN] Provider %q produced an unexpected new value for %s, but we are tolerating it because it is using the legacy plugin SDK.\n The following problems may be the cause of any confusing errors from downstream operations:", n.ResolvedProvider.String(), n.Addr) 2465 for _, err := range errs { 2466 fmt.Fprintf(&buf, "\n - %s", tfdiags.FormatError(err)) 2467 } 2468 log.Print(buf.String()) 2469 2470 // The sort of inconsistency we won't catch here is if a known value 2471 // in the plan is changed during apply. That can cause downstream 2472 // problems because a dependent resource would make its own plan based 2473 // on the planned value, and thus get a different result during the 2474 // apply phase. This will usually lead to a "Provider produced invalid plan" 2475 // error that incorrectly blames the downstream resource for the change. 2476 2477 } else { 2478 for _, err := range errs { 2479 diags = diags.Append(tfdiags.Sourceless( 2480 tfdiags.Error, 2481 "Provider produced inconsistent result after apply", 2482 fmt.Sprintf( 2483 "When applying changes to %s, provider %q produced an unexpected new value: %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", 2484 n.Addr, n.ResolvedProvider.String(), tfdiags.FormatError(err), 2485 ), 2486 )) 2487 } 2488 } 2489 } 2490 } 2491 2492 // If a provider returns a null or non-null object at the wrong time then 2493 // we still want to save that but it often causes some confusing behaviors 2494 // where it seems like OpenTofu is failing to take any action at all, 2495 // so we'll generate some errors to draw attention to it. 2496 if !diags.HasErrors() { 2497 if change.Action == plans.Delete && !newVal.IsNull() { 2498 diags = diags.Append(tfdiags.Sourceless( 2499 tfdiags.Error, 2500 "Provider returned invalid result object after apply", 2501 fmt.Sprintf( 2502 "After applying a %s plan, the provider returned a non-null object for %s. Destroying should always produce a null value, so this is always a bug in the provider and should be reported in the provider's own repository. OpenTofu will still save this errant object in the state for debugging and recovery.", 2503 change.Action, n.Addr, 2504 ), 2505 )) 2506 } 2507 if change.Action != plans.Delete && newVal.IsNull() { 2508 diags = diags.Append(tfdiags.Sourceless( 2509 tfdiags.Error, 2510 "Provider returned invalid result object after apply", 2511 fmt.Sprintf( 2512 "After applying a %s plan, the provider returned a null object for %s. Only destroying should always produce a null value, so this is always a bug in the provider and should be reported in the provider's own repository.", 2513 change.Action, n.Addr, 2514 ), 2515 )) 2516 } 2517 } 2518 2519 switch { 2520 case diags.HasErrors() && newVal.IsNull(): 2521 // Sometimes providers return a null value when an operation fails for 2522 // some reason, but we'd rather keep the prior state so that the error 2523 // can be corrected on a subsequent run. We must only do this for null 2524 // new value though, or else we may discard partial updates the 2525 // provider was able to complete. Otherwise, we'll continue using the 2526 // prior state as the new value, making this effectively a no-op. If 2527 // the item really _has_ been deleted then our next refresh will detect 2528 // that and fix it up. 2529 return state.DeepCopy(), diags 2530 2531 case diags.HasErrors() && !newVal.IsNull(): 2532 // if we have an error, make sure we restore the object status in the new state 2533 newState := &states.ResourceInstanceObject{ 2534 Status: state.Status, 2535 Value: newVal, 2536 Private: resp.Private, 2537 CreateBeforeDestroy: createBeforeDestroy, 2538 } 2539 2540 // if the resource was being deleted, the dependencies are not going to 2541 // be recalculated and we need to restore those as well. 2542 if change.Action == plans.Delete { 2543 newState.Dependencies = state.Dependencies 2544 } 2545 2546 return newState, diags 2547 2548 case !newVal.IsNull(): 2549 // Non error case with a new state 2550 newState := &states.ResourceInstanceObject{ 2551 Status: states.ObjectReady, 2552 Value: newVal, 2553 Private: resp.Private, 2554 CreateBeforeDestroy: createBeforeDestroy, 2555 } 2556 return newState, diags 2557 2558 default: 2559 // Non error case, were the object was deleted 2560 return nil, diags 2561 } 2562 } 2563 2564 func (n *NodeAbstractResourceInstance) prevRunAddr(ctx EvalContext) addrs.AbsResourceInstance { 2565 return resourceInstancePrevRunAddr(ctx, n.Addr) 2566 } 2567 2568 func resourceInstancePrevRunAddr(ctx EvalContext, currentAddr addrs.AbsResourceInstance) addrs.AbsResourceInstance { 2569 table := ctx.MoveResults() 2570 return table.OldAddr(currentAddr) 2571 }