Files
opentf/internal/engine/planning/plan_data.go
Martin Atkins 6a8e62d4fa engine/planning: Minimal building of execution graph
This is a _very_ early, minimal plumbing of the execution graph builder
into the experimental planning engine.

The goal here is mainly just to prove the idea that the planning engine can
build execution graphs using the execgraph.Builder API we currently have.
This implementation is not complete yet, and also we are expecting to
rework the structure of the planning engine later on anyway so this initial
work focuses on just plumbing it in to what we had as straightforwardly
as possible.

This is enough to get a serialized form of _an_ execution graph included
in the generated plan, though since we don't have the apply engine
implemented we don't actually use it for anything yet.

In subsequent commits we'll continue building out the graph-building logic
and then arrange for the apply phase to unmarshal the saved execution graph
and attempt to execute it, so we can hopefully see a minimal version of all
of this working end-to-end for the first time. But for now, this was mainly
just a proof-of-concept of building an execution graph and capturing it
into its temporary home in the plans.Plan model.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-12-12 07:03:52 -08:00

99 lines
4.0 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/engine/internal/execgraph"
"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, egb *execgraph.Builder) (cty.Value, execgraph.ResourceInstanceResultRef, tfdiags.Diagnostics) {
// Regardless of outcome we'll always report that we completed planning.
defer p.planCtx.reportResourceInstancePlanCompletion(inst.Addr)
var diags tfdiags.Diagnostics
validateDiags := p.planCtx.providers.ValidateResourceConfig(ctx, inst.Provider, inst.Addr.Resource.Resource.Mode, inst.Addr.Resource.Resource.Type, inst.ConfigVal)
diags = diags.Append(validateDiags)
if diags.HasErrors() {
return cty.DynamicVal, nil, diags
}
if inst.ProviderInstance == nil {
// TODO: Record that this was deferred because we don't yet know which
// provider instance it belongs to.
return deferredVal(cty.DynamicVal), nil, diags
}
// TODO: There are various other reasons why we might need to defer planning
// this until a future plan/apply round.
// 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 return a "proposed new
// value" for use during the planning phase.
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 cty.DynamicVal, nil, diags
}
resp := providerClient.ReadDataSource(ctx, providers.ReadDataSourceRequest{
TypeName: inst.Addr.Resource.Resource.Type,
Config: inst.ConfigVal,
// TODO: Add ProviderMeta information to eval.DesiredResourceInstance
// and then pass it on to the provider here.
})
diags = diags.Append(resp.Diagnostics)
if resp.Diagnostics.HasErrors() {
return cty.DynamicVal, nil, diags
}
// TODO: Implement
panic("unimplemented")
}
func (p *planGlue) planOrphanDataResourceInstance(_ context.Context, addr addrs.AbsResourceInstance, state *states.ResourceInstanceObjectFullSrc, egb *execgraph.Builder) tfdiags.Diagnostics {
// Regardless of outcome we'll always report that we completed planning.
defer p.planCtx.reportResourceInstancePlanCompletion(addr)
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.SetResourceInstanceObjectFull(addr, states.NotDeposed, nil)
return diags
}