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