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