Files
opentf/internal/engine/applying/operations_resource.go
Martin Atkins 468d66678d states: Separate SyncState method for removing "full" objects
Our new language runtime uses a set of new methods on SyncState to work
with its preferred "full" representation of resource instance objects, but
those are implemented in terms of methods that already existed for the old
runtime's benefit and so we need to deal with some quirks of those existing
methods.

One such quirk is that the operations to write or remove objects also want
to update some resource-level and instance-level metadata as a side-effect,
and we need to carry through that metadata even when we're intending to
completely remove a resource instance object.

To preserve our goal of leaving the existing codepaths untouched for now,
this pushes a little complexity back up into the main caller in the apply
engine, forcing it to call a different method when it knows it has deleted
an object. That new method then only takes the metadata we need and not
an actual resource instance object, so it gels better with the underlying
ModuleState methods it's implemented in terms of.

Hopefully in the long run we'll rethink the state models to not rely on
these hidden side-effects, but that's beyond the scope of our current phase
of work on the new language runtime.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-03-11 07:28:09 -07:00

99 lines
3.2 KiB
Go

// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package applying
import (
"context"
"fmt"
"log"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/engine/internal/exec"
"github.com/opentofu/opentofu/internal/lang/eval"
"github.com/opentofu/opentofu/internal/states"
"github.com/opentofu/opentofu/internal/tfdiags"
)
// ResourceInstanceDesired implements [exec.Operations].
func (ops *execOperations) ResourceInstanceDesired(
ctx context.Context,
instAddr addrs.AbsResourceInstance,
) (*eval.DesiredResourceInstance, tfdiags.Diagnostics) {
log.Printf("[TRACE] apply phase: ResourceInstanceDesired %s", instAddr)
return ops.configOracle.DesiredResourceInstance(ctx, instAddr)
}
// ResourceInstancePrior implements [exec.Operations].
func (ops *execOperations) ResourceInstancePrior(
ctx context.Context,
instAddr addrs.AbsResourceInstance,
) (*exec.ResourceInstanceObject, tfdiags.Diagnostics) {
log.Printf("[TRACE] apply phase: ResourceInstancePrior %s", instAddr)
return ops.resourceInstanceStateObject(ctx, ops.priorState, instAddr, states.NotDeposed)
}
// ResourceInstancePostconditions implements [exec.Operations].
func (ops *execOperations) ResourceInstancePostconditions(
ctx context.Context,
result *exec.ResourceInstanceObject,
) tfdiags.Diagnostics {
log.Printf("[TRACE] apply phase: ResourceInstancePostconditions (currently just a noop!)")
// TODO: Implement this by delegating to a special "run resource instance
// postconditions" method on ops.configOracle.
return nil
}
// ResourceInstancePrior implements [exec.Operations].
func (ops *execOperations) resourceInstanceStateObject(
ctx context.Context,
fromState *states.SyncState,
instAddr addrs.AbsResourceInstance,
deposedKey states.DeposedKey,
) (*exec.ResourceInstanceObject, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
src := ops.priorState.ResourceInstanceObjectFull(instAddr.Object(deposedKey))
if src == nil {
return nil, diags
}
// We must decode the resource-type-specific data using the provider's
// schema for this resource type.
providerAddr := src.ProviderInstanceAddr.Config.Config.Provider
schema, moreDiags := ops.plugins.ResourceTypeSchema(
ctx,
providerAddr,
instAddr.Resource.Resource.Mode, // TODO: Make this a direct field of src, as with src.ResourceType, to centralize this rule
src.ResourceType,
)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
return nil, diags
}
state, err := states.DecodeResourceInstanceObjectFull(src, schema.Block.ImpliedType())
if err != nil {
nounPhrase := "a current object"
if deposedKey != states.NotDeposed {
nounPhrase = "deposed object " + deposedKey.String()
}
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid resource instance object in prior state",
fmt.Sprintf(
"The prior state for %s has %s that doesn't conform to the resource type schema: %s.",
instAddr, nounPhrase, tfdiags.FormatError(err),
),
))
return nil, diags
}
if state == nil {
return nil, diags
}
return &exec.ResourceInstanceObject{
InstanceAddr: instAddr,
DeposedKey: deposedKey,
State: state,
}, diags
}