Skip to content

Commit

Permalink
plans: indicate when resource deleted due to move (#31695)
Browse files Browse the repository at this point in the history
Add a new ChangeReason, ReasonDeleteBecauseNoMoveTarget, to provide better
information in cases where a planned deletion is due to moving a resource to
a target not in configuration.

Consider a case in which a resource instance exists in state at address A, and
the user adds a moved block to move A to address B. Whether by the user's
intention or not, address B does not exist in configuration.
Terraform combines the move from A to B, and the lack of configuration for B,
into a single delete action for the (previously nonexistent) entity B.
Prior to this commit, the Terraform plan will report that resource B will be
destroyed because it does not exist in configuration, without explicitly
connecting this to the move.

This commit provides the user an additional clue as to what has happened, in a
case in which Terraform has elided a user's action and inaction into one
potentially destructive change.
  • Loading branch information
kmoe committed Aug 30, 2022
1 parent 1347aa2 commit dec48a8
Show file tree
Hide file tree
Showing 9 changed files with 53 additions and 18 deletions.
2 changes: 2 additions & 0 deletions internal/command/format/diff.go
Expand Up @@ -115,6 +115,8 @@ func ResourceChange(
switch change.ActionReason {
case plans.ResourceInstanceDeleteBecauseNoResourceConfig:
buf.WriteString(fmt.Sprintf("\n # (because %s is not in configuration)", addr.Resource.Resource))
case plans.ResourceInstanceDeleteBecauseNoMoveTarget:
buf.WriteString(fmt.Sprintf("\n # (because %s was moved to %s, which is not in configuration)", change.PrevRunAddr, addr.Resource.Resource))
case plans.ResourceInstanceDeleteBecauseNoModule:
// FIXME: Ideally we'd truncate addr.Module to reflect the earliest
// step that doesn't exist, so it's clearer which call this refers
Expand Down
2 changes: 2 additions & 0 deletions internal/command/jsonplan/plan.go
Expand Up @@ -413,6 +413,8 @@ func (p *plan) marshalResourceChanges(resources []*plans.ResourceInstanceChangeS
r.ActionReason = "delete_because_each_key"
case plans.ResourceInstanceDeleteBecauseNoModule:
r.ActionReason = "delete_because_no_module"
case plans.ResourceInstanceDeleteBecauseNoMoveTarget:
r.ActionReason = "delete_because_no_move_target"
case plans.ResourceInstanceReadBecauseConfigUnknown:
r.ActionReason = "read_because_config_unknown"
case plans.ResourceInstanceReadBecauseDependencyPending:
Expand Down
3 changes: 3 additions & 0 deletions internal/command/views/json/change.go
Expand Up @@ -80,6 +80,7 @@ const (
ReasonDeleteBecauseCountIndex ChangeReason = "delete_because_count_index"
ReasonDeleteBecauseEachKey ChangeReason = "delete_because_each_key"
ReasonDeleteBecauseNoModule ChangeReason = "delete_because_no_module"
ReasonDeleteBecauseNoMoveTarget ChangeReason = "delete_because_no_move_target"
ReasonReadBecauseConfigUnknown ChangeReason = "read_because_config_unknown"
ReasonReadBecauseDependencyPending ChangeReason = "read_because_dependency_pending"
)
Expand Down Expand Up @@ -108,6 +109,8 @@ func changeReason(reason plans.ResourceInstanceChangeActionReason) ChangeReason
return ReasonDeleteBecauseNoModule
case plans.ResourceInstanceReadBecauseConfigUnknown:
return ReasonReadBecauseConfigUnknown
case plans.ResourceInstanceDeleteBecauseNoMoveTarget:
return ReasonDeleteBecauseNoMoveTarget
case plans.ResourceInstanceReadBecauseDependencyPending:
return ReasonReadBecauseDependencyPending
default:
Expand Down
6 changes: 6 additions & 0 deletions internal/plans/changes.go
Expand Up @@ -427,6 +427,12 @@ const (
// specific reasons for a particular instance to no longer be declared.
ResourceInstanceDeleteBecauseNoModule ResourceInstanceChangeActionReason = 'M'

// ResourceInstanceDeleteBecauseNoMoveTarget indicates that the resource
// address appears as the target ("to") in a moved block, but no
// configuration exists for that resource. According to our move rules,
// this combination evaluates to a deletion of the "new" resource.
ResourceInstanceDeleteBecauseNoMoveTarget ResourceInstanceChangeActionReason = 'A'

// ResourceInstanceReadBecauseConfigUnknown indicates that the resource
// must be read during apply (rather than during planning) because its
// configuration contains unknown values. This reason applies only to
Expand Down
18 changes: 12 additions & 6 deletions internal/plans/internal/planproto/planfile.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions internal/plans/internal/planproto/planfile.proto
Expand Up @@ -151,6 +151,7 @@ enum ResourceInstanceActionReason {
REPLACE_BY_TRIGGERS = 9;
READ_BECAUSE_CONFIG_UNKNOWN = 10;
READ_BECAUSE_DEPENDENCY_PENDING = 11;
DELETE_BECAUSE_NO_MOVE_TARGET = 12;
}

message ResourceInstanceChange {
Expand Down
4 changes: 4 additions & 0 deletions internal/plans/planfile/tfplan.go
Expand Up @@ -331,6 +331,8 @@ func resourceChangeFromTfplan(rawChange *planproto.ResourceInstanceChange) (*pla
ret.ActionReason = plans.ResourceInstanceReadBecauseConfigUnknown
case planproto.ResourceInstanceActionReason_READ_BECAUSE_DEPENDENCY_PENDING:
ret.ActionReason = plans.ResourceInstanceReadBecauseDependencyPending
case planproto.ResourceInstanceActionReason_DELETE_BECAUSE_NO_MOVE_TARGET:
ret.ActionReason = plans.ResourceInstanceDeleteBecauseNoMoveTarget
default:
return nil, fmt.Errorf("resource has invalid action reason %s", rawChange.ActionReason)
}
Expand Down Expand Up @@ -705,6 +707,8 @@ func resourceChangeToTfplan(change *plans.ResourceInstanceChangeSrc) (*planproto
ret.ActionReason = planproto.ResourceInstanceActionReason_READ_BECAUSE_CONFIG_UNKNOWN
case plans.ResourceInstanceReadBecauseDependencyPending:
ret.ActionReason = planproto.ResourceInstanceActionReason_READ_BECAUSE_DEPENDENCY_PENDING
case plans.ResourceInstanceDeleteBecauseNoMoveTarget:
ret.ActionReason = planproto.ResourceInstanceActionReason_DELETE_BECAUSE_NO_MOVE_TARGET
default:
return nil, fmt.Errorf("resource %s has unsupported action reason %s", change.Addr, change.ActionReason)
}
Expand Down
28 changes: 16 additions & 12 deletions internal/plans/resourceinstancechangeactionreason_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions internal/terraform/node_resource_plan_orphan.go
Expand Up @@ -157,6 +157,13 @@ func (n *NodePlannableResourceInstanceOrphan) managedResourceExecute(ctx EvalCon
func (n *NodePlannableResourceInstanceOrphan) deleteActionReason(ctx EvalContext) plans.ResourceInstanceChangeActionReason {
cfg := n.Config
if cfg == nil {
if !n.Addr.Equal(n.prevRunAddr(ctx)) {
// This means the resource was moved - see also
// ResourceInstanceChange.Moved() which calculates
// this the same way.
return plans.ResourceInstanceDeleteBecauseNoMoveTarget
}

return plans.ResourceInstanceDeleteBecauseNoResourceConfig
}

Expand Down

0 comments on commit dec48a8

Please sign in to comment.