terraform/terraform/node_resource_destroy_depos...

256 lines
9.9 KiB
Go
Raw Normal View History

core: Be more explicit in how we handle create_before_destroy Previously our handling of create_before_destroy -- and of deposed objects in particular -- was rather "implicit" and spread over various different subsystems. We'd quietly just destroy every deposed object during a destroy operation, without any user-visible plan to do so. Here we make things more explicit by tracking each deposed object individually by its pseudorandomly-allocated key. There are two different mechanisms at play here, building on the same concepts: - During a replace operation with create_before_destroy, we *pre-allocate* a DeposedKey to use for the prior object in the "apply" node and then pass that exact id to the destroy node, ensuring that we only destroy the single object we planned to destroy. In the happy path here the user never actually sees the allocated deposed key because we use it and then immediately destroy it within the same operation. However, that destroy may fail, which brings us to the second mechanism: - If any deposed objects are already present in state during _plan_, we insert a destroy change for them into the plan so that it's explicit to the user that we are going to destroy these additional objects, and then create an individual graph node for each one in DiffTransformer. The main motivation here is to be more careful in how we handle these destroys so that from a user's standpoint we never destroy something without the user knowing about it ahead of time. However, this new organization also hopefully makes the code itself a little easier to follow because the connection between the create and destroy steps of a Replace is reprseented in a single place (in DiffTransformer) and deposed instances each have their own explicit graph node rather than being secretly handled as part of the main instance-level graph node.
2018-09-20 21:30:52 +02:00
package terraform
import (
"fmt"
"log"
core: Be more explicit in how we handle create_before_destroy Previously our handling of create_before_destroy -- and of deposed objects in particular -- was rather "implicit" and spread over various different subsystems. We'd quietly just destroy every deposed object during a destroy operation, without any user-visible plan to do so. Here we make things more explicit by tracking each deposed object individually by its pseudorandomly-allocated key. There are two different mechanisms at play here, building on the same concepts: - During a replace operation with create_before_destroy, we *pre-allocate* a DeposedKey to use for the prior object in the "apply" node and then pass that exact id to the destroy node, ensuring that we only destroy the single object we planned to destroy. In the happy path here the user never actually sees the allocated deposed key because we use it and then immediately destroy it within the same operation. However, that destroy may fail, which brings us to the second mechanism: - If any deposed objects are already present in state during _plan_, we insert a destroy change for them into the plan so that it's explicit to the user that we are going to destroy these additional objects, and then create an individual graph node for each one in DiffTransformer. The main motivation here is to be more careful in how we handle these destroys so that from a user's standpoint we never destroy something without the user knowing about it ahead of time. However, this new organization also hopefully makes the code itself a little easier to follow because the connection between the create and destroy steps of a Replace is reprseented in a single place (in DiffTransformer) and deposed instances each have their own explicit graph node rather than being secretly handled as part of the main instance-level graph node.
2018-09-20 21:30:52 +02:00
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/dag"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/states"
2020-10-27 23:16:28 +01:00
"github.com/hashicorp/terraform/tfdiags"
core: Be more explicit in how we handle create_before_destroy Previously our handling of create_before_destroy -- and of deposed objects in particular -- was rather "implicit" and spread over various different subsystems. We'd quietly just destroy every deposed object during a destroy operation, without any user-visible plan to do so. Here we make things more explicit by tracking each deposed object individually by its pseudorandomly-allocated key. There are two different mechanisms at play here, building on the same concepts: - During a replace operation with create_before_destroy, we *pre-allocate* a DeposedKey to use for the prior object in the "apply" node and then pass that exact id to the destroy node, ensuring that we only destroy the single object we planned to destroy. In the happy path here the user never actually sees the allocated deposed key because we use it and then immediately destroy it within the same operation. However, that destroy may fail, which brings us to the second mechanism: - If any deposed objects are already present in state during _plan_, we insert a destroy change for them into the plan so that it's explicit to the user that we are going to destroy these additional objects, and then create an individual graph node for each one in DiffTransformer. The main motivation here is to be more careful in how we handle these destroys so that from a user's standpoint we never destroy something without the user knowing about it ahead of time. However, this new organization also hopefully makes the code itself a little easier to follow because the connection between the create and destroy steps of a Replace is reprseented in a single place (in DiffTransformer) and deposed instances each have their own explicit graph node rather than being secretly handled as part of the main instance-level graph node.
2018-09-20 21:30:52 +02:00
)
// ConcreteResourceInstanceDeposedNodeFunc is a callback type used to convert
// an abstract resource instance to a concrete one of some type that has
// an associated deposed object key.
type ConcreteResourceInstanceDeposedNodeFunc func(*NodeAbstractResourceInstance, states.DeposedKey) dag.Vertex
type GraphNodeDeposedResourceInstanceObject interface {
DeposedInstanceObjectKey() states.DeposedKey
}
core: Be more explicit in how we handle create_before_destroy Previously our handling of create_before_destroy -- and of deposed objects in particular -- was rather "implicit" and spread over various different subsystems. We'd quietly just destroy every deposed object during a destroy operation, without any user-visible plan to do so. Here we make things more explicit by tracking each deposed object individually by its pseudorandomly-allocated key. There are two different mechanisms at play here, building on the same concepts: - During a replace operation with create_before_destroy, we *pre-allocate* a DeposedKey to use for the prior object in the "apply" node and then pass that exact id to the destroy node, ensuring that we only destroy the single object we planned to destroy. In the happy path here the user never actually sees the allocated deposed key because we use it and then immediately destroy it within the same operation. However, that destroy may fail, which brings us to the second mechanism: - If any deposed objects are already present in state during _plan_, we insert a destroy change for them into the plan so that it's explicit to the user that we are going to destroy these additional objects, and then create an individual graph node for each one in DiffTransformer. The main motivation here is to be more careful in how we handle these destroys so that from a user's standpoint we never destroy something without the user knowing about it ahead of time. However, this new organization also hopefully makes the code itself a little easier to follow because the connection between the create and destroy steps of a Replace is reprseented in a single place (in DiffTransformer) and deposed instances each have their own explicit graph node rather than being secretly handled as part of the main instance-level graph node.
2018-09-20 21:30:52 +02:00
// NodePlanDeposedResourceInstanceObject represents deposed resource
// instance objects during plan. These are distinct from the primary object
// for each resource instance since the only valid operation to do with them
// is to destroy them.
//
// This node type is also used during the refresh walk to ensure that the
// record of a deposed object is up-to-date before we plan to destroy it.
type NodePlanDeposedResourceInstanceObject struct {
*NodeAbstractResourceInstance
DeposedKey states.DeposedKey
}
var (
_ GraphNodeDeposedResourceInstanceObject = (*NodePlanDeposedResourceInstanceObject)(nil)
_ GraphNodeConfigResource = (*NodePlanDeposedResourceInstanceObject)(nil)
_ GraphNodeResourceInstance = (*NodePlanDeposedResourceInstanceObject)(nil)
_ GraphNodeReferenceable = (*NodePlanDeposedResourceInstanceObject)(nil)
_ GraphNodeReferencer = (*NodePlanDeposedResourceInstanceObject)(nil)
_ GraphNodeExecutable = (*NodePlanDeposedResourceInstanceObject)(nil)
_ GraphNodeProviderConsumer = (*NodePlanDeposedResourceInstanceObject)(nil)
_ GraphNodeProvisionerConsumer = (*NodePlanDeposedResourceInstanceObject)(nil)
core: Be more explicit in how we handle create_before_destroy Previously our handling of create_before_destroy -- and of deposed objects in particular -- was rather "implicit" and spread over various different subsystems. We'd quietly just destroy every deposed object during a destroy operation, without any user-visible plan to do so. Here we make things more explicit by tracking each deposed object individually by its pseudorandomly-allocated key. There are two different mechanisms at play here, building on the same concepts: - During a replace operation with create_before_destroy, we *pre-allocate* a DeposedKey to use for the prior object in the "apply" node and then pass that exact id to the destroy node, ensuring that we only destroy the single object we planned to destroy. In the happy path here the user never actually sees the allocated deposed key because we use it and then immediately destroy it within the same operation. However, that destroy may fail, which brings us to the second mechanism: - If any deposed objects are already present in state during _plan_, we insert a destroy change for them into the plan so that it's explicit to the user that we are going to destroy these additional objects, and then create an individual graph node for each one in DiffTransformer. The main motivation here is to be more careful in how we handle these destroys so that from a user's standpoint we never destroy something without the user knowing about it ahead of time. However, this new organization also hopefully makes the code itself a little easier to follow because the connection between the create and destroy steps of a Replace is reprseented in a single place (in DiffTransformer) and deposed instances each have their own explicit graph node rather than being secretly handled as part of the main instance-level graph node.
2018-09-20 21:30:52 +02:00
)
func (n *NodePlanDeposedResourceInstanceObject) Name() string {
return fmt.Sprintf("%s (deposed %s)", n.ResourceInstanceAddr().String(), n.DeposedKey)
core: Be more explicit in how we handle create_before_destroy Previously our handling of create_before_destroy -- and of deposed objects in particular -- was rather "implicit" and spread over various different subsystems. We'd quietly just destroy every deposed object during a destroy operation, without any user-visible plan to do so. Here we make things more explicit by tracking each deposed object individually by its pseudorandomly-allocated key. There are two different mechanisms at play here, building on the same concepts: - During a replace operation with create_before_destroy, we *pre-allocate* a DeposedKey to use for the prior object in the "apply" node and then pass that exact id to the destroy node, ensuring that we only destroy the single object we planned to destroy. In the happy path here the user never actually sees the allocated deposed key because we use it and then immediately destroy it within the same operation. However, that destroy may fail, which brings us to the second mechanism: - If any deposed objects are already present in state during _plan_, we insert a destroy change for them into the plan so that it's explicit to the user that we are going to destroy these additional objects, and then create an individual graph node for each one in DiffTransformer. The main motivation here is to be more careful in how we handle these destroys so that from a user's standpoint we never destroy something without the user knowing about it ahead of time. However, this new organization also hopefully makes the code itself a little easier to follow because the connection between the create and destroy steps of a Replace is reprseented in a single place (in DiffTransformer) and deposed instances each have their own explicit graph node rather than being secretly handled as part of the main instance-level graph node.
2018-09-20 21:30:52 +02:00
}
func (n *NodePlanDeposedResourceInstanceObject) DeposedInstanceObjectKey() states.DeposedKey {
return n.DeposedKey
}
core: Be more explicit in how we handle create_before_destroy Previously our handling of create_before_destroy -- and of deposed objects in particular -- was rather "implicit" and spread over various different subsystems. We'd quietly just destroy every deposed object during a destroy operation, without any user-visible plan to do so. Here we make things more explicit by tracking each deposed object individually by its pseudorandomly-allocated key. There are two different mechanisms at play here, building on the same concepts: - During a replace operation with create_before_destroy, we *pre-allocate* a DeposedKey to use for the prior object in the "apply" node and then pass that exact id to the destroy node, ensuring that we only destroy the single object we planned to destroy. In the happy path here the user never actually sees the allocated deposed key because we use it and then immediately destroy it within the same operation. However, that destroy may fail, which brings us to the second mechanism: - If any deposed objects are already present in state during _plan_, we insert a destroy change for them into the plan so that it's explicit to the user that we are going to destroy these additional objects, and then create an individual graph node for each one in DiffTransformer. The main motivation here is to be more careful in how we handle these destroys so that from a user's standpoint we never destroy something without the user knowing about it ahead of time. However, this new organization also hopefully makes the code itself a little easier to follow because the connection between the create and destroy steps of a Replace is reprseented in a single place (in DiffTransformer) and deposed instances each have their own explicit graph node rather than being secretly handled as part of the main instance-level graph node.
2018-09-20 21:30:52 +02:00
// GraphNodeReferenceable implementation, overriding the one from NodeAbstractResourceInstance
func (n *NodePlanDeposedResourceInstanceObject) ReferenceableAddrs() []addrs.Referenceable {
// Deposed objects don't participate in references.
return nil
}
// GraphNodeReferencer implementation, overriding the one from NodeAbstractResourceInstance
func (n *NodePlanDeposedResourceInstanceObject) References() []*addrs.Reference {
// We don't evaluate configuration for deposed objects, so they effectively
// make no references.
return nil
}
// GraphNodeEvalable impl.
func (n *NodePlanDeposedResourceInstanceObject) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) {
// Read the state for the deposed resource instance
Mildwonkey/eval apply (#27222) * rename files for consistency with contents * terraform: refactor EvalValidateSelfref The EvalValidateSelfref eval node implementation was removed in favor of a regular function. * terraform: refactor EvalValidateProvisioner EvalValidateProvisioner is now a method on NodeValidatableResource. * terraform: refactor EvalValidateResource EvalValidateResource is now a method on NodeValidatableResource, and the functions called by (the new) validateResource are now standalone functions. This particular refactor gets the prize for "most complicated test refactoring". * terraform: refactor EvalMaybeTainted EvalMaybeTainted was a relatively simple operation which never returned an error, so I've refactored it into a plain function and moved it into the only file its called from. * terraform: eval-related cleanup De-exported preApplyHook, which got missed in my general cleanup sweeps. Removed resourceHasUserVisibleApply in favor of moving the logic inline - it was a single-line check so calling the function was (nearly) as much code as just checking if the resource was managed. * terraform: refactor EvalApplyProvisioners EvalApplyProvisioners.Eval is now a method on NodeResourceAbstractInstance. There were two "apply"ish functions, so I named the first "evalApplyProvisioners" since it mainly determined if provisioners should be run before passing off execution to applyProvisioners. * terraform: refactor EvalApply EvalApply is now a method on NodeAbstractResourceInstance. This was one of the trickier Eval()s to refactor, and my goal was to change as little as possible to avoid unintended side effects. One notable change: there was a createNew boolean that was only used in NodeApplyableResourceInstance.managedResourceExecute, and that boolean was populated from the change (which was available from managedResourceExecute), so I removed it from apply entirely. Out of an abundance of caution I assigned the value to createNew in (roughtly) the same spot, in case I was missing some place where the change might get modified. TODO: Destroy nodes passed nil configs into apply, and I am curious if we can get the same functionality by checking if the planned change is a destroy, instead of passing a config into apply. That felt too risky for this refactor but it is something I would like to explore at a future point. There are also a few updates to log output in this PR, since I spent some time staring at logs and noticed various spots I missed.
2020-12-10 14:05:53 +01:00
state, err := n.readResourceInstanceStateDeposed(ctx, n.Addr, n.DeposedKey)
diags = diags.Append(err)
2020-10-28 17:23:03 +01:00
if diags.HasErrors() {
return diags
}
Eval() Refactor: Plan Edition (#27177) * terraforn: refactor EvalRefresh EvalRefresh.Eval(ctx) is now Refresh(evalRefreshReqest, ctx). While none of the inner logic of the function has changed, it now returns a states.ResourceInstanceObject instead of updating a pointer. This is a human-centric change, meant to make the logic flow (in the calling functions) easier to follow. * terraform: refactor EvalReadDataPlan and Apply This is a very minor refactor that removes the (currently) redundant types EvalReadDataPlan and EvalReadDataApply in favor of using EvalReadData with a Plan and Apply functions. This is in effect an aesthetic change; since there is no longer an Eval() abstraction we can rename functions to make their functionality as obvious as possible. * terraform: refactor EvalCheckPlannedChange EvalCheckPlannedChange was only used by NodeApplyableResourceInstance and has been refactored into a method on that type called checkPlannedChange. * terraform: refactor EvalDiff.Eval EvalDiff.Eval is now a method on NodeResourceAbstracted called Plan which takes as a parameter an EvalPlanRequest. Instead of updating pointers it returns a new plan and state. I removed as many redundant fields from the original EvalDiff struct as possible. * terraform: refactor EvalReduceDiff EvalReduceDiff is now reducePlan, a regular function (without a method) that returns a value. * terraform: refactor EvalDiffDestroy EvalDiffDestroy.Eval is now NodeAbstractResourceInstance.PlanDestroy which takes ctx, state and optional DeposedKey and returns a change. I've removed the state return value since it was only ever returning a nil state. * terraform: refactor EvalWriteDiff EvalWriteDiff.Eval is now NodeAbstractResourceInstance.WriteChange. * rename files to something more logical * terrafrom: refresh refactor, continued! I had originally made Refresh a stand-alone function since it was (obnoxiously) called from a graphNodeImportStateSub, but after some (greatly appreciated) prompting in the PR I instead made it a method on the NodeAbstractResourceInstance, in keeping with the other refactored eval nodes, and then built a NodeAbstractResourceInstance inside import. Since I did that I could also remove my duplicated 'writeState' code inside graphNodeImportStateSub and use n.writeResourceInstanceState, so double thanks! * unexport eval methods * re-refactor Plan, it made more sense on NodeAbstractResourceInstance. Sorry * Remove uninformative `Eval`s from EvalReadData, consolidate to a single file, and rename file to match function names. * manual rebase
2020-12-08 14:50:30 +01:00
change, destroyPlanDiags := n.planDestroy(ctx, state, n.DeposedKey)
diags = diags.Append(destroyPlanDiags)
2020-10-28 16:46:07 +01:00
if diags.HasErrors() {
return diags
}
Eval() Refactor: Plan Edition (#27177) * terraforn: refactor EvalRefresh EvalRefresh.Eval(ctx) is now Refresh(evalRefreshReqest, ctx). While none of the inner logic of the function has changed, it now returns a states.ResourceInstanceObject instead of updating a pointer. This is a human-centric change, meant to make the logic flow (in the calling functions) easier to follow. * terraform: refactor EvalReadDataPlan and Apply This is a very minor refactor that removes the (currently) redundant types EvalReadDataPlan and EvalReadDataApply in favor of using EvalReadData with a Plan and Apply functions. This is in effect an aesthetic change; since there is no longer an Eval() abstraction we can rename functions to make their functionality as obvious as possible. * terraform: refactor EvalCheckPlannedChange EvalCheckPlannedChange was only used by NodeApplyableResourceInstance and has been refactored into a method on that type called checkPlannedChange. * terraform: refactor EvalDiff.Eval EvalDiff.Eval is now a method on NodeResourceAbstracted called Plan which takes as a parameter an EvalPlanRequest. Instead of updating pointers it returns a new plan and state. I removed as many redundant fields from the original EvalDiff struct as possible. * terraform: refactor EvalReduceDiff EvalReduceDiff is now reducePlan, a regular function (without a method) that returns a value. * terraform: refactor EvalDiffDestroy EvalDiffDestroy.Eval is now NodeAbstractResourceInstance.PlanDestroy which takes ctx, state and optional DeposedKey and returns a change. I've removed the state return value since it was only ever returning a nil state. * terraform: refactor EvalWriteDiff EvalWriteDiff.Eval is now NodeAbstractResourceInstance.WriteChange. * rename files to something more logical * terrafrom: refresh refactor, continued! I had originally made Refresh a stand-alone function since it was (obnoxiously) called from a graphNodeImportStateSub, but after some (greatly appreciated) prompting in the PR I instead made it a method on the NodeAbstractResourceInstance, in keeping with the other refactored eval nodes, and then built a NodeAbstractResourceInstance inside import. Since I did that I could also remove my duplicated 'writeState' code inside graphNodeImportStateSub and use n.writeResourceInstanceState, so double thanks! * unexport eval methods * re-refactor Plan, it made more sense on NodeAbstractResourceInstance. Sorry * Remove uninformative `Eval`s from EvalReadData, consolidate to a single file, and rename file to match function names. * manual rebase
2020-12-08 14:50:30 +01:00
diags = diags.Append(n.writeChange(ctx, change, n.DeposedKey))
return diags
core: Be more explicit in how we handle create_before_destroy Previously our handling of create_before_destroy -- and of deposed objects in particular -- was rather "implicit" and spread over various different subsystems. We'd quietly just destroy every deposed object during a destroy operation, without any user-visible plan to do so. Here we make things more explicit by tracking each deposed object individually by its pseudorandomly-allocated key. There are two different mechanisms at play here, building on the same concepts: - During a replace operation with create_before_destroy, we *pre-allocate* a DeposedKey to use for the prior object in the "apply" node and then pass that exact id to the destroy node, ensuring that we only destroy the single object we planned to destroy. In the happy path here the user never actually sees the allocated deposed key because we use it and then immediately destroy it within the same operation. However, that destroy may fail, which brings us to the second mechanism: - If any deposed objects are already present in state during _plan_, we insert a destroy change for them into the plan so that it's explicit to the user that we are going to destroy these additional objects, and then create an individual graph node for each one in DiffTransformer. The main motivation here is to be more careful in how we handle these destroys so that from a user's standpoint we never destroy something without the user knowing about it ahead of time. However, this new organization also hopefully makes the code itself a little easier to follow because the connection between the create and destroy steps of a Replace is reprseented in a single place (in DiffTransformer) and deposed instances each have their own explicit graph node rather than being secretly handled as part of the main instance-level graph node.
2018-09-20 21:30:52 +02:00
}
// NodeDestroyDeposedResourceInstanceObject represents deposed resource
// instance objects during apply. Nodes of this type are inserted by
// DiffTransformer when the planned changeset contains "delete" changes for
// deposed instance objects, and its only supported operation is to destroy
// and then forget the associated object.
type NodeDestroyDeposedResourceInstanceObject struct {
*NodeAbstractResourceInstance
DeposedKey states.DeposedKey
}
var (
_ GraphNodeDeposedResourceInstanceObject = (*NodeDestroyDeposedResourceInstanceObject)(nil)
_ GraphNodeConfigResource = (*NodeDestroyDeposedResourceInstanceObject)(nil)
_ GraphNodeResourceInstance = (*NodeDestroyDeposedResourceInstanceObject)(nil)
_ GraphNodeDestroyer = (*NodeDestroyDeposedResourceInstanceObject)(nil)
_ GraphNodeDestroyerCBD = (*NodeDestroyDeposedResourceInstanceObject)(nil)
_ GraphNodeReferenceable = (*NodeDestroyDeposedResourceInstanceObject)(nil)
_ GraphNodeReferencer = (*NodeDestroyDeposedResourceInstanceObject)(nil)
_ GraphNodeExecutable = (*NodeDestroyDeposedResourceInstanceObject)(nil)
_ GraphNodeProviderConsumer = (*NodeDestroyDeposedResourceInstanceObject)(nil)
_ GraphNodeProvisionerConsumer = (*NodeDestroyDeposedResourceInstanceObject)(nil)
core: Be more explicit in how we handle create_before_destroy Previously our handling of create_before_destroy -- and of deposed objects in particular -- was rather "implicit" and spread over various different subsystems. We'd quietly just destroy every deposed object during a destroy operation, without any user-visible plan to do so. Here we make things more explicit by tracking each deposed object individually by its pseudorandomly-allocated key. There are two different mechanisms at play here, building on the same concepts: - During a replace operation with create_before_destroy, we *pre-allocate* a DeposedKey to use for the prior object in the "apply" node and then pass that exact id to the destroy node, ensuring that we only destroy the single object we planned to destroy. In the happy path here the user never actually sees the allocated deposed key because we use it and then immediately destroy it within the same operation. However, that destroy may fail, which brings us to the second mechanism: - If any deposed objects are already present in state during _plan_, we insert a destroy change for them into the plan so that it's explicit to the user that we are going to destroy these additional objects, and then create an individual graph node for each one in DiffTransformer. The main motivation here is to be more careful in how we handle these destroys so that from a user's standpoint we never destroy something without the user knowing about it ahead of time. However, this new organization also hopefully makes the code itself a little easier to follow because the connection between the create and destroy steps of a Replace is reprseented in a single place (in DiffTransformer) and deposed instances each have their own explicit graph node rather than being secretly handled as part of the main instance-level graph node.
2018-09-20 21:30:52 +02:00
)
func (n *NodeDestroyDeposedResourceInstanceObject) Name() string {
2019-11-19 23:30:27 +01:00
return fmt.Sprintf("%s (destroy deposed %s)", n.ResourceInstanceAddr(), n.DeposedKey)
core: Be more explicit in how we handle create_before_destroy Previously our handling of create_before_destroy -- and of deposed objects in particular -- was rather "implicit" and spread over various different subsystems. We'd quietly just destroy every deposed object during a destroy operation, without any user-visible plan to do so. Here we make things more explicit by tracking each deposed object individually by its pseudorandomly-allocated key. There are two different mechanisms at play here, building on the same concepts: - During a replace operation with create_before_destroy, we *pre-allocate* a DeposedKey to use for the prior object in the "apply" node and then pass that exact id to the destroy node, ensuring that we only destroy the single object we planned to destroy. In the happy path here the user never actually sees the allocated deposed key because we use it and then immediately destroy it within the same operation. However, that destroy may fail, which brings us to the second mechanism: - If any deposed objects are already present in state during _plan_, we insert a destroy change for them into the plan so that it's explicit to the user that we are going to destroy these additional objects, and then create an individual graph node for each one in DiffTransformer. The main motivation here is to be more careful in how we handle these destroys so that from a user's standpoint we never destroy something without the user knowing about it ahead of time. However, this new organization also hopefully makes the code itself a little easier to follow because the connection between the create and destroy steps of a Replace is reprseented in a single place (in DiffTransformer) and deposed instances each have their own explicit graph node rather than being secretly handled as part of the main instance-level graph node.
2018-09-20 21:30:52 +02:00
}
func (n *NodeDestroyDeposedResourceInstanceObject) DeposedInstanceObjectKey() states.DeposedKey {
return n.DeposedKey
}
core: Be more explicit in how we handle create_before_destroy Previously our handling of create_before_destroy -- and of deposed objects in particular -- was rather "implicit" and spread over various different subsystems. We'd quietly just destroy every deposed object during a destroy operation, without any user-visible plan to do so. Here we make things more explicit by tracking each deposed object individually by its pseudorandomly-allocated key. There are two different mechanisms at play here, building on the same concepts: - During a replace operation with create_before_destroy, we *pre-allocate* a DeposedKey to use for the prior object in the "apply" node and then pass that exact id to the destroy node, ensuring that we only destroy the single object we planned to destroy. In the happy path here the user never actually sees the allocated deposed key because we use it and then immediately destroy it within the same operation. However, that destroy may fail, which brings us to the second mechanism: - If any deposed objects are already present in state during _plan_, we insert a destroy change for them into the plan so that it's explicit to the user that we are going to destroy these additional objects, and then create an individual graph node for each one in DiffTransformer. The main motivation here is to be more careful in how we handle these destroys so that from a user's standpoint we never destroy something without the user knowing about it ahead of time. However, this new organization also hopefully makes the code itself a little easier to follow because the connection between the create and destroy steps of a Replace is reprseented in a single place (in DiffTransformer) and deposed instances each have their own explicit graph node rather than being secretly handled as part of the main instance-level graph node.
2018-09-20 21:30:52 +02:00
// GraphNodeReferenceable implementation, overriding the one from NodeAbstractResourceInstance
func (n *NodeDestroyDeposedResourceInstanceObject) ReferenceableAddrs() []addrs.Referenceable {
// Deposed objects don't participate in references.
return nil
}
// GraphNodeReferencer implementation, overriding the one from NodeAbstractResourceInstance
func (n *NodeDestroyDeposedResourceInstanceObject) References() []*addrs.Reference {
// We don't evaluate configuration for deposed objects, so they effectively
// make no references.
return nil
}
// GraphNodeDestroyer
func (n *NodeDestroyDeposedResourceInstanceObject) DestroyAddr() *addrs.AbsResourceInstance {
addr := n.ResourceInstanceAddr()
return &addr
}
// GraphNodeDestroyerCBD
func (n *NodeDestroyDeposedResourceInstanceObject) CreateBeforeDestroy() bool {
// A deposed instance is always CreateBeforeDestroy by definition, since
// we use deposed only to handle create-before-destroy.
return true
}
// GraphNodeDestroyerCBD
func (n *NodeDestroyDeposedResourceInstanceObject) ModifyCreateBeforeDestroy(v bool) error {
if !v {
// Should never happen: deposed instances are _always_ create_before_destroy.
return fmt.Errorf("can't deactivate create_before_destroy for a deposed instance")
}
return nil
}
// GraphNodeExecutable impl.
func (n *NodeDestroyDeposedResourceInstanceObject) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) {
core: Be more explicit in how we handle create_before_destroy Previously our handling of create_before_destroy -- and of deposed objects in particular -- was rather "implicit" and spread over various different subsystems. We'd quietly just destroy every deposed object during a destroy operation, without any user-visible plan to do so. Here we make things more explicit by tracking each deposed object individually by its pseudorandomly-allocated key. There are two different mechanisms at play here, building on the same concepts: - During a replace operation with create_before_destroy, we *pre-allocate* a DeposedKey to use for the prior object in the "apply" node and then pass that exact id to the destroy node, ensuring that we only destroy the single object we planned to destroy. In the happy path here the user never actually sees the allocated deposed key because we use it and then immediately destroy it within the same operation. However, that destroy may fail, which brings us to the second mechanism: - If any deposed objects are already present in state during _plan_, we insert a destroy change for them into the plan so that it's explicit to the user that we are going to destroy these additional objects, and then create an individual graph node for each one in DiffTransformer. The main motivation here is to be more careful in how we handle these destroys so that from a user's standpoint we never destroy something without the user knowing about it ahead of time. However, this new organization also hopefully makes the code itself a little easier to follow because the connection between the create and destroy steps of a Replace is reprseented in a single place (in DiffTransformer) and deposed instances each have their own explicit graph node rather than being secretly handled as part of the main instance-level graph node.
2018-09-20 21:30:52 +02:00
var change *plans.ResourceInstanceChange
// Read the state for the deposed resource instance
Mildwonkey/eval apply (#27222) * rename files for consistency with contents * terraform: refactor EvalValidateSelfref The EvalValidateSelfref eval node implementation was removed in favor of a regular function. * terraform: refactor EvalValidateProvisioner EvalValidateProvisioner is now a method on NodeValidatableResource. * terraform: refactor EvalValidateResource EvalValidateResource is now a method on NodeValidatableResource, and the functions called by (the new) validateResource are now standalone functions. This particular refactor gets the prize for "most complicated test refactoring". * terraform: refactor EvalMaybeTainted EvalMaybeTainted was a relatively simple operation which never returned an error, so I've refactored it into a plain function and moved it into the only file its called from. * terraform: eval-related cleanup De-exported preApplyHook, which got missed in my general cleanup sweeps. Removed resourceHasUserVisibleApply in favor of moving the logic inline - it was a single-line check so calling the function was (nearly) as much code as just checking if the resource was managed. * terraform: refactor EvalApplyProvisioners EvalApplyProvisioners.Eval is now a method on NodeResourceAbstractInstance. There were two "apply"ish functions, so I named the first "evalApplyProvisioners" since it mainly determined if provisioners should be run before passing off execution to applyProvisioners. * terraform: refactor EvalApply EvalApply is now a method on NodeAbstractResourceInstance. This was one of the trickier Eval()s to refactor, and my goal was to change as little as possible to avoid unintended side effects. One notable change: there was a createNew boolean that was only used in NodeApplyableResourceInstance.managedResourceExecute, and that boolean was populated from the change (which was available from managedResourceExecute), so I removed it from apply entirely. Out of an abundance of caution I assigned the value to createNew in (roughtly) the same spot, in case I was missing some place where the change might get modified. TODO: Destroy nodes passed nil configs into apply, and I am curious if we can get the same functionality by checking if the planned change is a destroy, instead of passing a config into apply. That felt too risky for this refactor but it is something I would like to explore at a future point. There are also a few updates to log output in this PR, since I spent some time staring at logs and noticed various spots I missed.
2020-12-10 14:05:53 +01:00
state, err := n.readResourceInstanceStateDeposed(ctx, n.Addr, n.DeposedKey)
if err != nil {
return diags.Append(err)
}
Eval() Refactor: Plan Edition (#27177) * terraforn: refactor EvalRefresh EvalRefresh.Eval(ctx) is now Refresh(evalRefreshReqest, ctx). While none of the inner logic of the function has changed, it now returns a states.ResourceInstanceObject instead of updating a pointer. This is a human-centric change, meant to make the logic flow (in the calling functions) easier to follow. * terraform: refactor EvalReadDataPlan and Apply This is a very minor refactor that removes the (currently) redundant types EvalReadDataPlan and EvalReadDataApply in favor of using EvalReadData with a Plan and Apply functions. This is in effect an aesthetic change; since there is no longer an Eval() abstraction we can rename functions to make their functionality as obvious as possible. * terraform: refactor EvalCheckPlannedChange EvalCheckPlannedChange was only used by NodeApplyableResourceInstance and has been refactored into a method on that type called checkPlannedChange. * terraform: refactor EvalDiff.Eval EvalDiff.Eval is now a method on NodeResourceAbstracted called Plan which takes as a parameter an EvalPlanRequest. Instead of updating pointers it returns a new plan and state. I removed as many redundant fields from the original EvalDiff struct as possible. * terraform: refactor EvalReduceDiff EvalReduceDiff is now reducePlan, a regular function (without a method) that returns a value. * terraform: refactor EvalDiffDestroy EvalDiffDestroy.Eval is now NodeAbstractResourceInstance.PlanDestroy which takes ctx, state and optional DeposedKey and returns a change. I've removed the state return value since it was only ever returning a nil state. * terraform: refactor EvalWriteDiff EvalWriteDiff.Eval is now NodeAbstractResourceInstance.WriteChange. * rename files to something more logical * terrafrom: refresh refactor, continued! I had originally made Refresh a stand-alone function since it was (obnoxiously) called from a graphNodeImportStateSub, but after some (greatly appreciated) prompting in the PR I instead made it a method on the NodeAbstractResourceInstance, in keeping with the other refactored eval nodes, and then built a NodeAbstractResourceInstance inside import. Since I did that I could also remove my duplicated 'writeState' code inside graphNodeImportStateSub and use n.writeResourceInstanceState, so double thanks! * unexport eval methods * re-refactor Plan, it made more sense on NodeAbstractResourceInstance. Sorry * Remove uninformative `Eval`s from EvalReadData, consolidate to a single file, and rename file to match function names. * manual rebase
2020-12-08 14:50:30 +01:00
change, destroyPlanDiags := n.planDestroy(ctx, state, n.DeposedKey)
diags = diags.Append(destroyPlanDiags)
2020-10-28 16:46:07 +01:00
if diags.HasErrors() {
return diags
}
// Call pre-apply hook
Mildwonkey/eval apply (#27222) * rename files for consistency with contents * terraform: refactor EvalValidateSelfref The EvalValidateSelfref eval node implementation was removed in favor of a regular function. * terraform: refactor EvalValidateProvisioner EvalValidateProvisioner is now a method on NodeValidatableResource. * terraform: refactor EvalValidateResource EvalValidateResource is now a method on NodeValidatableResource, and the functions called by (the new) validateResource are now standalone functions. This particular refactor gets the prize for "most complicated test refactoring". * terraform: refactor EvalMaybeTainted EvalMaybeTainted was a relatively simple operation which never returned an error, so I've refactored it into a plain function and moved it into the only file its called from. * terraform: eval-related cleanup De-exported preApplyHook, which got missed in my general cleanup sweeps. Removed resourceHasUserVisibleApply in favor of moving the logic inline - it was a single-line check so calling the function was (nearly) as much code as just checking if the resource was managed. * terraform: refactor EvalApplyProvisioners EvalApplyProvisioners.Eval is now a method on NodeResourceAbstractInstance. There were two "apply"ish functions, so I named the first "evalApplyProvisioners" since it mainly determined if provisioners should be run before passing off execution to applyProvisioners. * terraform: refactor EvalApply EvalApply is now a method on NodeAbstractResourceInstance. This was one of the trickier Eval()s to refactor, and my goal was to change as little as possible to avoid unintended side effects. One notable change: there was a createNew boolean that was only used in NodeApplyableResourceInstance.managedResourceExecute, and that boolean was populated from the change (which was available from managedResourceExecute), so I removed it from apply entirely. Out of an abundance of caution I assigned the value to createNew in (roughtly) the same spot, in case I was missing some place where the change might get modified. TODO: Destroy nodes passed nil configs into apply, and I am curious if we can get the same functionality by checking if the planned change is a destroy, instead of passing a config into apply. That felt too risky for this refactor but it is something I would like to explore at a future point. There are also a few updates to log output in this PR, since I spent some time staring at logs and noticed various spots I missed.
2020-12-10 14:05:53 +01:00
diags = diags.Append(n.preApplyHook(ctx, change))
2020-10-27 23:16:28 +01:00
if diags.HasErrors() {
return diags
}
Mildwonkey/eval apply (#27222) * rename files for consistency with contents * terraform: refactor EvalValidateSelfref The EvalValidateSelfref eval node implementation was removed in favor of a regular function. * terraform: refactor EvalValidateProvisioner EvalValidateProvisioner is now a method on NodeValidatableResource. * terraform: refactor EvalValidateResource EvalValidateResource is now a method on NodeValidatableResource, and the functions called by (the new) validateResource are now standalone functions. This particular refactor gets the prize for "most complicated test refactoring". * terraform: refactor EvalMaybeTainted EvalMaybeTainted was a relatively simple operation which never returned an error, so I've refactored it into a plain function and moved it into the only file its called from. * terraform: eval-related cleanup De-exported preApplyHook, which got missed in my general cleanup sweeps. Removed resourceHasUserVisibleApply in favor of moving the logic inline - it was a single-line check so calling the function was (nearly) as much code as just checking if the resource was managed. * terraform: refactor EvalApplyProvisioners EvalApplyProvisioners.Eval is now a method on NodeResourceAbstractInstance. There were two "apply"ish functions, so I named the first "evalApplyProvisioners" since it mainly determined if provisioners should be run before passing off execution to applyProvisioners. * terraform: refactor EvalApply EvalApply is now a method on NodeAbstractResourceInstance. This was one of the trickier Eval()s to refactor, and my goal was to change as little as possible to avoid unintended side effects. One notable change: there was a createNew boolean that was only used in NodeApplyableResourceInstance.managedResourceExecute, and that boolean was populated from the change (which was available from managedResourceExecute), so I removed it from apply entirely. Out of an abundance of caution I assigned the value to createNew in (roughtly) the same spot, in case I was missing some place where the change might get modified. TODO: Destroy nodes passed nil configs into apply, and I am curious if we can get the same functionality by checking if the planned change is a destroy, instead of passing a config into apply. That felt too risky for this refactor but it is something I would like to explore at a future point. There are also a few updates to log output in this PR, since I spent some time staring at logs and noticed various spots I missed.
2020-12-10 14:05:53 +01:00
// we pass a nil configuration to apply because we are destroying
state, applyDiags := n.apply(ctx, state, change, nil, false)
diags = diags.Append(applyDiags)
// don't return immediately on errors, we need to handle the state
// Always write the resource back to the state deposed. If it
// was successfully destroyed it will be pruned. If it was not, it will
// be caught on the next run.
err = n.writeResourceInstanceState(ctx, state)
if err != nil {
return diags.Append(err)
}
diags = diags.Append(n.postApplyHook(ctx, state, diags.Err()))
Mildwonkey/eval apply (#27222) * rename files for consistency with contents * terraform: refactor EvalValidateSelfref The EvalValidateSelfref eval node implementation was removed in favor of a regular function. * terraform: refactor EvalValidateProvisioner EvalValidateProvisioner is now a method on NodeValidatableResource. * terraform: refactor EvalValidateResource EvalValidateResource is now a method on NodeValidatableResource, and the functions called by (the new) validateResource are now standalone functions. This particular refactor gets the prize for "most complicated test refactoring". * terraform: refactor EvalMaybeTainted EvalMaybeTainted was a relatively simple operation which never returned an error, so I've refactored it into a plain function and moved it into the only file its called from. * terraform: eval-related cleanup De-exported preApplyHook, which got missed in my general cleanup sweeps. Removed resourceHasUserVisibleApply in favor of moving the logic inline - it was a single-line check so calling the function was (nearly) as much code as just checking if the resource was managed. * terraform: refactor EvalApplyProvisioners EvalApplyProvisioners.Eval is now a method on NodeResourceAbstractInstance. There were two "apply"ish functions, so I named the first "evalApplyProvisioners" since it mainly determined if provisioners should be run before passing off execution to applyProvisioners. * terraform: refactor EvalApply EvalApply is now a method on NodeAbstractResourceInstance. This was one of the trickier Eval()s to refactor, and my goal was to change as little as possible to avoid unintended side effects. One notable change: there was a createNew boolean that was only used in NodeApplyableResourceInstance.managedResourceExecute, and that boolean was populated from the change (which was available from managedResourceExecute), so I removed it from apply entirely. Out of an abundance of caution I assigned the value to createNew in (roughtly) the same spot, in case I was missing some place where the change might get modified. TODO: Destroy nodes passed nil configs into apply, and I am curious if we can get the same functionality by checking if the planned change is a destroy, instead of passing a config into apply. That felt too risky for this refactor but it is something I would like to explore at a future point. There are also a few updates to log output in this PR, since I spent some time staring at logs and noticed various spots I missed.
2020-12-10 14:05:53 +01:00
return diags.Append(updateStateHook(ctx))
core: Be more explicit in how we handle create_before_destroy Previously our handling of create_before_destroy -- and of deposed objects in particular -- was rather "implicit" and spread over various different subsystems. We'd quietly just destroy every deposed object during a destroy operation, without any user-visible plan to do so. Here we make things more explicit by tracking each deposed object individually by its pseudorandomly-allocated key. There are two different mechanisms at play here, building on the same concepts: - During a replace operation with create_before_destroy, we *pre-allocate* a DeposedKey to use for the prior object in the "apply" node and then pass that exact id to the destroy node, ensuring that we only destroy the single object we planned to destroy. In the happy path here the user never actually sees the allocated deposed key because we use it and then immediately destroy it within the same operation. However, that destroy may fail, which brings us to the second mechanism: - If any deposed objects are already present in state during _plan_, we insert a destroy change for them into the plan so that it's explicit to the user that we are going to destroy these additional objects, and then create an individual graph node for each one in DiffTransformer. The main motivation here is to be more careful in how we handle these destroys so that from a user's standpoint we never destroy something without the user knowing about it ahead of time. However, this new organization also hopefully makes the code itself a little easier to follow because the connection between the create and destroy steps of a Replace is reprseented in a single place (in DiffTransformer) and deposed instances each have their own explicit graph node rather than being secretly handled as part of the main instance-level graph node.
2018-09-20 21:30:52 +02:00
}
// GraphNodeDeposer is an optional interface implemented by graph nodes that
// might create a single new deposed object for a specific associated resource
// instance, allowing a caller to optionally pre-allocate a DeposedKey for
// it.
type GraphNodeDeposer interface {
// SetPreallocatedDeposedKey will be called during graph construction
// if a particular node must use a pre-allocated deposed key if/when it
// "deposes" the current object of its associated resource instance.
SetPreallocatedDeposedKey(key states.DeposedKey)
}
// graphNodeDeposer is an embeddable implementation of GraphNodeDeposer.
// Embed it in a node type to get automatic support for it, and then access
// the field PreallocatedDeposedKey to access any pre-allocated key.
type graphNodeDeposer struct {
PreallocatedDeposedKey states.DeposedKey
}
func (n *graphNodeDeposer) SetPreallocatedDeposedKey(key states.DeposedKey) {
n.PreallocatedDeposedKey = key
}
func (n *NodeDestroyDeposedResourceInstanceObject) writeResourceInstanceState(ctx EvalContext, obj *states.ResourceInstanceObject) error {
absAddr := n.Addr
key := n.DeposedKey
state := ctx.State()
if key == states.NotDeposed {
// should never happen
return fmt.Errorf("can't save deposed object for %s without a deposed key; this is a bug in Terraform that should be reported", absAddr)
}
if obj == nil {
// No need to encode anything: we'll just write it directly.
state.SetResourceInstanceDeposed(absAddr, key, nil, n.ResolvedProvider)
log.Printf("[TRACE] writeResourceInstanceStateDeposed: removing state object for %s deposed %s", absAddr, key)
return nil
}
_, providerSchema, err := getProvider(ctx, n.ResolvedProvider)
if err != nil {
return err
}
if providerSchema == nil {
// Should never happen, unless our state object is nil
panic("writeResourceInstanceStateDeposed used with no ProviderSchema object")
}
schema, currentVersion := providerSchema.SchemaForResourceAddr(absAddr.ContainingResource().Resource)
if schema == nil {
// It shouldn't be possible to get this far in any real scenario
// without a schema, but we might end up here in contrived tests that
// fail to set up their world properly.
return fmt.Errorf("failed to encode %s in state: no resource type schema available", absAddr)
}
src, err := obj.Encode(schema.ImpliedType(), currentVersion)
if err != nil {
return fmt.Errorf("failed to encode %s in state: %s", absAddr, err)
}
log.Printf("[TRACE] writeResourceInstanceStateDeposed: writing state object for %s deposed %s", absAddr, key)
state.SetResourceInstanceDeposed(absAddr, key, src, n.ResolvedProvider)
return nil
}