From ac9a26d5b96f8b6beeabfcda3d9db582b1cd7dff Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Mon, 9 Feb 2026 14:53:27 -0800 Subject: [PATCH] exec: ManagedDepose and ManagedChangeAddr take object as operand These both effectively had the behavior of ResourceInstancePrior embedded in them, reading something from the state and change its address as a single compound operation. In the case of ManagedDepose we need to split these up for the CreateThenDestroy variant of "replace", because we want to make sure the final plans are valid before we depose anything and we need the prior state to produce the final plan. (Actually using that will follow in a subsequent commit.) This isn't actually necessary for ManageChangeAddr, but splitting it keeps these two operations consistent in how they interact with the rest of the operations. Due to how the existing states.SyncState works we're not actually making good use of the data flow of these objects right now, but in a future world where we're no longer using the old state models hopefully the state API will switch to an approach that's more aligned with how the execgraph operations are modeled. Signed-off-by: Martin Atkins --- .../applying/operations_resource_managed.go | 48 ++++++++++++---- internal/engine/internal/exec/operations.go | 55 +++++++++++-------- internal/engine/internal/exec/resource.go | 10 ++++ internal/engine/internal/execgraph/builder.go | 11 ++-- .../engine/internal/execgraph/compiler_ops.go | 20 ++++--- .../internal/execgraph/graph_unmarshal.go | 20 ++++--- .../internal/execgraph/operations_test.go | 16 +++--- internal/engine/planning/execgraph_managed.go | 20 +++---- .../engine/planning/execgraph_managed_test.go | 33 +++++++++++ 9 files changed, 160 insertions(+), 73 deletions(-) diff --git a/internal/engine/applying/operations_resource_managed.go b/internal/engine/applying/operations_resource_managed.go index 3d7a6bc3cb..30fc0db895 100644 --- a/internal/engine/applying/operations_resource_managed.go +++ b/internal/engine/applying/operations_resource_managed.go @@ -311,18 +311,27 @@ func (ops *execOperations) ManagedApply( // ManagedDepose implements [exec.Operations]. func (ops *execOperations) ManagedDepose( ctx context.Context, - instAddr addrs.AbsResourceInstance, + currentObj *exec.ResourceInstanceObject, ) (*exec.ResourceInstanceObject, tfdiags.Diagnostics) { - log.Printf("[TRACE] apply phase: ManagedDepose %s", instAddr) var diags tfdiags.Diagnostics - - deposedKey := ops.workingState.DeposeResourceInstanceObject(instAddr) - if deposedKey == states.NotDeposed { - // This means that there was no "current" object to depose, and - // so we'll return nil to represent that there's nothing here. + if currentObj == nil { + log.Println("[TRACE] apply phase: ManagedDepose with nil object (ignored)") return nil, diags } - return ops.resourceInstanceStateObject(ctx, ops.workingState, instAddr, deposedKey) + log.Printf("[TRACE] apply phase: ManagedDepose %s", currentObj.InstanceAddr) + + deposedKey := ops.workingState.DeposeResourceInstanceObject(currentObj.InstanceAddr) + if deposedKey == states.NotDeposed { + // We should not get here with a correctly-constructed execution graph + // because currentObj being non-nil means that there should definitely + // be something to depose. + diags = diags.Append(fmt.Errorf( + "failed to depose the current object for %s; this is a bug in OpenTofu", + currentObj.InstanceAddr, + )) + return nil, diags + } + return currentObj.IntoDeposed(deposedKey), diags } // ManagedAlreadyDeposed implements [exec.Operations]. @@ -341,9 +350,24 @@ func (ops *execOperations) ManagedAlreadyDeposed( // ManagedChangeAddr implements [exec.Operations]. func (ops *execOperations) ManagedChangeAddr( ctx context.Context, - currentInstAddr, newInstAddr addrs.AbsResourceInstance, + currentObj *exec.ResourceInstanceObject, + newAddr addrs.AbsResourceInstance, ) (*exec.ResourceInstanceObject, tfdiags.Diagnostics) { - log.Printf("[TRACE] apply phase: ManagedChangeAddr from %s to %s", currentInstAddr, newInstAddr) - // TODO: Implement - panic("ManagedChangeAddr not yet implemented") + var diags tfdiags.Diagnostics + if currentObj == nil { + log.Println("[TRACE] apply phase: ManagedChangeAddr with nil object (ignored)") + return nil, diags + } + log.Printf("[TRACE] apply phase: ManagedChangeAddr from %s to %s", currentObj.InstanceAddr, newAddr) + if !ops.workingState.MaybeMoveResourceInstance(currentObj.InstanceAddr, newAddr) { + // We should not get here with a correctly-constructed execution graph + // because currentObj being non-nil means that there should definitely + // be something to move. + diags = diags.Append(fmt.Errorf( + "failed to move %s to %s; this is a bug in OpenTofu", + currentObj.InstanceAddr, newAddr, + )) + return nil, diags + } + return currentObj.WithNewAddr(newAddr), diags } diff --git a/internal/engine/internal/exec/operations.go b/internal/engine/internal/exec/operations.go index 21c5d624ed..6bd5d0ffd2 100644 --- a/internal/engine/internal/exec/operations.go +++ b/internal/engine/internal/exec/operations.go @@ -191,21 +191,26 @@ type Operations interface { providerClient *ProviderClient, ) (*ResourceInstanceObject, tfdiags.Diagnostics) - // ManagedDepose transforms the "current" object associated with the given - // resource instance address into a "deposed" object for the same resource - // instance, and then returns the description of the now-deposed object. - // - // If there is no current object associated with that resource instance, - // this returns nil without changing anything. + // ManagedDepose takes a "current" object for some resource instance and + // changes it to be a "deposed" object for the same resource instance, + // returning a new representation of the object with its + // pseudorandomly-chosen unique DeposedKey. // // When using this as part of a "create then destroy" replace operation, // a correct execution graph arranges for the result to be propagated into // the "fallback" argument of a subsequent [Operations.ManagedApply] call, // so that the deposed object can be restored back to current if the // apply operation fails to the extent that no new object is created at all. + // + // The given object must not already have "DeposedKey" set, because that + // would make it a deposed object instead of a current object. + // If the given object is nil then this returns nil without changing + // anything. In practice though the planning engine should not include + // this operation unless it found an existing current object that needs to + // be deposed as part of a create-then-destroy "replace" change. ManagedDepose( ctx context.Context, - instAddr addrs.AbsResourceInstance, + object *ResourceInstanceObject, ) (*ResourceInstanceObject, tfdiags.Diagnostics) // ManagedAlreadyDeposed returns a deposed object from the prior state, @@ -217,6 +222,9 @@ type Operations interface { // That occurs only when a previous plan/apply round encountered an error // partway through a "create then destroy" replace operation where both // the newly-created object and the previously-existing object still exist. + // In that case, this operation serves a similar purpose to + // [Operations.ResourceInstancePrior] but returns a deposed object rather + // than a current object. // // [Operations.ManagedDepose] deals with the more common case where a // previously-"current" object becomes deposed during the apply phase as @@ -227,25 +235,28 @@ type Operations interface { deposedKey states.DeposedKey, ) (*ResourceInstanceObject, tfdiags.Diagnostics) - // ManageChangeAddr rebinds the current object associated with - // currentInstAddr to be associated with newInstAddr instead, and then - // returns that object with its updated address. + // ManageChangeAddr rebinds the given object to be associated with + // newInstAddr instead, and then returns a new representation of that object + // with its updated address. // - // This is used in place of [Operations.ResourceInstancePrior] whenever a - // resource instance address is being moved to a new address. The move - // and the read from the state are combined into a single action so that - // we can treat this as an atomic operation where there's no intermediate - // state where the relevant object is associated with either neither or both - // of the two addresses. + // This is used between [Operations.ResourceInstancePrior] and + // [Operations.ManagedFinalPlan] whenever an existing resource instance + // object is being moved to a new address using "moved" blocks. The move + // is modelled as a separate action because it's okay for the final state + // to reflect the address change even if subsequent plan/apply actions + // fail. // - // If there is no current object associated with currentInstAddr when - // this operation executes then it does nothing and returns a nil object - // with no errors, though in practice the planning engine should not include - // this operation unless it found an existing object that needed to be - // moved. + // If the incoming object is nil then this also returns nil without making + // any change and no errors. In practice though the planning engine should + // not include this operation unless it found an existing object that needed + // to be moved. + // + // This is for use with "current" resource instance objects only, so + // implementers can assume that the given object will have no DeposedKey. ManagedChangeAddr( ctx context.Context, - currentInstAddr, newInstAddr addrs.AbsResourceInstance, + object *ResourceInstanceObject, + newAddr addrs.AbsResourceInstance, ) (*ResourceInstanceObject, tfdiags.Diagnostics) ////////////////////////////////////////////////////////////////////////////// diff --git a/internal/engine/internal/exec/resource.go b/internal/engine/internal/exec/resource.go index fbdee4407d..cc2d5ea0a0 100644 --- a/internal/engine/internal/exec/resource.go +++ b/internal/engine/internal/exec/resource.go @@ -117,6 +117,16 @@ func (o *ResourceInstanceObject) IntoDeposed(key states.DeposedKey) *ResourceIns } } +// WithNewAddr returns a new [ResourceInstanceObject] that has the same +// State as the receiver but has InstanceAddr set to the given address. +func (o *ResourceInstanceObject) WithNewAddr(addr addrs.AbsResourceInstance) *ResourceInstanceObject { + return &ResourceInstanceObject{ + InstanceAddr: addr, + DeposedKey: o.DeposedKey, + State: o.State, + } +} + // IntoCurrent returns a new [ResourceInstanceObject] that has the same // address information as the receiver but has State set to the given object. // diff --git a/internal/engine/internal/execgraph/builder.go b/internal/engine/internal/execgraph/builder.go index a8b903c046..45830c0d2c 100644 --- a/internal/engine/internal/execgraph/builder.go +++ b/internal/engine/internal/execgraph/builder.go @@ -193,11 +193,12 @@ func (b *Builder) ManagedApply( } func (b *Builder) ManagedDepose( - instAddr ResultRef[addrs.AbsResourceInstance], + currentObj ResourceInstanceResultRef, + waitFor AnyResultRef, ) ResourceInstanceResultRef { return operationRef[*exec.ResourceInstanceObject](b, operationDesc{ opCode: opManagedDepose, - operands: []AnyResultRef{instAddr}, + operands: []AnyResultRef{currentObj, waitFor}, }) } @@ -212,12 +213,12 @@ func (b *Builder) ManagedAlreadyDeposed( } func (b *Builder) ManagedChangeAddr( - currentInstAddr ResultRef[addrs.AbsResourceInstance], - newInstAddr ResultRef[addrs.AbsResourceInstance], + currentObj ResourceInstanceResultRef, + newAddr ResultRef[addrs.AbsResourceInstance], ) ResourceInstanceResultRef { return operationRef[*exec.ResourceInstanceObject](b, operationDesc{ opCode: opManagedChangeAddr, - operands: []AnyResultRef{currentInstAddr, newInstAddr}, + operands: []AnyResultRef{currentObj, newAddr}, }) } diff --git a/internal/engine/internal/execgraph/compiler_ops.go b/internal/engine/internal/execgraph/compiler_ops.go index 2681077329..d500e3456e 100644 --- a/internal/engine/internal/execgraph/compiler_ops.go +++ b/internal/engine/internal/execgraph/compiler_ops.go @@ -231,7 +231,8 @@ func (c *compiler) compileOpManagedApply(operands *compilerOperands) nodeExecute func (c *compiler) compileOpManagedDepose(operands *compilerOperands) nodeExecuteRaw { ops := c.ops - getInstAddr := nextOperand[addrs.AbsResourceInstance](operands) + getCurrentObj := nextOperand[*exec.ResourceInstanceObject](operands) + waitForDeps := operands.OperandWaiter() diags := operands.Finish() c.diags = c.diags.Append(diags) if diags.HasErrors() { @@ -240,14 +241,17 @@ func (c *compiler) compileOpManagedDepose(operands *compilerOperands) nodeExecut return func(ctx context.Context) (any, bool, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics + if !waitForDeps(ctx) { + return nil, false, diags + } - instAddr, ok, moreDiags := getInstAddr(ctx) + currentObj, ok, moreDiags := getCurrentObj(ctx) diags = diags.Append(moreDiags) if !ok { return nil, false, diags } - ret, moreDiags := ops.ManagedDepose(ctx, instAddr) + ret, moreDiags := ops.ManagedDepose(ctx, currentObj) diags = diags.Append(moreDiags) return ret, !diags.HasErrors(), diags } @@ -285,8 +289,8 @@ func (c *compiler) compileOpManagedAlreadyDeposed(operands *compilerOperands) no func (c *compiler) compileOpManagedChangeAddr(operands *compilerOperands) nodeExecuteRaw { ops := c.ops - getCurrentInstAddr := nextOperand[addrs.AbsResourceInstance](operands) - getNewInstAddr := nextOperand[addrs.AbsResourceInstance](operands) + getCurrentObj := nextOperand[*exec.ResourceInstanceObject](operands) + getNewAddr := nextOperand[addrs.AbsResourceInstance](operands) diags := operands.Finish() c.diags = c.diags.Append(diags) if diags.HasErrors() { @@ -296,18 +300,18 @@ func (c *compiler) compileOpManagedChangeAddr(operands *compilerOperands) nodeEx return func(ctx context.Context) (any, bool, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics - currentInstAddr, ok, moreDiags := getCurrentInstAddr(ctx) + currentObj, ok, moreDiags := getCurrentObj(ctx) diags = diags.Append(moreDiags) if !ok { return nil, false, diags } - newInstAddr, ok, moreDiags := getNewInstAddr(ctx) + newAddr, ok, moreDiags := getNewAddr(ctx) diags = diags.Append(moreDiags) if !ok { return nil, false, diags } - ret, moreDiags := ops.ManagedChangeAddr(ctx, currentInstAddr, newInstAddr) + ret, moreDiags := ops.ManagedChangeAddr(ctx, currentObj, newAddr) diags = diags.Append(moreDiags) return ret, !diags.HasErrors(), diags } diff --git a/internal/engine/internal/execgraph/graph_unmarshal.go b/internal/engine/internal/execgraph/graph_unmarshal.go index 4a0a0ff991..6ec0a25ac7 100644 --- a/internal/engine/internal/execgraph/graph_unmarshal.go +++ b/internal/engine/internal/execgraph/graph_unmarshal.go @@ -271,14 +271,18 @@ func unmarshalOpManagedApply(rawOperands []uint64, prevResults []AnyResultRef, b } func unmarshalOpManagedDepose(rawOperands []uint64, prevResults []AnyResultRef, builder *Builder) (AnyResultRef, error) { - if len(rawOperands) != 1 { + if len(rawOperands) != 2 { return nil, fmt.Errorf("wrong number of operands (%d) for opManagedDepose", len(rawOperands)) } - instAddr, err := unmarshalGetPrevResultOf[addrs.AbsResourceInstance](prevResults, rawOperands[0]) + currentObj, err := unmarshalGetPrevResultOf[*exec.ResourceInstanceObject](prevResults, rawOperands[0]) if err != nil { - return nil, fmt.Errorf("invalid opManagedDepose instAddr: %w", err) + return nil, fmt.Errorf("invalid opManagedDepose currentObj: %w", err) } - return builder.ManagedDepose(instAddr), nil + waitFor, err := unmarshalGetPrevResultWaiter(prevResults, rawOperands[1]) + if err != nil { + return nil, fmt.Errorf("invalid opManagedDepose waitFor: %w", err) + } + return builder.ManagedDepose(currentObj, waitFor), nil } func unmarshalOpManagedAlreadyDeposed(rawOperands []uint64, prevResults []AnyResultRef, builder *Builder) (AnyResultRef, error) { @@ -300,15 +304,15 @@ func unmarshalOpManagedChangeAddr(rawOperands []uint64, prevResults []AnyResultR if len(rawOperands) != 2 { return nil, fmt.Errorf("wrong number of operands (%d) for opManagedChangeAddr", len(rawOperands)) } - currentInstAddr, err := unmarshalGetPrevResultOf[addrs.AbsResourceInstance](prevResults, rawOperands[0]) + currentObj, err := unmarshalGetPrevResultOf[*exec.ResourceInstanceObject](prevResults, rawOperands[0]) if err != nil { - return nil, fmt.Errorf("invalid opManagedChangeAddr currentInstAddr: %w", err) + return nil, fmt.Errorf("invalid opManagedChangeAddr currentObj: %w", err) } - newInstAddr, err := unmarshalGetPrevResultOf[addrs.AbsResourceInstance](prevResults, rawOperands[1]) + newAddr, err := unmarshalGetPrevResultOf[addrs.AbsResourceInstance](prevResults, rawOperands[1]) if err != nil { return nil, fmt.Errorf("invalid opManagedChangeAddr newInstAddr: %w", err) } - return builder.ManagedChangeAddr(currentInstAddr, newInstAddr), nil + return builder.ManagedChangeAddr(currentObj, newAddr), nil } func unmarshalOpDataRead(rawOperands []uint64, prevResults []AnyResultRef, builder *Builder) (AnyResultRef, error) { diff --git a/internal/engine/internal/execgraph/operations_test.go b/internal/engine/internal/execgraph/operations_test.go index 0be51fdcf6..5b3bacdce3 100644 --- a/internal/engine/internal/execgraph/operations_test.go +++ b/internal/engine/internal/execgraph/operations_test.go @@ -28,8 +28,8 @@ type mockOperations struct { EphemeralStateFunc func(ctx context.Context, ephemeral *exec.OpenEphemeralResourceInstance) (*exec.ResourceInstanceObject, tfdiags.Diagnostics) ManagedAlreadyDeposedFunc func(ctx context.Context, instAddr addrs.AbsResourceInstance, deposedKey states.DeposedKey) (*exec.ResourceInstanceObject, tfdiags.Diagnostics) ManagedApplyFunc func(ctx context.Context, plan *exec.ManagedResourceObjectFinalPlan, fallback *exec.ResourceInstanceObject, providerClient *exec.ProviderClient) (*exec.ResourceInstanceObject, tfdiags.Diagnostics) - ManagedChangeAddrFunc func(ctx context.Context, currentInstAddr, newInstAddr addrs.AbsResourceInstance) (*exec.ResourceInstanceObject, tfdiags.Diagnostics) - ManagedDeposeFunc func(ctx context.Context, instAddr addrs.AbsResourceInstance) (*exec.ResourceInstanceObject, tfdiags.Diagnostics) + ManagedChangeAddrFunc func(ctx context.Context, currentObj *exec.ResourceInstanceObject, newAddr addrs.AbsResourceInstance) (*exec.ResourceInstanceObject, tfdiags.Diagnostics) + ManagedDeposeFunc func(ctx context.Context, currentObj *exec.ResourceInstanceObject) (*exec.ResourceInstanceObject, tfdiags.Diagnostics) ManagedFinalPlanFunc func(ctx context.Context, desired *eval.DesiredResourceInstance, prior *exec.ResourceInstanceObject, plannedVal cty.Value, providerClient *exec.ProviderClient) (*exec.ManagedResourceObjectFinalPlan, tfdiags.Diagnostics) ProviderInstanceCloseFunc func(ctx context.Context, client *exec.ProviderClient) tfdiags.Diagnostics ProviderInstanceConfigFunc func(ctx context.Context, instAddr addrs.AbsProviderInstanceCorrect) (*exec.ProviderInstanceConfig, tfdiags.Diagnostics) @@ -109,24 +109,24 @@ func (m *mockOperations) ManagedApply(ctx context.Context, plan *exec.ManagedRes } // ManagedChangeAddr implements [exec.Operations]. -func (m *mockOperations) ManagedChangeAddr(ctx context.Context, currentInstAddr, newInstAddr addrs.AbsResourceInstance) (*exec.ResourceInstanceObject, tfdiags.Diagnostics) { +func (m *mockOperations) ManagedChangeAddr(ctx context.Context, currentObj *exec.ResourceInstanceObject, newAddr addrs.AbsResourceInstance) (*exec.ResourceInstanceObject, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics var result *exec.ResourceInstanceObject if m.ManagedChangeAddrFunc != nil { - result, diags = m.ManagedChangeAddrFunc(ctx, currentInstAddr, newInstAddr) + result, diags = m.ManagedChangeAddrFunc(ctx, currentObj, newAddr) } - m.appendLog("ManagedChangeAddr", []any{currentInstAddr, newInstAddr}, result) + m.appendLog("ManagedChangeAddr", []any{currentObj, newAddr}, result) return result, diags } // ManagedDepose implements [exec.Operations]. -func (m *mockOperations) ManagedDepose(ctx context.Context, instAddr addrs.AbsResourceInstance) (*exec.ResourceInstanceObject, tfdiags.Diagnostics) { +func (m *mockOperations) ManagedDepose(ctx context.Context, currentObj *exec.ResourceInstanceObject) (*exec.ResourceInstanceObject, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics var result *exec.ResourceInstanceObject if m.ManagedDeposeFunc != nil { - result, diags = m.ManagedDeposeFunc(ctx, instAddr) + result, diags = m.ManagedDeposeFunc(ctx, currentObj) } - m.appendLog("ManagedDepose", []any{instAddr}, result) + m.appendLog("ManagedDepose", []any{currentObj}, result) return result, diags } diff --git a/internal/engine/planning/execgraph_managed.go b/internal/engine/planning/execgraph_managed.go index 444d974540..3c10c714ee 100644 --- a/internal/engine/planning/execgraph_managed.go +++ b/internal/engine/planning/execgraph_managed.go @@ -255,7 +255,6 @@ func (b *execGraphBuilder) managedResourceInstanceChangeAddrAndPriorStateRefs( newAddrRef := b.lower.ConstantResourceInstAddr(plannedChange.Addr) return newAddrRef, execgraph.NilResultRef[*exec.ResourceInstanceObject]() } - prevAddrRef := b.lower.ConstantResourceInstAddr(plannedChange.PrevRunAddr) if plannedChange.DeposedKey != states.NotDeposed { // We need to use a different operation to access deposed objects. prevAddrRef := b.lower.ConstantResourceInstAddr(plannedChange.PrevRunAddr) @@ -263,15 +262,16 @@ func (b *execGraphBuilder) managedResourceInstanceChangeAddrAndPriorStateRefs( stateRef := b.lower.ManagedAlreadyDeposed(prevAddrRef, dkRef) return execgraph.NilResultRef[addrs.AbsResourceInstance](), stateRef } - newAddrRef := b.lower.ConstantResourceInstAddr(plannedChange.Addr) + prevAddrRef := b.lower.ConstantResourceInstAddr(plannedChange.PrevRunAddr) + priorStateRef := b.lower.ResourceInstancePrior(prevAddrRef) + retAddrRef := prevAddrRef + retStateRef := priorStateRef if !plannedChange.PrevRunAddr.Equal(plannedChange.Addr) { - // If the address is changing then we need to use the "change address" - // operation instead of just reading te prior state. - stateRef := b.lower.ManagedChangeAddr(prevAddrRef, newAddrRef) - return newAddrRef, stateRef + // If the address is changing then we'll also include the + // "change address" operation so that the object will get rebound + // to its new address before we do any other work. + retAddrRef = b.lower.ConstantResourceInstAddr(plannedChange.Addr) + retStateRef = b.lower.ManagedChangeAddr(retStateRef, retAddrRef) } - // In all other cases we just take the prior state directly out of the - // prior state, without any special behavior. - stateRef := b.lower.ResourceInstancePrior(prevAddrRef) - return newAddrRef, stateRef + return retAddrRef, retStateRef } diff --git a/internal/engine/planning/execgraph_managed_test.go b/internal/engine/planning/execgraph_managed_test.go index b999777551..49d2d41d1e 100644 --- a/internal/engine/planning/execgraph_managed_test.go +++ b/internal/engine/planning/execgraph_managed_test.go @@ -92,6 +92,39 @@ func TestExecGraphBuilder_ManagedResourceInstanceSubgraph(t *testing.T) { test.placeholder = r[3]; `, }, + "update with move": { + func(b *execGraphBuilder, providerClientRef execgraph.ResultRef[*exec.ProviderClient]) execgraph.ResourceInstanceResultRef { + oldInstAddr := addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test", + Name: "old", + }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance) + return b.ManagedResourceInstanceSubgraph( + &plans.ResourceInstanceChange{ + Addr: instAddr, + PrevRunAddr: oldInstAddr, + Change: plans.Change{ + Action: plans.Update, + Before: cty.StringVal("before"), + After: cty.StringVal("after"), + }, + }, + providerClientRef, + addrs.MakeSet[addrs.AbsResourceInstance](), + ) + }, + ` + v[0] = cty.StringVal("after"); + + r[0] = ResourceInstancePrior(test.old); + r[1] = ManagedChangeAddr(r[0], test.placeholder); + r[2] = ResourceInstanceDesired(test.placeholder, await()); + r[3] = ManagedFinalPlan(r[2], r[1], v[0], nil); + r[4] = ManagedApply(r[3], nil, nil, await()); + + test.placeholder = r[4]; + `, + }, "delete": { func(b *execGraphBuilder, providerClientRef execgraph.ResultRef[*exec.ProviderClient]) execgraph.ResourceInstanceResultRef { return b.ManagedResourceInstanceSubgraph(