2018-09-11 16:22:25 -05:00
package terraform
import (
"fmt"
2020-12-04 08:16:26 -06:00
"log"
2018-09-11 16:22:25 -05:00
2021-05-17 14:00:50 -05:00
"github.com/hashicorp/terraform/internal/addrs"
2021-05-17 14:17:09 -05:00
"github.com/hashicorp/terraform/internal/configs"
2021-05-17 14:33:17 -05:00
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/plans/objchange"
2021-05-17 14:43:35 -05:00
"github.com/hashicorp/terraform/internal/states"
2021-05-17 12:11:06 -05:00
"github.com/hashicorp/terraform/internal/tfdiags"
2018-09-11 16:22:25 -05:00
)
// NodeApplyableResourceInstance represents a resource instance that is
// "applyable": it is ready to be applied and is represented by a diff.
//
// This node is for a specific instance of a resource. It will usually be
// accompanied in the graph by a NodeApplyableResource representing its
// containing resource, and should depend on that node to ensure that the
// state is properly prepared to receive changes to instances.
type NodeApplyableResourceInstance struct {
* NodeAbstractResourceInstance
2020-03-23 14:26:18 -05:00
graphNodeDeposer // implementation of GraphNodeDeposerConfig
2020-05-14 13:10:17 -05:00
// If this node is forced to be CreateBeforeDestroy, we need to record that
// in the state to.
ForceCreateBeforeDestroy bool
2021-04-06 19:37:38 -05:00
// forceReplace are resource instance addresses where the user wants to
// force generating a replace action. This set isn't pre-filtered, so
// it might contain addresses that have nothing to do with the resource
// that this node represents, which the node itself must therefore ignore.
forceReplace [ ] addrs . AbsResourceInstance
2018-09-11 16:22:25 -05:00
}
var (
2020-03-15 10:32:06 -05:00
_ GraphNodeConfigResource = ( * NodeApplyableResourceInstance ) ( nil )
2019-10-17 15:05:27 -05:00
_ GraphNodeResourceInstance = ( * NodeApplyableResourceInstance ) ( nil )
_ GraphNodeCreator = ( * NodeApplyableResourceInstance ) ( nil )
_ GraphNodeReferencer = ( * NodeApplyableResourceInstance ) ( nil )
_ GraphNodeDeposer = ( * NodeApplyableResourceInstance ) ( nil )
2020-09-28 15:40:40 -05:00
_ GraphNodeExecutable = ( * NodeApplyableResourceInstance ) ( nil )
2019-10-17 15:05:27 -05:00
_ GraphNodeAttachDependencies = ( * NodeApplyableResourceInstance ) ( nil )
2018-09-11 16:22:25 -05:00
)
2020-09-16 09:06:44 -05:00
// CreateBeforeDestroy returns this node's CreateBeforeDestroy status.
2020-05-14 13:10:17 -05:00
func ( n * NodeApplyableResourceInstance ) CreateBeforeDestroy ( ) bool {
if n . ForceCreateBeforeDestroy {
return n . ForceCreateBeforeDestroy
}
2018-09-11 16:22:25 -05:00
if n . Config != nil && n . Config . Managed != nil {
2020-05-14 13:10:17 -05:00
return n . Config . Managed . CreateBeforeDestroy
2018-09-11 16:22:25 -05:00
}
2020-05-14 13:10:17 -05:00
return false
}
func ( n * NodeApplyableResourceInstance ) ModifyCreateBeforeDestroy ( v bool ) error {
n . ForceCreateBeforeDestroy = v
return nil
2018-09-11 16:22:25 -05:00
}
// GraphNodeCreator
func ( n * NodeApplyableResourceInstance ) CreateAddr ( ) * addrs . AbsResourceInstance {
addr := n . ResourceInstanceAddr ( )
return & addr
}
// GraphNodeReferencer, overriding NodeAbstractResourceInstance
func ( n * NodeApplyableResourceInstance ) References ( ) [ ] * addrs . Reference {
// Start with the usual resource instance implementation
ret := n . NodeAbstractResourceInstance . References ( )
// Applying a resource must also depend on the destruction of any of its
// dependencies, since this may for example affect the outcome of
// evaluating an entire list of resources with "count" set (by reducing
// the count).
//
// However, we can't do this in create_before_destroy mode because that
// would create a dependency cycle. We make a compromise here of requiring
// changes to be updated across two applies in this case, since the first
// plan will use the old values.
2020-05-14 13:10:17 -05:00
if ! n . CreateBeforeDestroy ( ) {
2018-09-11 16:22:25 -05:00
for _ , ref := range ret {
switch tr := ref . Subject . ( type ) {
case addrs . ResourceInstance :
newRef := * ref // shallow copy so we can mutate
newRef . Subject = tr . Phase ( addrs . ResourceInstancePhaseDestroy )
newRef . Remaining = nil // can't access attributes of something being destroyed
ret = append ( ret , & newRef )
case addrs . Resource :
newRef := * ref // shallow copy so we can mutate
newRef . Subject = tr . Phase ( addrs . ResourceInstancePhaseDestroy )
newRef . Remaining = nil // can't access attributes of something being destroyed
ret = append ( ret , & newRef )
}
}
}
return ret
}
2019-10-17 15:05:27 -05:00
// GraphNodeAttachDependencies
2020-03-15 10:32:06 -05:00
func ( n * NodeApplyableResourceInstance ) AttachDependencies ( deps [ ] addrs . ConfigResource ) {
2020-03-23 14:26:18 -05:00
n . Dependencies = deps
2019-10-17 15:05:27 -05:00
}
2020-09-28 15:40:40 -05:00
// GraphNodeExecutable
2020-10-28 12:47:04 -05:00
func ( n * NodeApplyableResourceInstance ) Execute ( ctx EvalContext , op walkOperation ) ( diags tfdiags . Diagnostics ) {
2018-09-11 16:22:25 -05:00
addr := n . ResourceInstanceAddr ( )
2019-05-14 18:09:19 -05:00
if n . Config == nil {
// This should not be possible, but we've got here in at least one
// case as discussed in the following issue:
// https://github.com/hashicorp/terraform/issues/21258
// To avoid an outright crash here, we'll instead return an explicit
// error.
diags = diags . Append ( tfdiags . Sourceless (
tfdiags . Error ,
"Resource node has no configuration attached" ,
fmt . Sprintf (
"The graph node for %s has no configuration attached to it. This suggests a bug in Terraform's apply graph builder; please report it!" ,
addr ,
) ,
) )
2020-10-28 12:47:04 -05:00
return diags
2019-05-14 18:09:19 -05:00
}
2018-09-11 16:22:25 -05:00
// Eval info is different depending on what kind of resource this is
switch n . Config . Mode {
case addrs . ManagedResourceMode :
2020-09-28 15:40:40 -05:00
return n . managedResourceExecute ( ctx )
2018-09-11 16:22:25 -05:00
case addrs . DataResourceMode :
2020-09-28 15:40:40 -05:00
return n . dataResourceExecute ( ctx )
2018-09-11 16:22:25 -05:00
default :
panic ( fmt . Errorf ( "unsupported resource mode %s" , n . Config . Mode ) )
}
}
2020-10-28 12:47:04 -05:00
func ( n * NodeApplyableResourceInstance ) dataResourceExecute ( ctx EvalContext ) ( diags tfdiags . Diagnostics ) {
2020-12-10 08:55:50 -06:00
_ , providerSchema , err := getProvider ( ctx , n . ResolvedProvider )
2020-10-28 12:47:04 -05:00
diags = diags . Append ( err )
if diags . HasErrors ( ) {
return diags
2020-09-28 15:40:40 -05:00
}
2018-09-11 16:22:25 -05:00
2020-10-01 07:12:10 -05:00
change , err := n . readDiff ( ctx , providerSchema )
2020-10-28 12:47:04 -05:00
diags = diags . Append ( err )
if diags . HasErrors ( ) {
return diags
2020-09-28 15:40:40 -05:00
}
// Stop early if we don't actually have a diff
if change == nil {
2020-10-28 12:47:04 -05:00
return diags
2020-09-28 15:40:40 -05:00
}
2020-12-08 09:43:04 -06:00
// In this particular call to applyDataSource we include our planned
2020-09-28 15:40:40 -05:00
// change, which signals that we expect this read to complete fully
// with no unknown values; it'll produce an error if not.
2020-12-08 09:43:04 -06:00
state , applyDiags := n . applyDataSource ( ctx , change )
diags = diags . Append ( applyDiags )
2020-10-28 10:57:45 -05:00
if diags . HasErrors ( ) {
2020-10-28 12:47:04 -05:00
return diags
2020-09-28 15:40:40 -05:00
}
2021-03-26 08:40:33 -05:00
diags = diags . Append ( n . writeResourceInstanceState ( ctx , state , workingState ) )
2020-10-28 11:23:03 -05:00
if diags . HasErrors ( ) {
2020-10-28 12:47:04 -05:00
return diags
2020-09-28 15:40:40 -05:00
}
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 07:50:30 -06:00
diags = diags . Append ( n . writeChange ( ctx , nil , "" ) )
2020-09-28 15:40:40 -05:00
2020-12-10 08:55:50 -06:00
diags = diags . Append ( updateStateHook ( ctx ) )
2020-10-28 12:47:04 -05:00
return diags
2018-09-11 16:22:25 -05:00
}
2020-10-28 12:47:04 -05:00
func ( n * NodeApplyableResourceInstance ) managedResourceExecute ( ctx EvalContext ) ( diags tfdiags . Diagnostics ) {
2018-09-11 16:22:25 -05:00
// Declare a bunch of variables that are used for state during
// evaluation. Most of this are written to by-address below.
var state * states . ResourceInstanceObject
var createBeforeDestroyEnabled bool
var deposedKey states . DeposedKey
2020-09-28 15:40:40 -05:00
addr := n . ResourceInstanceAddr ( ) . Resource
2020-12-10 08:55:50 -06:00
_ , providerSchema , err := getProvider ( ctx , n . ResolvedProvider )
2020-10-28 12:47:04 -05:00
diags = diags . Append ( err )
if diags . HasErrors ( ) {
return diags
2020-09-28 15:40:40 -05:00
}
// Get the saved diff for apply
2020-10-01 07:12:10 -05:00
diffApply , err := n . readDiff ( ctx , providerSchema )
2020-10-28 12:47:04 -05:00
diags = diags . Append ( err )
if diags . HasErrors ( ) {
return diags
2020-09-28 15:40:40 -05:00
}
// We don't want to do any destroys
// (these are handled by NodeDestroyResourceInstance instead)
if diffApply == nil || diffApply . Action == plans . Delete {
2020-10-28 12:47:04 -05:00
return diags
2020-09-28 15:40:40 -05:00
}
destroy := ( diffApply . Action == plans . Delete || diffApply . Action . IsReplace ( ) )
// Get the stored action for CBD if we have a plan already
createBeforeDestroyEnabled = diffApply . Change . Action == plans . CreateThenDelete
if destroy && n . CreateBeforeDestroy ( ) {
createBeforeDestroyEnabled = true
}
if createBeforeDestroyEnabled {
2020-12-04 08:16:26 -06:00
state := ctx . State ( )
if n . PreallocatedDeposedKey == states . NotDeposed {
deposedKey = state . DeposeResourceInstanceObject ( n . Addr )
} else {
deposedKey = n . PreallocatedDeposedKey
state . DeposeResourceInstanceObjectForceKey ( n . Addr , deposedKey )
2020-09-28 15:40:40 -05:00
}
2020-12-04 08:16:26 -06:00
log . Printf ( "[TRACE] managedResourceExecute: prior object for %s now deposed with key %s" , n . Addr , deposedKey )
2020-09-28 15:40:40 -05:00
}
2021-04-28 12:43:45 -05:00
state , readDiags := n . readResourceInstanceState ( ctx , n . ResourceInstanceAddr ( ) )
diags = diags . Append ( readDiags )
2020-10-28 11:23:03 -05:00
if diags . HasErrors ( ) {
2020-10-28 12:47:04 -05:00
return diags
2020-09-28 15:40:40 -05:00
}
// Get the saved diff
2020-10-01 07:12:10 -05:00
diff , err := n . readDiff ( ctx , providerSchema )
2020-10-28 12:47:04 -05:00
diags = diags . Append ( err )
if diags . HasErrors ( ) {
return diags
2020-09-28 15:40:40 -05:00
}
// Make a new diff, in case we've learned new values in the state
// during apply which we can now incorporate.
2021-04-06 19:37:38 -05:00
diffApply , _ , planDiags := n . plan ( ctx , diff , state , false , n . forceReplace )
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 07:50:30 -06:00
diags = diags . Append ( planDiags )
2020-10-28 10:46:07 -05:00
if diags . HasErrors ( ) {
2020-10-28 12:47:04 -05:00
return diags
2020-09-28 15:40:40 -05:00
}
// Compare the diffs
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 07:50:30 -06:00
diags = diags . Append ( n . checkPlannedChange ( ctx , diff , diffApply , providerSchema ) )
2020-10-28 10:46:07 -05:00
if diags . HasErrors ( ) {
2020-10-28 12:47:04 -05:00
return diags
2020-09-28 15:40:40 -05:00
}
2021-04-28 12:43:45 -05:00
state , readDiags = n . readResourceInstanceState ( ctx , n . ResourceInstanceAddr ( ) )
diags = diags . Append ( readDiags )
2020-10-28 11:23:03 -05:00
if diags . HasErrors ( ) {
2020-10-28 12:47:04 -05:00
return diags
2020-09-28 15:40:40 -05:00
}
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 07:50:30 -06:00
diffApply = reducePlan ( addr , diffApply , false )
// reducePlan may have simplified our planned change
2020-09-28 15:40:40 -05:00
// into a NoOp if it only requires destroying, since destroying
// is handled by NodeDestroyResourceInstance.
if diffApply == nil || diffApply . Action == plans . NoOp {
2020-10-28 12:47:04 -05:00
return diags
2020-09-28 15:40:40 -05:00
}
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 07:05:53 -06:00
diags = diags . Append ( n . preApplyHook ( ctx , diffApply ) )
2020-10-27 17:16:28 -05:00
if diags . HasErrors ( ) {
2020-10-28 12:47:04 -05:00
return diags
2020-09-28 15:40:40 -05:00
}
2021-01-13 14:08:53 -06:00
state , applyDiags := n . apply ( ctx , state , diffApply , n . Config , n . CreateBeforeDestroy ( ) )
2021-01-13 14:27:17 -06:00
diags = diags . Append ( applyDiags )
2020-09-28 15:40:40 -05:00
2020-10-02 10:54:58 -05:00
// We clear the change out here so that future nodes don't see a change
// that is already complete.
2021-01-13 14:27:17 -06:00
err = n . writeChange ( ctx , nil , "" )
if err != nil {
return diags . Append ( err )
}
2020-10-02 10:54:58 -05:00
2021-01-13 14:27:17 -06:00
state = maybeTainted ( addr . Absolute ( ctx . Path ( ) ) , state , diffApply , diags . Err ( ) )
2020-09-28 15:40:40 -05:00
2021-03-26 08:40:33 -05:00
if state != nil {
// dependencies are always updated to match the configuration during apply
state . Dependencies = n . Dependencies
}
err = n . writeResourceInstanceState ( ctx , state , workingState )
2021-01-13 14:27:17 -06:00
if err != nil {
return diags . Append ( err )
2020-09-28 15:40:40 -05:00
}
2021-01-13 14:27:17 -06:00
// Run Provisioners
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 07:05:53 -06:00
createNew := ( diffApply . Action == plans . Create || diffApply . Action . IsReplace ( ) )
2021-01-13 14:08:53 -06:00
applyProvisionersDiags := n . evalApplyProvisioners ( ctx , state , createNew , configs . ProvisionerWhenCreate )
// the provisioner errors count as port of the apply error, so we can bundle the diags
2021-01-13 14:27:17 -06:00
diags = diags . Append ( applyProvisionersDiags )
2020-09-28 15:40:40 -05:00
2021-01-13 14:27:17 -06:00
state = maybeTainted ( addr . Absolute ( ctx . Path ( ) ) , state , diffApply , diags . Err ( ) )
2020-09-28 15:40:40 -05:00
2021-03-26 08:40:33 -05:00
err = n . writeResourceInstanceState ( ctx , state , workingState )
2021-01-13 14:27:17 -06:00
if err != nil {
return diags . Append ( err )
2020-09-28 15:40:40 -05:00
}
2021-01-13 14:27:17 -06:00
if createBeforeDestroyEnabled && diags . HasErrors ( ) {
2020-12-04 08:16:26 -06:00
if deposedKey == states . NotDeposed {
// This should never happen, and so it always indicates a bug.
// We should evaluate this node only if we've previously deposed
// an object as part of the same operation.
if diffApply != nil {
diags = diags . Append ( tfdiags . Sourceless (
tfdiags . Error ,
"Attempt to restore non-existent deposed object" ,
fmt . Sprintf (
"Terraform has encountered a bug where it would need to restore a deposed object for %s without knowing a deposed object key for that object. This occurred during a %s action. This is a bug in Terraform; please report it!" ,
addr , diffApply . Action ,
) ,
) )
} else {
diags = diags . Append ( tfdiags . Sourceless (
tfdiags . Error ,
"Attempt to restore non-existent deposed object" ,
fmt . Sprintf (
"Terraform has encountered a bug where it would need to restore a deposed object for %s without knowing a deposed object key for that object. This is a bug in Terraform; please report it!" ,
addr ,
) ,
) )
}
} else {
restored := ctx . State ( ) . MaybeRestoreResourceInstanceDeposed ( addr . Absolute ( ctx . Path ( ) ) , deposedKey )
if restored {
2020-12-10 08:55:50 -06:00
log . Printf ( "[TRACE] managedResourceExecute: %s deposed object %s was restored as the current object" , addr , deposedKey )
2020-12-04 08:16:26 -06:00
} else {
2020-12-10 08:55:50 -06:00
log . Printf ( "[TRACE] managedResourceExecute: %s deposed object %s remains deposed" , addr , deposedKey )
2020-12-04 08:16:26 -06:00
}
2020-09-28 15:40:40 -05:00
}
}
2021-01-13 14:14:32 -06:00
diags = diags . Append ( n . postApplyHook ( ctx , state , diags . Err ( ) ) )
2020-12-10 08:55:50 -06:00
diags = diags . Append ( updateStateHook ( ctx ) )
2020-10-28 12:47:04 -05:00
return diags
2018-09-11 16:22:25 -05:00
}
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 07:50:30 -06:00
// checkPlannedChange produces errors if the _actual_ expected value is not
// compatible with what was recorded in the plan.
//
// Errors here are most often indicative of a bug in the provider, so our error
// messages will report with that in mind. It's also possible that there's a bug
// in Terraform's Core's own "proposed new value" code in EvalDiff.
func ( n * NodeApplyableResourceInstance ) checkPlannedChange ( ctx EvalContext , plannedChange , actualChange * plans . ResourceInstanceChange , providerSchema * ProviderSchema ) tfdiags . Diagnostics {
var diags tfdiags . Diagnostics
addr := n . ResourceInstanceAddr ( ) . Resource
schema , _ := providerSchema . SchemaForResourceAddr ( addr . ContainingResource ( ) )
if schema == nil {
// Should be caught during validation, so we don't bother with a pretty error here
diags = diags . Append ( fmt . Errorf ( "provider does not support %q" , addr . Resource . Type ) )
return diags
}
absAddr := addr . Absolute ( ctx . Path ( ) )
2020-12-10 08:55:50 -06:00
log . Printf ( "[TRACE] checkPlannedChange: Verifying that actual change (action %s) matches planned change (action %s)" , actualChange . Action , plannedChange . Action )
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 07:50:30 -06:00
if plannedChange . Action != actualChange . Action {
switch {
case plannedChange . Action == plans . Update && actualChange . Action == plans . NoOp :
// It's okay for an update to become a NoOp once we've filled in
// all of the unknown values, since the final values might actually
// match what was there before after all.
log . Printf ( "[DEBUG] After incorporating new values learned so far during apply, %s change has become NoOp" , absAddr )
case ( plannedChange . Action == plans . CreateThenDelete && actualChange . Action == plans . DeleteThenCreate ) ||
( plannedChange . Action == plans . DeleteThenCreate && actualChange . Action == plans . CreateThenDelete ) :
// If the order of replacement changed, then that is a bug in terraform
diags = diags . Append ( tfdiags . Sourceless (
tfdiags . Error ,
"Terraform produced inconsistent final plan" ,
fmt . Sprintf (
"When expanding the plan for %s to include new values learned so far during apply, the planned action changed from %s to %s.\n\nThis is a bug in Terraform and should be reported." ,
absAddr , plannedChange . Action , actualChange . Action ,
) ,
) )
default :
diags = diags . Append ( tfdiags . Sourceless (
tfdiags . Error ,
"Provider produced inconsistent final plan" ,
fmt . Sprintf (
"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." ,
absAddr , n . ResolvedProvider . Provider . String ( ) ,
plannedChange . Action , actualChange . Action ,
) ,
) )
}
}
errs := objchange . AssertObjectCompatible ( schema , plannedChange . After , actualChange . After )
for _ , err := range errs {
diags = diags . Append ( tfdiags . Sourceless (
tfdiags . Error ,
"Provider produced inconsistent final plan" ,
fmt . Sprintf (
"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." ,
absAddr , n . ResolvedProvider . Provider . String ( ) , tfdiags . FormatError ( err ) ,
) ,
) )
}
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 07:05:53 -06:00
// maybeTainted takes the resource addr, new value, planned change, and possible
// error from an apply operation and return a new instance object marked as
// tainted if it appears that a create operation has failed.
func maybeTainted ( addr addrs . AbsResourceInstance , state * states . ResourceInstanceObject , change * plans . ResourceInstanceChange , err error ) * states . ResourceInstanceObject {
if state == nil || change == nil || err == nil {
return state
}
if state . Status == states . ObjectTainted {
log . Printf ( "[TRACE] maybeTainted: %s was already tainted, so nothing to do" , addr )
return state
}
if change . Action == plans . Create {
// If there are errors during a _create_ then the object is
// in an undefined state, and so we'll mark it as tainted so
// we can try again on the next run.
//
// We don't do this for other change actions because errors
// during updates will often not change the remote object at all.
// If there _were_ changes prior to the error, it's the provider's
// responsibility to record the effect of those changes in the
// object value it returned.
log . Printf ( "[TRACE] maybeTainted: %s encountered an error during creation, so it is now marked as tainted" , addr )
return state . AsTainted ( )
}
return state
}