core: Split Replace changes into separate Delete/Create changes
Since we do our deletes using a separate graph node from all of the other actions, and a "Replace" change implies both a delete _and_ a create, we need to pretend at apply time that a single replace change was actually two separate changes. This will also early-exit eval if a destroy node finds a non-Delete change or if an apply node finds a Delete change. These should not happen in practice because we leave these nodes out of the graph when they are not needed for the given action, but we do this here for robustness so as not to have an invisible dependency between the graph builder and the eval phase.
This commit is contained in:
parent
1cc9d00da6
commit
6fd82ef97e
|
@ -121,6 +121,86 @@ func (rc *ResourceInstanceChange) Encode(ty cty.Type) (*ResourceInstanceChangeSr
|
|||
}, err
|
||||
}
|
||||
|
||||
// Simplify will, where possible, produce a change with a simpler action than
|
||||
// the receiever given a flag indicating whether the caller is dealing with
|
||||
// a normal apply or a destroy. This flag deals with the fact that Terraform
|
||||
// Core uses a specialized graph node type for destroying; only that
|
||||
// specialized node should set "destroying" to true.
|
||||
//
|
||||
// The following table shows the simplification behavior:
|
||||
//
|
||||
// Action Destroying? New Action
|
||||
// --------+-------------+-----------
|
||||
// Create true NoOp
|
||||
// Delete false NoOp
|
||||
// Replace true Delete
|
||||
// Replace false Create
|
||||
//
|
||||
// For any combination not in the above table, the Simplify just returns the
|
||||
// receiver as-is.
|
||||
func (rc *ResourceInstanceChange) Simplify(destroying bool) *ResourceInstanceChange {
|
||||
if destroying {
|
||||
switch rc.Action {
|
||||
case Delete:
|
||||
// We'll fall out and just return rc verbatim, then.
|
||||
case Replace:
|
||||
return &ResourceInstanceChange{
|
||||
Addr: rc.Addr,
|
||||
DeposedKey: rc.DeposedKey,
|
||||
Private: rc.Private,
|
||||
ProviderAddr: rc.ProviderAddr,
|
||||
Change: Change{
|
||||
Action: Delete,
|
||||
Before: rc.Before,
|
||||
After: cty.NullVal(rc.Before.Type()),
|
||||
},
|
||||
}
|
||||
default:
|
||||
return &ResourceInstanceChange{
|
||||
Addr: rc.Addr,
|
||||
DeposedKey: rc.DeposedKey,
|
||||
Private: rc.Private,
|
||||
ProviderAddr: rc.ProviderAddr,
|
||||
Change: Change{
|
||||
Action: NoOp,
|
||||
Before: rc.Before,
|
||||
After: rc.Before,
|
||||
},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch rc.Action {
|
||||
case Delete:
|
||||
return &ResourceInstanceChange{
|
||||
Addr: rc.Addr,
|
||||
DeposedKey: rc.DeposedKey,
|
||||
Private: rc.Private,
|
||||
ProviderAddr: rc.ProviderAddr,
|
||||
Change: Change{
|
||||
Action: NoOp,
|
||||
Before: rc.Before,
|
||||
After: rc.Before,
|
||||
},
|
||||
}
|
||||
case Replace:
|
||||
return &ResourceInstanceChange{
|
||||
Addr: rc.Addr,
|
||||
DeposedKey: rc.DeposedKey,
|
||||
Private: rc.Private,
|
||||
ProviderAddr: rc.ProviderAddr,
|
||||
Change: Change{
|
||||
Action: Create,
|
||||
Before: cty.NullVal(rc.After.Type()),
|
||||
After: rc.After,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we fall out here then our change is already simple enough.
|
||||
return rc
|
||||
}
|
||||
|
||||
// OutputChange describes a change to an output value.
|
||||
type OutputChange struct {
|
||||
// Change is an embedded description of the change.
|
||||
|
|
|
@ -722,6 +722,43 @@ func (n *EvalDiffDestroyModule) Eval(ctx EvalContext) (interface{}, error) {
|
|||
*/
|
||||
}
|
||||
|
||||
// EvalReduceDiff is an EvalNode implementation that takes a planned resource
|
||||
// instance change as might be produced by EvalDiff or EvalDiffDestroy and
|
||||
// "simplifies" it to a single atomic action to be performed by a specific
|
||||
// graph node.
|
||||
//
|
||||
// Callers must specify whether they are a destroy node or a regular apply
|
||||
// node. If the result is NoOp then the given change requires no action for
|
||||
// the specific graph node calling this and so evaluation of the that graph
|
||||
// node should exit early and take no action.
|
||||
//
|
||||
// The object written to OutChange may either be identical to InChange or
|
||||
// a new change object derived from InChange. Because of the former case, the
|
||||
// caller must not mutate the object returned in OutChange.
|
||||
type EvalReduceDiff struct {
|
||||
Addr addrs.ResourceInstance
|
||||
InChange **plans.ResourceInstanceChange
|
||||
Destroy bool
|
||||
OutChange **plans.ResourceInstanceChange
|
||||
}
|
||||
|
||||
// TODO: test
|
||||
func (n *EvalReduceDiff) Eval(ctx EvalContext) (interface{}, error) {
|
||||
in := *n.InChange
|
||||
out := in.Simplify(n.Destroy)
|
||||
if n.OutChange != nil {
|
||||
*n.OutChange = out
|
||||
}
|
||||
if out.Action != in.Action {
|
||||
if n.Destroy {
|
||||
log.Printf("[TRACE] EvalReduceDiff: %s change simplified from %s to %s for destroy node", n.Addr, in.Action, out.Action)
|
||||
} else {
|
||||
log.Printf("[TRACE] EvalReduceDiff: %s change simplified from %s to %s for apply node", n.Addr, in.Action, out.Action)
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// EvalReadDiff is an EvalNode implementation that retrieves the planned
|
||||
// change for a particular resource instance object.
|
||||
type EvalReadDiff struct {
|
||||
|
|
|
@ -298,6 +298,26 @@ func (n *NodeApplyableResourceInstance) evalTreeManagedResource(addr addrs.AbsRe
|
|||
Output: &state,
|
||||
},
|
||||
|
||||
&EvalReduceDiff{
|
||||
Addr: addr.Resource,
|
||||
InChange: &diffApply,
|
||||
Destroy: false,
|
||||
OutChange: &diffApply,
|
||||
},
|
||||
|
||||
// EvalReduceDiff may have simplified our planned change
|
||||
// into a NoOp if it only requires destroying, since destroying
|
||||
// is handled by NodeDestroyResourceInstance.
|
||||
&EvalIf{
|
||||
If: func(ctx EvalContext) (bool, error) {
|
||||
if diffApply == nil || diffApply.Action == plans.NoOp {
|
||||
return true, EvalEarlyExitError{}
|
||||
}
|
||||
return true, nil
|
||||
},
|
||||
Then: EvalNoop{},
|
||||
},
|
||||
|
||||
// Call pre-apply hook
|
||||
&EvalApplyPre{
|
||||
Addr: addr.Resource,
|
||||
|
|
|
@ -189,16 +189,21 @@ func (n *NodeDestroyResourceInstance) EvalTree() EvalNode {
|
|||
Change: &changeApply,
|
||||
},
|
||||
|
||||
// If we're not destroying, then compare diffs
|
||||
&EvalReduceDiff{
|
||||
Addr: addr.Resource,
|
||||
InChange: &changeApply,
|
||||
Destroy: true,
|
||||
OutChange: &changeApply,
|
||||
},
|
||||
|
||||
// EvalReduceDiff may have simplified our planned change
|
||||
// into a NoOp if it does not require destroying.
|
||||
&EvalIf{
|
||||
If: func(ctx EvalContext) (bool, error) {
|
||||
if changeApply != nil {
|
||||
if changeApply.Action == plans.Delete || changeApply.Action == plans.Replace {
|
||||
return true, nil
|
||||
}
|
||||
if changeApply == nil || changeApply.Action == plans.NoOp {
|
||||
return true, EvalEarlyExitError{}
|
||||
}
|
||||
|
||||
return true, EvalEarlyExitError{}
|
||||
return true, nil
|
||||
},
|
||||
Then: EvalNoop{},
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue