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
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/plans"
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
"github.com/hashicorp/terraform/plans/objchange"
2018-09-11 16:22:25 -05:00
"github.com/hashicorp/terraform/states"
2019-05-14 18:09:19 -05:00
"github.com/hashicorp/terraform/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
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-08 09:43:04 -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
}
2020-12-04 11:44:40 -06:00
// We don't write dependencies for datasources
diags = diags . Append ( n . writeResourceInstanceState ( ctx , state , nil , 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-10-28 12:47:04 -05:00
diags = diags . Append ( UpdateStateHook ( ctx ) )
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 createNew bool
var createBeforeDestroyEnabled bool
var deposedKey states . DeposedKey
2020-09-28 15:40:40 -05:00
addr := n . ResourceInstanceAddr ( ) . Resource
provider , 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
}
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
state , err = n . readResourceInstanceState ( ctx , n . ResourceInstanceAddr ( ) )
2020-12-04 08:16:26 -06:00
diags = diags . Append ( err )
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.
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 , state , planDiags := n . plan ( ctx , diff , state , false )
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
}
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
state , err = n . readResourceInstanceState ( ctx , n . ResourceInstanceAddr ( ) )
2020-12-04 08:16:26 -06:00
diags = diags . Append ( err )
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
}
2020-12-04 08:16:26 -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
}
var applyError error
evalApply := & EvalApply {
Addr : addr ,
Config : n . Config ,
State : & state ,
Change : & diffApply ,
Provider : & provider ,
ProviderAddr : n . ResolvedProvider ,
ProviderMetas : n . ProviderMetas ,
ProviderSchema : & providerSchema ,
Output : & state ,
Error : & applyError ,
CreateNew : & createNew ,
CreateBeforeDestroy : n . CreateBeforeDestroy ( ) ,
}
2020-10-28 12:47:04 -05:00
diags = diags . Append ( evalApply . Eval ( ctx ) )
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
}
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.
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-10-02 10:54:58 -05:00
2020-09-28 15:40:40 -05:00
evalMaybeTainted := & EvalMaybeTainted {
Addr : addr ,
State : & state ,
Change : & diffApply ,
Error : & applyError ,
}
2020-10-28 12:47:04 -05:00
diags = diags . Append ( evalMaybeTainted . Eval ( ctx ) )
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
}
2020-12-04 11:44:40 -06:00
diags = diags . Append ( n . writeResourceInstanceState ( ctx , state , n . Dependencies , 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
}
applyProvisioners := & EvalApplyProvisioners {
Addr : addr ,
State : & state , // EvalApplyProvisioners will skip if already tainted
ResourceConfig : n . Config ,
CreateNew : & createNew ,
Error : & applyError ,
When : configs . ProvisionerWhenCreate ,
}
2020-10-28 12:47:04 -05:00
diags = diags . Append ( applyProvisioners . Eval ( ctx ) )
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
}
evalMaybeTainted = & EvalMaybeTainted {
Addr : addr ,
State : & state ,
Change : & diffApply ,
Error : & applyError ,
}
2020-10-28 12:47:04 -05:00
diags = diags . Append ( evalMaybeTainted . Eval ( ctx ) )
2020-10-27 17:16:28 -05:00
if diags . HasErrors ( ) {
2020-10-28 12:47:04 -05:00
return diags
2018-09-11 16:22:25 -05:00
}
2020-09-28 15:40:40 -05:00
2020-12-04 11:44:40 -06:00
diags = diags . Append ( n . writeResourceInstanceState ( ctx , state , n . Dependencies , 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
}
if createBeforeDestroyEnabled && applyError != nil {
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 {
log . Printf ( "[TRACE] EvalMaybeRestoreDeposedObject: %s deposed object %s was restored as the current object" , addr , deposedKey )
} else {
log . Printf ( "[TRACE] EvalMaybeRestoreDeposedObject: %s deposed object %s remains deposed" , addr , deposedKey )
}
2020-09-28 15:40:40 -05:00
}
}
2020-12-04 08:16:26 -06:00
if diags . HasErrors ( ) {
return diags
2020-09-28 15:40:40 -05:00
}
2020-12-04 08:16:26 -06: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 . postApplyHook ( ctx , state , & applyError ) )
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
}
2020-10-28 12:47:04 -05:00
diags = diags . Append ( UpdateStateHook ( ctx ) )
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 ( ) )
log . Printf ( "[TRACE] EvalCheckPlannedChange: Verifying that actual change (action %s) matches planned change (action %s)" , actualChange . Action , plannedChange . Action )
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
}