Skip to content

Commit 22b36d1

Browse files
Field for the previous address of each resource instance in the plan
In order to expose the effect of any relevant "moved" statements we dealt with prior to creating the plan, we'll record with each ResourceInstanceChange both is current address and the address it was tracked at for the previous run. To save consumers of these objects from having to special-case the situation where there _was_ no previous run (e.g. because this is a Create change), we'll just pretend the previous run address was the same as the current address in that case, the same as for an update without any renaming in effect. This includes a breaking change to the plan file format, but one that doesn't require a version number increment because there is no ambiguity between the two formats and so mismatched parsers will already fail with an error message. As of this commit we've just added the new field but not yet populated it with any useful information: it always just matches Addr. A future commit will wire this up to the result of applying the moves so that we can populate it correctly. We also don't yet expose this new information anywhere in the UI layer.
1 parent c29e10b commit 22b36d1

7 files changed

Lines changed: 241 additions & 344 deletions

File tree

internal/plans/changes.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,19 @@ type ResourceInstanceChange struct {
147147
// will apply to.
148148
Addr addrs.AbsResourceInstance
149149

150+
// PrevRunAddr is the absolute address that this resource instance had at
151+
// the conclusion of a previous run.
152+
//
153+
// This will typically be the same as Addr, but can be different if the
154+
// previous resource instance was subject to a "moved" block that we
155+
// handled in the process of creating this plan.
156+
//
157+
// For the initial creation of a resource instance there isn't really any
158+
// meaningful "previous run address", but PrevRunAddr will still be set
159+
// equal to Addr in that case in order to simplify logic elsewhere which
160+
// aims to detect and react to the movement of instances between addresses.
161+
PrevRunAddr addrs.AbsResourceInstance
162+
150163
// DeposedKey is the identifier for a deposed object associated with the
151164
// given instance, or states.NotDeposed if this change applies to the
152165
// current object.
@@ -203,8 +216,15 @@ func (rc *ResourceInstanceChange) Encode(ty cty.Type) (*ResourceInstanceChangeSr
203216
if err != nil {
204217
return nil, err
205218
}
219+
prevRunAddr := rc.PrevRunAddr
220+
if prevRunAddr.Resource.Resource.Type == "" {
221+
// Suggests an old caller that hasn't been properly updated to
222+
// populate this yet.
223+
prevRunAddr = rc.Addr
224+
}
206225
return &ResourceInstanceChangeSrc{
207226
Addr: rc.Addr,
227+
PrevRunAddr: prevRunAddr,
208228
DeposedKey: rc.DeposedKey,
209229
ProviderAddr: rc.ProviderAddr,
210230
ChangeSrc: *cs,

internal/plans/changes_src.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,19 @@ type ResourceInstanceChangeSrc struct {
1616
// will apply to.
1717
Addr addrs.AbsResourceInstance
1818

19+
// PrevRunAddr is the absolute address that this resource instance had at
20+
// the conclusion of a previous run.
21+
//
22+
// This will typically be the same as Addr, but can be different if the
23+
// previous resource instance was subject to a "moved" block that we
24+
// handled in the process of creating this plan.
25+
//
26+
// For the initial creation of a resource instance there isn't really any
27+
// meaningful "previous run address", but PrevRunAddr will still be set
28+
// equal to Addr in that case in order to simplify logic elsewhere which
29+
// aims to detect and react to the movement of instances between addresses.
30+
PrevRunAddr addrs.AbsResourceInstance
31+
1932
// DeposedKey is the identifier for a deposed object associated with the
2033
// given instance, or states.NotDeposed if this change applies to the
2134
// current object.
@@ -66,8 +79,15 @@ func (rcs *ResourceInstanceChangeSrc) Decode(ty cty.Type) (*ResourceInstanceChan
6679
if err != nil {
6780
return nil, err
6881
}
82+
prevRunAddr := rcs.PrevRunAddr
83+
if prevRunAddr.Resource.Resource.Type == "" {
84+
// Suggests an old caller that hasn't been properly updated to
85+
// populate this yet.
86+
prevRunAddr = rcs.Addr
87+
}
6988
return &ResourceInstanceChange{
7089
Addr: rcs.Addr,
90+
PrevRunAddr: prevRunAddr,
7191
DeposedKey: rcs.DeposedKey,
7292
ProviderAddr: rcs.ProviderAddr,
7393
Change: *change,

internal/plans/internal/planproto/planfile.pb.go

Lines changed: 118 additions & 244 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/plans/internal/planproto/planfile.proto

Lines changed: 16 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -129,34 +129,23 @@ enum ResourceInstanceActionReason {
129129
}
130130

131131
message ResourceInstanceChange {
132-
// module_path is an address to the module that defined this resource.
133-
// module_path is omitted for resources in the root module. For descendent modules
134-
// it is a string like module.foo.module.bar as would be seen at the beginning of a
135-
// resource address. The format of this string is not yet frozen and so external
136-
// callers should treat it as an opaque key for filtering purposes.
137-
string module_path = 1;
138-
139-
// mode is the resource mode.
140-
ResourceMode mode = 2;
141-
enum ResourceMode {
142-
managed = 0; // for "resource" blocks in configuration
143-
data = 1; // for "data" blocks in configuration
144-
}
145-
146-
// type is the resource type name, like "aws_instance".
147-
string type = 3;
132+
// addr is a string representation of the resource instance address that
133+
// this change will apply to.
134+
string addr = 13;
148135

149-
// name is the logical name of the resource as defined in configuration.
150-
// For example, in aws_instance.foo this would be "foo".
151-
string name = 4;
136+
// prev_run_addr is a string representation of the address at which
137+
// this resource instance was tracked during the previous apply operation.
138+
//
139+
// This is populated only if it would be different from addr due to
140+
// Terraform having reacted to refactoring annotations in the configuration.
141+
// If empty, the previous run address is the same as the current address.
142+
string prev_run_addr = 14;
152143

153-
// instance_key is either an integer index or a string key, depending on which iteration
154-
// attributes ("count" or "for_each") are being used for this resource. If none
155-
// are in use, this field is omitted.
156-
oneof instance_key {
157-
string str = 5;
158-
int64 int = 6;
159-
};
144+
// NOTE: Earlier versions of this format had fields 1 through 6 describing
145+
// various indivdual parts of "addr". We're now using our standard compact
146+
// string representation to capture the same information. We don't support
147+
// preserving plan files from one Terraform version to the next, so we
148+
// no longer declare nor accept those fields.
160149

161150
// deposed_key, if set, indicates that this change applies to a deposed
162151
// object for the indicated instance with the given deposed key. If not
@@ -169,8 +158,7 @@ message ResourceInstanceChange {
169158
string provider = 8;
170159

171160
// Description of the proposed change. May use "create", "read", "update",
172-
// "replace" and "delete" actions. "no-op" changes are not currently used here
173-
// but consumers must accept and discard them to allow for future expansion.
161+
// "replace", "delete" and "no-op" actions.
174162
Change change = 9;
175163

176164
// raw blob value provided by the provider as additional context for the

internal/plans/planfile/tfplan.go

Lines changed: 32 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"github.com/hashicorp/terraform/internal/plans"
1313
"github.com/hashicorp/terraform/internal/plans/internal/planproto"
1414
"github.com/hashicorp/terraform/internal/states"
15-
"github.com/hashicorp/terraform/internal/tfdiags"
1615
"github.com/hashicorp/terraform/version"
1716
"github.com/zclconf/go-cty/cty"
1817
)
@@ -157,12 +156,23 @@ func resourceChangeFromTfplan(rawChange *planproto.ResourceInstanceChange) (*pla
157156

158157
ret := &plans.ResourceInstanceChangeSrc{}
159158

160-
moduleAddr := addrs.RootModuleInstance
161-
if rawChange.ModulePath != "" {
162-
var diags tfdiags.Diagnostics
163-
moduleAddr, diags = addrs.ParseModuleInstanceStr(rawChange.ModulePath)
159+
if rawChange.Addr == "" {
160+
// If "Addr" isn't populated then seems likely that this is a plan
161+
// file created by an earlier version of Terraform, which had the
162+
// same information spread over various other fields:
163+
// ModulePath, Mode, Name, Type, and InstanceKey.
164+
return nil, fmt.Errorf("no instance address for resource instance change; perhaps this plan was created by a different version of Terraform?")
165+
}
166+
167+
instAddr, diags := addrs.ParseAbsResourceInstanceStr(rawChange.Addr)
168+
if diags.HasErrors() {
169+
return nil, fmt.Errorf("invalid resource instance address %q: %w", rawChange.Addr, diags.Err())
170+
}
171+
prevRunAddr := instAddr
172+
if rawChange.PrevRunAddr != "" {
173+
prevRunAddr, diags = addrs.ParseAbsResourceInstanceStr(rawChange.PrevRunAddr)
164174
if diags.HasErrors() {
165-
return nil, diags.Err()
175+
return nil, fmt.Errorf("invalid resource instance previous run address %q: %w", rawChange.PrevRunAddr, diags.Err())
166176
}
167177
}
168178

@@ -172,37 +182,8 @@ func resourceChangeFromTfplan(rawChange *planproto.ResourceInstanceChange) (*pla
172182
}
173183
ret.ProviderAddr = providerAddr
174184

175-
var mode addrs.ResourceMode
176-
switch rawChange.Mode {
177-
case planproto.ResourceInstanceChange_managed:
178-
mode = addrs.ManagedResourceMode
179-
case planproto.ResourceInstanceChange_data:
180-
mode = addrs.DataResourceMode
181-
default:
182-
return nil, fmt.Errorf("resource has invalid mode %s", rawChange.Mode)
183-
}
184-
185-
typeName := rawChange.Type
186-
name := rawChange.Name
187-
188-
resAddr := addrs.Resource{
189-
Mode: mode,
190-
Type: typeName,
191-
Name: name,
192-
}
193-
194-
var instKey addrs.InstanceKey
195-
switch rawTk := rawChange.InstanceKey.(type) {
196-
case nil:
197-
case *planproto.ResourceInstanceChange_Int:
198-
instKey = addrs.IntKey(rawTk.Int)
199-
case *planproto.ResourceInstanceChange_Str:
200-
instKey = addrs.StringKey(rawTk.Str)
201-
default:
202-
return nil, fmt.Errorf("instance of %s has invalid key type %T", resAddr.Absolute(moduleAddr), rawChange.InstanceKey)
203-
}
204-
205-
ret.Addr = resAddr.Instance(instKey).Absolute(moduleAddr)
185+
ret.Addr = instAddr
186+
ret.PrevRunAddr = prevRunAddr
206187

207188
if rawChange.DeposedKey != "" {
208189
if len(rawChange.DeposedKey) != 8 {
@@ -454,35 +435,20 @@ func writeTfplan(plan *plans.Plan, w io.Writer) error {
454435
func resourceChangeToTfplan(change *plans.ResourceInstanceChangeSrc) (*planproto.ResourceInstanceChange, error) {
455436
ret := &planproto.ResourceInstanceChange{}
456437

457-
ret.ModulePath = change.Addr.Module.String()
458-
459-
relAddr := change.Addr.Resource
460-
461-
switch relAddr.Resource.Mode {
462-
case addrs.ManagedResourceMode:
463-
ret.Mode = planproto.ResourceInstanceChange_managed
464-
case addrs.DataResourceMode:
465-
ret.Mode = planproto.ResourceInstanceChange_data
466-
default:
467-
return nil, fmt.Errorf("resource %s has unsupported mode %s", relAddr, relAddr.Resource.Mode)
438+
if change.PrevRunAddr.Resource.Resource.Type == "" {
439+
// Suggests that an old caller wasn't yet updated to populate this
440+
// properly. All code that generates plans should populate this field,
441+
// even if it's just to write in the same value as in change.Addr.
442+
change.PrevRunAddr = change.Addr
468443
}
469444

470-
ret.Type = relAddr.Resource.Type
471-
ret.Name = relAddr.Resource.Name
472-
473-
switch tk := relAddr.Key.(type) {
474-
case nil:
475-
// Nothing to do, then.
476-
case addrs.IntKey:
477-
ret.InstanceKey = &planproto.ResourceInstanceChange_Int{
478-
Int: int64(tk),
479-
}
480-
case addrs.StringKey:
481-
ret.InstanceKey = &planproto.ResourceInstanceChange_Str{
482-
Str: string(tk),
483-
}
484-
default:
485-
return nil, fmt.Errorf("resource %s has unsupported instance key type %T", relAddr, relAddr.Key)
445+
ret.Addr = change.Addr.String()
446+
ret.PrevRunAddr = change.PrevRunAddr.String()
447+
if ret.PrevRunAddr == ret.Addr {
448+
// In the on-disk format we leave PrevRunAddr unpopulated in the common
449+
// case where it's the same as Addr, and then fill it back in again on
450+
// read.
451+
ret.PrevRunAddr = ""
486452
}
487453

488454
ret.DeposedKey = string(change.DeposedKey)
@@ -500,7 +466,7 @@ func resourceChangeToTfplan(change *plans.ResourceInstanceChangeSrc) (*planproto
500466

501467
valChange, err := changeToTfplan(&change.ChangeSrc)
502468
if err != nil {
503-
return nil, fmt.Errorf("failed to serialize resource %s change: %s", relAddr, err)
469+
return nil, fmt.Errorf("failed to serialize resource %s change: %s", change.Addr, err)
504470
}
505471
ret.Change = valChange
506472

@@ -514,7 +480,7 @@ func resourceChangeToTfplan(change *plans.ResourceInstanceChangeSrc) (*planproto
514480
case plans.ResourceInstanceReplaceByRequest:
515481
ret.ActionReason = planproto.ResourceInstanceActionReason_REPLACE_BY_REQUEST
516482
default:
517-
return nil, fmt.Errorf("resource %s has unsupported action reason %s", relAddr, change.ActionReason)
483+
return nil, fmt.Errorf("resource %s has unsupported action reason %s", change.Addr, change.ActionReason)
518484
}
519485

520486
if len(change.Private) > 0 {

internal/plans/planfile/tfplan_test.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ func TestTFPlanRoundTrip(t *testing.T) {
5757
Type: "test_thing",
5858
Name: "woot",
5959
}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
60+
PrevRunAddr: addrs.Resource{
61+
Mode: addrs.ManagedResourceMode,
62+
Type: "test_thing",
63+
Name: "woot",
64+
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
6065
ProviderAddr: addrs.AbsProviderConfig{
6166
Provider: addrs.NewDefaultProvider("test"),
6267
Module: addrs.RootModule,
@@ -93,7 +98,12 @@ func TestTFPlanRoundTrip(t *testing.T) {
9398
Mode: addrs.ManagedResourceMode,
9499
Type: "test_thing",
95100
Name: "woot",
96-
}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
101+
}.Instance(addrs.IntKey(1)).Absolute(addrs.RootModuleInstance),
102+
PrevRunAddr: addrs.Resource{
103+
Mode: addrs.ManagedResourceMode,
104+
Type: "test_thing",
105+
Name: "woot",
106+
}.Instance(addrs.IntKey(1)).Absolute(addrs.RootModuleInstance),
97107
DeposedKey: "foodface",
98108
ProviderAddr: addrs.AbsProviderConfig{
99109
Provider: addrs.NewDefaultProvider("test"),
@@ -214,6 +224,11 @@ func TestTFPlanRoundTripDestroy(t *testing.T) {
214224
Type: "test_thing",
215225
Name: "woot",
216226
}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
227+
PrevRunAddr: addrs.Resource{
228+
Mode: addrs.ManagedResourceMode,
229+
Type: "test_thing",
230+
Name: "woot",
231+
}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
217232
ProviderAddr: addrs.AbsProviderConfig{
218233
Provider: addrs.NewDefaultProvider("test"),
219234
Module: addrs.RootModule,

internal/terraform/node_resource_abstract_instance.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -392,8 +392,9 @@ func (n *NodeAbstractResourceInstance) planDestroy(ctx EvalContext, currentState
392392
// that we checked something and concluded no changes were needed
393393
// vs. that something being entirely excluded e.g. due to -target.
394394
noop := &plans.ResourceInstanceChange{
395-
Addr: absAddr,
396-
DeposedKey: deposedKey,
395+
Addr: absAddr,
396+
PrevRunAddr: absAddr, // TODO-PrevRunAddr: If this instance was moved/renamed in this run, record its old address
397+
DeposedKey: deposedKey,
397398
Change: plans.Change{
398399
Action: plans.NoOp,
399400
Before: cty.NullVal(cty.DynamicPseudoType),
@@ -419,8 +420,9 @@ func (n *NodeAbstractResourceInstance) planDestroy(ctx EvalContext, currentState
419420
// Plan is always the same for a destroy. We don't need the provider's
420421
// help for this one.
421422
plan := &plans.ResourceInstanceChange{
422-
Addr: absAddr,
423-
DeposedKey: deposedKey,
423+
Addr: absAddr,
424+
PrevRunAddr: absAddr, // TODO-PrevRunAddr: If this instance was moved/renamed in this run, record its old address
425+
DeposedKey: deposedKey,
424426
Change: plans.Change{
425427
Action: plans.Delete,
426428
Before: currentState.Value,
@@ -444,7 +446,7 @@ func (n *NodeAbstractResourceInstance) planDestroy(ctx EvalContext, currentState
444446
return plan, diags
445447
}
446448

447-
// writeChange saves a planned change for an instance object into the set of
449+
// writeChange saves a planned change for an instance object into the set of
448450
// global planned changes.
449451
func (n *NodeAbstractResourceInstance) writeChange(ctx EvalContext, change *plans.ResourceInstanceChange, deposedKey states.DeposedKey) error {
450452
changes := ctx.Changes()
@@ -469,6 +471,16 @@ func (n *NodeAbstractResourceInstance) writeChange(ctx EvalContext, change *plan
469471
// Should never happen, and indicates a bug in the caller.
470472
panic("inconsistent address and/or deposed key in writeChange")
471473
}
474+
if change.PrevRunAddr.Resource.Resource.Type == "" {
475+
// Should never happen, and indicates a bug in the caller.
476+
// (The change.Encode function actually has its own fixup to just
477+
// quietly make this match change.Addr in the incorrect case, but we
478+
// intentionally panic here in order to catch incorrect callers where
479+
// the stack trace will hopefully be actually useful. The tolerance
480+
// at the next layer down is mainly to accommodate sloppy input in
481+
// older tests.)
482+
panic("unpopulated ResourceInstanceChange.PrevRunAddr in writeChange")
483+
}
472484

473485
ri := n.Addr.Resource
474486
schema, _ := providerSchema.SchemaForResourceAddr(ri.Resource)
@@ -1054,6 +1066,7 @@ func (n *NodeAbstractResourceInstance) plan(
10541066
// Update our return plan
10551067
plan = &plans.ResourceInstanceChange{
10561068
Addr: n.Addr,
1069+
PrevRunAddr: n.Addr, // TODO-PrevRunAddr: If this instance was moved/renamed in this run, record its old address
10571070
Private: plannedPrivate,
10581071
ProviderAddr: n.ResolvedProvider,
10591072
Change: plans.Change{
@@ -1515,6 +1528,7 @@ func (n *NodeAbstractResourceInstance) planDataSource(ctx EvalContext, currentSt
15151528
// value containing unknowns from PlanDataResourceObject.
15161529
plannedChange := &plans.ResourceInstanceChange{
15171530
Addr: n.Addr,
1531+
PrevRunAddr: n.Addr, // data resources are not refactorable
15181532
ProviderAddr: n.ResolvedProvider,
15191533
Change: plans.Change{
15201534
Action: plans.Read,

0 commit comments

Comments
 (0)