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