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