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