10 "github.com/hashicorp/hcl2/hcl"
11 "github.com/zclconf/go-cty/cty"
13 "github.com/hashicorp/terraform/addrs"
14 "github.com/hashicorp/terraform/configs"
15 "github.com/hashicorp/terraform/plans"
16 "github.com/hashicorp/terraform/plans/objchange"
17 "github.com/hashicorp/terraform/providers"
18 "github.com/hashicorp/terraform/states"
19 "github.com/hashicorp/terraform/tfdiags"
22 // EvalCheckPlannedChange is an EvalNode implementation that produces errors
23 // if the _actual_ expected value is not compatible with what was recorded
26 // Errors here are most often indicative of a bug in the provider, so our
27 // error messages will report with that in mind. It's also possible that
28 // there's a bug in Terraform's Core's own "proposed new value" code in
30 type EvalCheckPlannedChange struct {
31 Addr addrs.ResourceInstance
32 ProviderAddr addrs.AbsProviderConfig
33 ProviderSchema **ProviderSchema
35 // We take ResourceInstanceChange objects here just because that's what's
36 // convenient to pass in from the evaltree implementation, but we really
37 // only look at the "After" value of each change.
38 Planned, Actual **plans.ResourceInstanceChange
41 func (n *EvalCheckPlannedChange) Eval(ctx EvalContext) (interface{}, error) {
42 providerSchema := *n.ProviderSchema
43 plannedChange := *n.Planned
44 actualChange := *n.Actual
46 schema, _ := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource())
48 // Should be caught during validation, so we don't bother with a pretty error here
49 return nil, fmt.Errorf("provider does not support %q", n.Addr.Resource.Type)
52 var diags tfdiags.Diagnostics
53 absAddr := n.Addr.Absolute(ctx.Path())
55 log.Printf("[TRACE] EvalCheckPlannedChange: Verifying that actual change (action %s) matches planned change (action %s)", actualChange.Action, plannedChange.Action)
57 if plannedChange.Action != actualChange.Action {
59 case plannedChange.Action == plans.Update && actualChange.Action == plans.NoOp:
60 // It's okay for an update to become a NoOp once we've filled in
61 // all of the unknown values, since the final values might actually
62 // match what was there before after all.
63 log.Printf("[DEBUG] After incorporating new values learned so far during apply, %s change has become NoOp", absAddr)
65 diags = diags.Append(tfdiags.Sourceless(
67 "Provider produced inconsistent final plan",
69 "When expanding the plan for %s to include new values learned so far during apply, provider %q changed the planned action from %s to %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.",
70 absAddr, n.ProviderAddr.ProviderConfig.Type,
71 plannedChange.Action, actualChange.Action,
77 errs := objchange.AssertObjectCompatible(schema, plannedChange.After, actualChange.After)
78 for _, err := range errs {
79 diags = diags.Append(tfdiags.Sourceless(
81 "Provider produced inconsistent final plan",
83 "When expanding the plan for %s to include new values learned so far during apply, provider %q produced an invalid new value for %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.",
84 absAddr, n.ProviderAddr.ProviderConfig.Type, tfdiags.FormatError(err),
88 return nil, diags.Err()
91 // EvalDiff is an EvalNode implementation that detects changes for a given
93 type EvalDiff struct {
94 Addr addrs.ResourceInstance
95 Config *configs.Resource
96 Provider *providers.Interface
97 ProviderAddr addrs.AbsProviderConfig
98 ProviderSchema **ProviderSchema
99 State **states.ResourceInstanceObject
100 PreviousDiff **plans.ResourceInstanceChange
102 // CreateBeforeDestroy is set if either the resource's own config sets
103 // create_before_destroy explicitly or if dependencies have forced the
104 // resource to be handled as create_before_destroy in order to avoid
105 // a dependency cycle.
106 CreateBeforeDestroy bool
108 OutputChange **plans.ResourceInstanceChange
109 OutputValue *cty.Value
110 OutputState **states.ResourceInstanceObject
116 func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) {
119 provider := *n.Provider
120 providerSchema := *n.ProviderSchema
122 if providerSchema == nil {
123 return nil, fmt.Errorf("provider schema is unavailable for %s", n.Addr)
125 if n.ProviderAddr.ProviderConfig.Type == "" {
126 panic(fmt.Sprintf("EvalDiff for %s does not have ProviderAddr set", n.Addr.Absolute(ctx.Path())))
129 var diags tfdiags.Diagnostics
131 // Evaluate the configuration
132 schema, _ := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource())
134 // Should be caught during validation, so we don't bother with a pretty error here
135 return nil, fmt.Errorf("provider does not support resource type %q", n.Addr.Resource.Type)
137 keyData := EvalDataForInstanceKey(n.Addr.Key)
138 configVal, _, configDiags := ctx.EvaluateBlock(config.Config, schema, nil, keyData)
139 diags = diags.Append(configDiags)
140 if configDiags.HasErrors() {
141 return nil, diags.Err()
144 absAddr := n.Addr.Absolute(ctx.Path())
145 var priorVal cty.Value
146 var priorValTainted cty.Value
147 var priorPrivate []byte
149 if state.Status != states.ObjectTainted {
150 priorVal = state.Value
151 priorPrivate = state.Private
153 // If the prior state is tainted then we'll proceed below like
154 // we're creating an entirely new object, but then turn it into
155 // a synthetic "Replace" change at the end, creating the same
156 // result as if the provider had marked at least one argument
157 // change as "requires replacement".
158 priorValTainted = state.Value
159 priorVal = cty.NullVal(schema.ImpliedType())
162 priorVal = cty.NullVal(schema.ImpliedType())
165 proposedNewVal := objchange.ProposedNewObject(schema, priorVal, configVal)
167 // Call pre-diff hook
169 err := ctx.Hook(func(h Hook) (HookAction, error) {
170 return h.PreDiff(absAddr, states.CurrentGen, priorVal, proposedNewVal)
177 // The provider gets an opportunity to customize the proposed new value,
178 // which in turn produces the _planned_ new value.
179 resp := provider.PlanResourceChange(providers.PlanResourceChangeRequest{
180 TypeName: n.Addr.Resource.Type,
182 PriorState: priorVal,
183 ProposedNewState: proposedNewVal,
184 PriorPrivate: priorPrivate,
186 diags = diags.Append(resp.Diagnostics.InConfigBody(config.Config))
187 if diags.HasErrors() {
188 return nil, diags.Err()
191 plannedNewVal := resp.PlannedState
192 plannedPrivate := resp.PlannedPrivate
194 if plannedNewVal == cty.NilVal {
195 // Should never happen. Since real-world providers return via RPC a nil
196 // is always a bug in the client-side stub. This is more likely caused
197 // by an incompletely-configured mock provider in tests, though.
198 panic(fmt.Sprintf("PlanResourceChange of %s produced nil value", absAddr.String()))
201 // We allow the planned new value to disagree with configuration _values_
202 // here, since that allows the provider to do special logic like a
203 // DiffSuppressFunc, but we still require that the provider produces
204 // a value whose type conforms to the schema.
205 for _, err := range plannedNewVal.Type().TestConformance(schema.ImpliedType()) {
206 diags = diags.Append(tfdiags.Sourceless(
208 "Provider produced invalid plan",
210 "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.",
211 n.ProviderAddr.ProviderConfig.Type, tfdiags.FormatErrorPrefixed(err, absAddr.String()),
215 if diags.HasErrors() {
216 return nil, diags.Err()
219 if errs := objchange.AssertPlanValid(schema, priorVal, configVal, plannedNewVal); len(errs) > 0 {
220 if resp.LegacyTypeSystem {
221 // The shimming of the old type system in the legacy SDK is not precise
222 // enough to pass this consistency check, so we'll give it a pass here,
223 // but we will generate a warning about it so that we are more likely
224 // to notice in the logs if an inconsistency beyond the type system
225 // leads to a downstream provider failure.
226 var buf strings.Builder
227 fmt.Fprintf(&buf, "[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:", n.ProviderAddr.ProviderConfig.Type, absAddr)
228 for _, err := range errs {
229 fmt.Fprintf(&buf, "\n - %s", tfdiags.FormatError(err))
231 log.Print(buf.String())
233 for _, err := range errs {
234 diags = diags.Append(tfdiags.Sourceless(
236 "Provider produced invalid plan",
238 "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.",
239 n.ProviderAddr.ProviderConfig.Type, tfdiags.FormatErrorPrefixed(err, absAddr.String()),
243 return nil, diags.Err()
248 var moreDiags tfdiags.Diagnostics
249 plannedNewVal, moreDiags = n.processIgnoreChanges(priorVal, plannedNewVal)
250 diags = diags.Append(moreDiags)
251 if moreDiags.HasErrors() {
252 return nil, diags.Err()
256 // The provider produces a list of paths to attributes whose changes mean
257 // that we must replace rather than update an existing remote object.
258 // However, we only need to do that if the identified attributes _have_
259 // actually changed -- particularly after we may have undone some of the
260 // changes in processIgnoreChanges -- so now we'll filter that list to
261 // include only where changes are detected.
262 reqRep := cty.NewPathSet()
263 if len(resp.RequiresReplace) > 0 {
264 for _, path := range resp.RequiresReplace {
265 if priorVal.IsNull() {
266 // If prior is null then we don't expect any RequiresReplace at all,
267 // because this is a Create action.
271 priorChangedVal, priorPathDiags := hcl.ApplyPath(priorVal, path, nil)
272 plannedChangedVal, plannedPathDiags := hcl.ApplyPath(plannedNewVal, path, nil)
273 if plannedPathDiags.HasErrors() && priorPathDiags.HasErrors() {
274 // This means the path was invalid in both the prior and new
275 // values, which is an error with the provider itself.
276 diags = diags.Append(tfdiags.Sourceless(
278 "Provider produced invalid plan",
280 "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.",
281 n.ProviderAddr.ProviderConfig.Type, absAddr, path,
287 // Make sure we have valid Values for both values.
288 // Note: if the opposing value was of the type
289 // cty.DynamicPseudoType, the type assigned here may not exactly
290 // match the schema. This is fine here, since we're only going to
291 // check for equality, but if the NullVal is to be used, we need to
292 // check the schema for th true type.
294 case priorChangedVal == cty.NilVal && plannedChangedVal == cty.NilVal:
295 // this should never happen without ApplyPath errors above
296 panic("requires replace path returned 2 nil values")
297 case priorChangedVal == cty.NilVal:
298 priorChangedVal = cty.NullVal(plannedChangedVal.Type())
299 case plannedChangedVal == cty.NilVal:
300 plannedChangedVal = cty.NullVal(priorChangedVal.Type())
303 eqV := plannedChangedVal.Equals(priorChangedVal)
304 if !eqV.IsKnown() || eqV.False() {
308 if diags.HasErrors() {
309 return nil, diags.Err()
313 eqV := plannedNewVal.Equals(priorVal)
314 eq := eqV.IsKnown() && eqV.True()
316 var action plans.Action
318 case priorVal.IsNull():
319 action = plans.Create
322 case !reqRep.Empty():
323 // If there are any "requires replace" paths left _after our filtering
324 // above_ then this is a replace action.
325 if n.CreateBeforeDestroy {
326 action = plans.CreateThenDelete
328 action = plans.DeleteThenCreate
331 action = plans.Update
332 // "Delete" is never chosen here, because deletion plans are always
333 // created more directly elsewhere, such as in "orphan" handling.
336 if action.IsReplace() {
337 // In this strange situation we want to produce a change object that
338 // shows our real prior object but has a _new_ object that is built
339 // from a null prior object, since we're going to delete the one
340 // that has all the computed values on it.
342 // Therefore we'll ask the provider to plan again here, giving it
343 // a null object for the prior, and then we'll meld that with the
344 // _actual_ prior state to produce a correctly-shaped replace change.
345 // The resulting change should show any computed attributes changing
346 // from known prior values to unknown values, unless the provider is
347 // able to predict new values for any of these computed attributes.
348 nullPriorVal := cty.NullVal(schema.ImpliedType())
350 // create a new proposed value from the null state and the config
351 proposedNewVal = objchange.ProposedNewObject(schema, nullPriorVal, configVal)
353 resp = provider.PlanResourceChange(providers.PlanResourceChangeRequest{
354 TypeName: n.Addr.Resource.Type,
356 PriorState: nullPriorVal,
357 ProposedNewState: proposedNewVal,
358 PriorPrivate: plannedPrivate,
360 // We need to tread carefully here, since if there are any warnings
361 // in here they probably also came out of our previous call to
362 // PlanResourceChange above, and so we don't want to repeat them.
363 // Consequently, we break from the usual pattern here and only
364 // append these new diagnostics if there's at least one error inside.
365 if resp.Diagnostics.HasErrors() {
366 diags = diags.Append(resp.Diagnostics.InConfigBody(config.Config))
367 return nil, diags.Err()
369 plannedNewVal = resp.PlannedState
370 plannedPrivate = resp.PlannedPrivate
371 for _, err := range plannedNewVal.Type().TestConformance(schema.ImpliedType()) {
372 diags = diags.Append(tfdiags.Sourceless(
374 "Provider produced invalid plan",
376 "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.",
377 n.ProviderAddr.ProviderConfig.Type, absAddr, tfdiags.FormatError(err),
381 if diags.HasErrors() {
382 return nil, diags.Err()
386 // If our prior value was tainted then we actually want this to appear
387 // as a replace change, even though so far we've been treating it as a
389 if action == plans.Create && priorValTainted != cty.NilVal {
390 if n.CreateBeforeDestroy {
391 action = plans.CreateThenDelete
393 action = plans.DeleteThenCreate
395 priorVal = priorValTainted
398 // As a special case, if we have a previous diff (presumably from the plan
399 // phases, whereas we're now in the apply phase) and it was for a replace,
400 // we've already deleted the original object from state by the time we
401 // get here and so we would've ended up with a _create_ action this time,
402 // which we now need to paper over to get a result consistent with what
403 // we originally intended.
404 if n.PreviousDiff != nil {
405 prevChange := *n.PreviousDiff
406 if prevChange.Action.IsReplace() && action == plans.Create {
407 log.Printf("[TRACE] EvalDiff: %s treating Create change as %s change to match with earlier plan", absAddr, prevChange.Action)
408 action = prevChange.Action
409 priorVal = prevChange.Before
413 // Call post-refresh hook
415 err := ctx.Hook(func(h Hook) (HookAction, error) {
416 return h.PostDiff(absAddr, states.CurrentGen, action, priorVal, plannedNewVal)
423 // Update our output if we care
424 if n.OutputChange != nil {
425 *n.OutputChange = &plans.ResourceInstanceChange{
427 Private: plannedPrivate,
428 ProviderAddr: n.ProviderAddr,
429 Change: plans.Change{
432 After: plannedNewVal,
434 RequiredReplace: reqRep,
438 if n.OutputValue != nil {
439 *n.OutputValue = configVal
442 // Update the state if we care
443 if n.OutputState != nil {
444 *n.OutputState = &states.ResourceInstanceObject{
445 // We use the special "planned" status here to note that this
446 // object's value is not yet complete. Objects with this status
447 // cannot be used during expression evaluation, so the caller
448 // must _also_ record the returned change in the active plan,
449 // which the expression evaluator will use in preference to this
450 // incomplete value recorded in the state.
451 Status: states.ObjectPlanned,
452 Value: plannedNewVal,
459 func (n *EvalDiff) processIgnoreChanges(prior, proposed cty.Value) (cty.Value, tfdiags.Diagnostics) {
460 // ignore_changes only applies when an object already exists, since we
461 // can't ignore changes to a thing we've not created yet.
466 ignoreChanges := n.Config.Managed.IgnoreChanges
467 ignoreAll := n.Config.Managed.IgnoreAllChanges
469 if len(ignoreChanges) == 0 && !ignoreAll {
475 if prior.IsNull() || proposed.IsNull() {
476 // Ignore changes doesn't apply when we're creating for the first time.
477 // Proposed should never be null here, but if it is then we'll just let it be.
481 return processIgnoreChangesIndividual(prior, proposed, ignoreChanges)
484 func processIgnoreChangesIndividual(prior, proposed cty.Value, ignoreChanges []hcl.Traversal) (cty.Value, tfdiags.Diagnostics) {
485 // When we walk below we will be using cty.Path values for comparison, so
486 // we'll convert our traversals here so we can compare more easily.
487 ignoreChangesPath := make([]cty.Path, len(ignoreChanges))
488 for i, traversal := range ignoreChanges {
489 path := make(cty.Path, len(traversal))
490 for si, step := range traversal {
491 switch ts := step.(type) {
492 case hcl.TraverseRoot:
493 path[si] = cty.GetAttrStep{
496 case hcl.TraverseAttr:
497 path[si] = cty.GetAttrStep{
500 case hcl.TraverseIndex:
501 path[si] = cty.IndexStep{
505 panic(fmt.Sprintf("unsupported traversal step %#v", step))
508 ignoreChangesPath[i] = path
511 var diags tfdiags.Diagnostics
512 ret, _ := cty.Transform(proposed, func(path cty.Path, v cty.Value) (cty.Value, error) {
513 // First we must see if this is a path that's being ignored at all.
514 // We're looking for an exact match here because this walk will visit
515 // leaf values first and then their containers, and we want to do
516 // the "ignore" transform once we reach the point indicated, throwing
517 // away any deeper values we already produced at that point.
518 var ignoreTraversal hcl.Traversal
519 for i, candidate := range ignoreChangesPath {
520 if reflect.DeepEqual(path, candidate) {
521 ignoreTraversal = ignoreChanges[i]
524 if ignoreTraversal == nil {
528 // If we're able to follow the same path through the prior value,
529 // we'll take the value there instead, effectively undoing the
530 // change that was planned.
531 priorV, diags := hcl.ApplyPath(prior, path, nil)
532 if diags.HasErrors() {
533 // We just ignore the errors and move on here, since we assume it's
534 // just because the prior value was a slightly-different shape.
535 // It could potentially also be that the traversal doesn't match
536 // the schema, but we should've caught that during the validate
545 func (n *EvalDiff) processIgnoreChangesOld(diff *InstanceDiff) error {
546 if diff == nil || n.Config == nil || n.Config.Managed == nil {
549 ignoreChanges := n.Config.Managed.IgnoreChanges
550 ignoreAll := n.Config.Managed.IgnoreAllChanges
552 if len(ignoreChanges) == 0 && !ignoreAll {
556 // If we're just creating the resource, we shouldn't alter the
558 if diff.ChangeType() == DiffCreate {
562 // If the resource has been tainted then we don't process ignore changes
563 // since we MUST recreate the entire resource.
564 if diff.GetDestroyTainted() {
568 attrs := diff.CopyAttributes()
570 // get the complete set of keys we want to ignore
571 ignorableAttrKeys := make(map[string]bool)
572 for k := range attrs {
574 ignorableAttrKeys[k] = true
577 for _, ignoredTraversal := range ignoreChanges {
578 ignoredKey := legacyFlatmapKeyForTraversal(ignoredTraversal)
579 if k == ignoredKey || strings.HasPrefix(k, ignoredKey+".") {
580 ignorableAttrKeys[k] = true
585 // If the resource was being destroyed, check to see if we can ignore the
586 // reason for it being destroyed.
587 if diff.GetDestroy() {
588 for k, v := range attrs {
590 // id will always be changed if we intended to replace this instance
593 if v.Empty() || v.NewComputed {
597 // If any RequiresNew attribute isn't ignored, we need to keep the diff
598 // as-is to be able to replace the resource.
599 if v.RequiresNew && !ignorableAttrKeys[k] {
604 // Now that we know that we aren't replacing the instance, we can filter
605 // out all the empty and computed attributes. There may be a bunch of
606 // extraneous attribute diffs for the other non-requires-new attributes
607 // going from "" -> "configval" or "" -> "<computed>".
608 // We must make sure any flatmapped containers are filterred (or not) as a
610 containers := groupContainers(diff)
611 keep := map[string]bool{}
612 for _, v := range containers {
613 if v.keepDiff(ignorableAttrKeys) {
614 // At least one key has changes, so list all the sibling keys
615 // to keep in the diff
618 // this key may have been added by the user to ignore, but
619 // if it's a subkey in a container, we need to un-ignore it
620 // to keep the complete containter.
621 delete(ignorableAttrKeys, k)
626 for k, v := range attrs {
627 if (v.Empty() || v.NewComputed) && !keep[k] {
628 ignorableAttrKeys[k] = true
633 // Here we undo the two reactions to RequireNew in EvalDiff - the "id"
634 // attribute diff and the Destroy boolean field
635 log.Printf("[DEBUG] Removing 'id' diff and setting Destroy to false " +
636 "because after ignore_changes, this diff no longer requires replacement")
637 diff.DelAttribute("id")
638 diff.SetDestroy(false)
640 // If we didn't hit any of our early exit conditions, we can filter the diff.
641 for k := range ignorableAttrKeys {
642 log.Printf("[DEBUG] [EvalIgnoreChanges] %s: Ignoring diff attribute: %s", n.Addr.String(), k)
649 // legacyFlagmapKeyForTraversal constructs a key string compatible with what
650 // the flatmap package would generate for an attribute addressable by the given
653 // This is used only to shim references to attributes within the diff and
654 // state structures, which have not (at the time of writing) yet been updated
655 // to use the newer HCL-based representations.
656 func legacyFlatmapKeyForTraversal(traversal hcl.Traversal) string {
659 for _, step := range traversal {
663 switch ts := step.(type) {
664 case hcl.TraverseRoot:
665 buf.WriteString(ts.Name)
666 case hcl.TraverseAttr:
667 buf.WriteString(ts.Name)
668 case hcl.TraverseIndex:
672 bf := val.AsBigFloat()
673 buf.WriteString(bf.String())
678 // should never happen, since no other types appear in
679 // traversals in practice.
683 // should never happen, since we've covered all of the types
684 // that show up in parsed traversals in practice.
692 // a group of key-*ResourceAttrDiff pairs from the same flatmapped container
693 type flatAttrDiff map[string]*ResourceAttrDiff
695 // we need to keep all keys if any of them have a diff that's not ignored
696 func (f flatAttrDiff) keepDiff(ignoreChanges map[string]bool) bool {
697 for k, v := range f {
699 for attr := range ignoreChanges {
700 if strings.HasPrefix(k, attr) {
705 if !v.Empty() && !v.NewComputed && !ignore {
712 // sets, lists and maps need to be compared for diff inclusion as a whole, so
713 // group the flatmapped keys together for easier comparison.
714 func groupContainers(d *InstanceDiff) map[string]flatAttrDiff {
715 isIndex := multiVal.MatchString
716 containers := map[string]flatAttrDiff{}
717 attrs := d.CopyAttributes()
718 // we need to loop once to find the index key
719 for k := range attrs {
721 // add the key, always including the final dot to fully qualify it
722 containers[k[:len(k)-1]] = flatAttrDiff{}
726 // loop again to find all the sub keys
727 for prefix, values := range containers {
728 for k, attrDiff := range attrs {
729 // we include the index value as well, since it could be part of the diff
730 if strings.HasPrefix(k, prefix) {
739 // EvalDiffDestroy is an EvalNode implementation that returns a plain
741 type EvalDiffDestroy struct {
742 Addr addrs.ResourceInstance
743 DeposedKey states.DeposedKey
744 State **states.ResourceInstanceObject
745 ProviderAddr addrs.AbsProviderConfig
747 Output **plans.ResourceInstanceChange
748 OutputState **states.ResourceInstanceObject
752 func (n *EvalDiffDestroy) Eval(ctx EvalContext) (interface{}, error) {
753 absAddr := n.Addr.Absolute(ctx.Path())
756 if n.ProviderAddr.ProviderConfig.Type == "" {
757 if n.DeposedKey == "" {
758 panic(fmt.Sprintf("EvalDiffDestroy for %s does not have ProviderAddr set", absAddr))
760 panic(fmt.Sprintf("EvalDiffDestroy for %s (deposed %s) does not have ProviderAddr set", absAddr, n.DeposedKey))
764 // If there is no state or our attributes object is null then we're already
766 if state == nil || state.Value.IsNull() {
770 // Call pre-diff hook
771 err := ctx.Hook(func(h Hook) (HookAction, error) {
773 absAddr, n.DeposedKey.Generation(),
775 cty.NullVal(cty.DynamicPseudoType),
782 // Change is always the same for a destroy. We don't need the provider's
783 // help for this one.
784 // TODO: Should we give the provider an opportunity to veto this?
785 change := &plans.ResourceInstanceChange{
787 DeposedKey: n.DeposedKey,
788 Change: plans.Change{
789 Action: plans.Delete,
791 After: cty.NullVal(cty.DynamicPseudoType),
793 ProviderAddr: n.ProviderAddr,
796 // Call post-diff hook
797 err = ctx.Hook(func(h Hook) (HookAction, error) {
800 n.DeposedKey.Generation(),
813 if n.OutputState != nil {
814 // Record our proposed new state, which is nil because we're destroying.
821 // EvalReduceDiff is an EvalNode implementation that takes a planned resource
822 // instance change as might be produced by EvalDiff or EvalDiffDestroy and
823 // "simplifies" it to a single atomic action to be performed by a specific
826 // Callers must specify whether they are a destroy node or a regular apply
827 // node. If the result is NoOp then the given change requires no action for
828 // the specific graph node calling this and so evaluation of the that graph
829 // node should exit early and take no action.
831 // The object written to OutChange may either be identical to InChange or
832 // a new change object derived from InChange. Because of the former case, the
833 // caller must not mutate the object returned in OutChange.
834 type EvalReduceDiff struct {
835 Addr addrs.ResourceInstance
836 InChange **plans.ResourceInstanceChange
838 OutChange **plans.ResourceInstanceChange
842 func (n *EvalReduceDiff) Eval(ctx EvalContext) (interface{}, error) {
844 out := in.Simplify(n.Destroy)
845 if n.OutChange != nil {
848 if out.Action != in.Action {
850 log.Printf("[TRACE] EvalReduceDiff: %s change simplified from %s to %s for destroy node", n.Addr, in.Action, out.Action)
852 log.Printf("[TRACE] EvalReduceDiff: %s change simplified from %s to %s for apply node", n.Addr, in.Action, out.Action)
858 // EvalReadDiff is an EvalNode implementation that retrieves the planned
859 // change for a particular resource instance object.
860 type EvalReadDiff struct {
861 Addr addrs.ResourceInstance
862 DeposedKey states.DeposedKey
863 ProviderSchema **ProviderSchema
864 Change **plans.ResourceInstanceChange
867 func (n *EvalReadDiff) Eval(ctx EvalContext) (interface{}, error) {
868 providerSchema := *n.ProviderSchema
869 changes := ctx.Changes()
870 addr := n.Addr.Absolute(ctx.Path())
872 schema, _ := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource())
874 // Should be caught during validation, so we don't bother with a pretty error here
875 return nil, fmt.Errorf("provider does not support resource type %q", n.Addr.Resource.Type)
878 gen := states.CurrentGen
879 if n.DeposedKey != states.NotDeposed {
882 csrc := changes.GetResourceInstanceChange(addr, gen)
884 log.Printf("[TRACE] EvalReadDiff: No planned change recorded for %s", addr)
888 change, err := csrc.Decode(schema.ImpliedType())
890 return nil, fmt.Errorf("failed to decode planned changes for %s: %s", addr, err)
896 log.Printf("[TRACE] EvalReadDiff: Read %s change from plan for %s", change.Action, addr)
901 // EvalWriteDiff is an EvalNode implementation that saves a planned change
902 // for an instance object into the set of global planned changes.
903 type EvalWriteDiff struct {
904 Addr addrs.ResourceInstance
905 DeposedKey states.DeposedKey
906 ProviderSchema **ProviderSchema
907 Change **plans.ResourceInstanceChange
911 func (n *EvalWriteDiff) Eval(ctx EvalContext) (interface{}, error) {
912 changes := ctx.Changes()
913 addr := n.Addr.Absolute(ctx.Path())
914 if n.Change == nil || *n.Change == nil {
915 // Caller sets nil to indicate that we need to remove a change from
916 // the set of changes.
917 gen := states.CurrentGen
918 if n.DeposedKey != states.NotDeposed {
921 changes.RemoveResourceInstanceChange(addr, gen)
925 providerSchema := *n.ProviderSchema
928 if change.Addr.String() != addr.String() || change.DeposedKey != n.DeposedKey {
929 // Should never happen, and indicates a bug in the caller.
930 panic("inconsistent address and/or deposed key in EvalWriteDiff")
933 schema, _ := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource())
935 // Should be caught during validation, so we don't bother with a pretty error here
936 return nil, fmt.Errorf("provider does not support resource type %q", n.Addr.Resource.Type)
939 csrc, err := change.Encode(schema.ImpliedType())
941 return nil, fmt.Errorf("failed to encode planned changes for %s: %s", addr, err)
944 changes.AppendResourceInstanceChange(csrc)
945 if n.DeposedKey == states.NotDeposed {
946 log.Printf("[TRACE] EvalWriteDiff: recorded %s change for %s", change.Action, addr)
948 log.Printf("[TRACE] EvalWriteDiff: recorded %s change for %s deposed object %s", change.Action, addr, n.DeposedKey)