mirror of
https://github.com/opentffoundation/opentf.git
synced 2026-03-20 22:01:25 -04:00
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>
129 lines
5.1 KiB
Go
129 lines
5.1 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 planning
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/opentofu/opentofu/internal/addrs"
|
|
"github.com/opentofu/opentofu/internal/lang/eval"
|
|
"github.com/opentofu/opentofu/internal/providers"
|
|
"github.com/opentofu/opentofu/internal/states"
|
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
|
)
|
|
|
|
func (p *planGlue) planDesiredDataResourceInstance(ctx context.Context, inst *eval.DesiredResourceInstance) (*resourceInstanceObject, tfdiags.Diagnostics) {
|
|
var diags tfdiags.Diagnostics
|
|
|
|
ret := &resourceInstanceObject{
|
|
Addr: inst.Addr.CurrentObject(),
|
|
Dependencies: addrs.MakeSet[addrs.AbsResourceInstanceObject](),
|
|
Provider: inst.Provider,
|
|
|
|
// We'll start off with a completely-unknown placeholder value, but
|
|
// we might refine this to be more specific as we learn more below.
|
|
PlaceholderValue: cty.DynamicVal,
|
|
|
|
// NOTE: PlannedChange remains nil until we actually produce a plan,
|
|
// so early returns with errors are not guaranteed to have a valid
|
|
// change object. Evaluation falls back on using PlaceholderValue
|
|
// when no planned change is present.
|
|
}
|
|
for dep := range inst.RequiredResourceInstances.All() {
|
|
ret.Dependencies.Add(dep.CurrentObject())
|
|
}
|
|
|
|
validateDiags := p.planCtx.providers.ValidateResourceConfig(ctx, inst.Provider, inst.ResourceMode, inst.ResourceType, inst.ConfigVal)
|
|
diags = diags.Append(validateDiags)
|
|
if diags.HasErrors() {
|
|
return ret, diags
|
|
}
|
|
|
|
if inst.ProviderInstance == nil {
|
|
// TODO: Record that this was deferred because we don't yet know which
|
|
// provider instance it belongs to.
|
|
return ret, diags
|
|
}
|
|
|
|
// The equivalent of "refreshing" a data resource is just to discard it
|
|
// completely, because we only retain the previous result in state snapshots
|
|
// to support unusual situations like "tofu console"; it's not expected that
|
|
// data resource instances persist between rounds and they cannot because
|
|
// the protocol doesn't include any way to "upgrade" them if the provider
|
|
// schema has changed since previous round.
|
|
// FIXME: State is still using the weird old representation of provider
|
|
// instance addresses, so we can't actually populate the provider instance
|
|
// arguments properly here.
|
|
p.planCtx.refreshedState.SetResourceInstanceCurrent(inst.Addr, nil, addrs.AbsProviderConfig{}, inst.ProviderInstance.Key)
|
|
|
|
// TODO: If the config value is not wholly known, or if any resource
|
|
// instance in inst.RequiredResourceInstances already has a planned change,
|
|
// then plan to read this during the apply phase and set ret.PlannedChange
|
|
// to an object using the [plans.Read] action, without writing a new
|
|
// object into the refreshed state yet.
|
|
|
|
providerClient, moreDiags := p.providerClient(ctx, *inst.ProviderInstance)
|
|
if providerClient == nil {
|
|
moreDiags = moreDiags.Append(tfdiags.AttributeValue(
|
|
tfdiags.Error,
|
|
"Provider instance not available",
|
|
fmt.Sprintf("Cannot plan %s because its associated provider instance %s cannot initialize.", inst.Addr, *inst.ProviderInstance),
|
|
nil,
|
|
))
|
|
}
|
|
diags = diags.Append(moreDiags)
|
|
if moreDiags.HasErrors() {
|
|
return ret, diags
|
|
}
|
|
|
|
resp := providerClient.ReadDataSource(ctx, providers.ReadDataSourceRequest{
|
|
TypeName: inst.ResourceType,
|
|
Config: inst.ConfigVal,
|
|
|
|
// TODO: ProviderMeta is a rarely-used feature that only really makes
|
|
// sense when the module and provider are both written by the same
|
|
// party and the module author is using the provider as a way to
|
|
// transport module usage telemetry. We should decide whether we want
|
|
// to keep supporting that, and if so design a way for the relevant
|
|
// meta value to get from the evaluator into here.
|
|
ProviderMeta: cty.NullVal(cty.DynamicPseudoType),
|
|
})
|
|
diags = diags.Append(resp.Diagnostics)
|
|
if resp.Diagnostics.HasErrors() {
|
|
return ret, diags
|
|
}
|
|
// TODO: Verify that the object the provider returned is a valid completion
|
|
// of the configuration value.
|
|
|
|
// TODO: Update the refreshed state to match what we've just read.
|
|
|
|
// Since we've already read the data source during the planning phase,
|
|
// we don't need a PlannedChange here and can instead just use the result
|
|
// as the PlaceholderValue.
|
|
ret.PlaceholderValue = resp.State
|
|
|
|
return ret, diags
|
|
}
|
|
|
|
func (p *planGlue) planOrphanDataResourceInstance(_ context.Context, addr addrs.AbsResourceInstance, state *states.ResourceInstanceObjectFullSrc) (*resourceInstanceObject, tfdiags.Diagnostics) {
|
|
var diags tfdiags.Diagnostics
|
|
|
|
// An orphan data object is always just discarded completely, because
|
|
// OpenTofu retains them only for esoteric uses like the "tofu console"
|
|
// command: they are not actually expected to persist between rounds.
|
|
p.planCtx.refreshedState.RemoveResourceInstanceObjectFull(addr.CurrentObject(), state.ProviderInstanceAddr)
|
|
|
|
return &resourceInstanceObject{
|
|
Addr: addr.CurrentObject(),
|
|
Dependencies: addrs.MakeSet[addrs.AbsResourceInstanceObject](),
|
|
Provider: state.ProviderInstanceAddr.Config.Config.Provider,
|
|
PlaceholderValue: cty.NullVal(cty.DynamicPseudoType),
|
|
}, diags
|
|
}
|