Split out provider schemas vs instances in new engine (#3530)

Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
Christian Mesh
2025-12-01 13:09:58 -05:00
committed by GitHub
parent 5e7397b8a3
commit ffc9c4d556
22 changed files with 429 additions and 379 deletions

View File

@@ -7,28 +7,23 @@ package local
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"sync"
"sync/atomic" "sync/atomic"
"github.com/apparentlymart/go-versions/versions" "github.com/apparentlymart/go-versions/versions"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/zclconf/go-cty/cty"
"github.com/opentofu/opentofu/internal/addrs" "github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/backend" "github.com/opentofu/opentofu/internal/backend"
"github.com/opentofu/opentofu/internal/configs" "github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/configs/configload" "github.com/opentofu/opentofu/internal/configs/configload"
"github.com/opentofu/opentofu/internal/configs/configschema"
"github.com/opentofu/opentofu/internal/engine/planning" "github.com/opentofu/opentofu/internal/engine/planning"
"github.com/opentofu/opentofu/internal/engine/plugins"
"github.com/opentofu/opentofu/internal/lang/eval" "github.com/opentofu/opentofu/internal/lang/eval"
"github.com/opentofu/opentofu/internal/plans" "github.com/opentofu/opentofu/internal/plans"
"github.com/opentofu/opentofu/internal/providers"
"github.com/opentofu/opentofu/internal/provisioners"
"github.com/opentofu/opentofu/internal/states" "github.com/opentofu/opentofu/internal/states"
"github.com/opentofu/opentofu/internal/states/statemgr" "github.com/opentofu/opentofu/internal/states/statemgr"
"github.com/opentofu/opentofu/internal/tfdiags" "github.com/opentofu/opentofu/internal/tfdiags"
@@ -151,10 +146,7 @@ func (b *Local) opPlanWithExperimentalRuntime(stopCtx context.Context, cancelCtx
prevRoundState = states.NewState() // this is the first round, starting with an empty state prevRoundState = states.NewState() // this is the first round, starting with an empty state
} }
plugins := &newRuntimePlugins{ plugins := plugins.NewRuntimePlugins(b.ContextOpts.Providers, b.ContextOpts.Provisioners)
providers: b.ContextOpts.Providers,
provisioners: b.ContextOpts.Provisioners,
}
evalCtx := &eval.EvalContext{ evalCtx := &eval.EvalContext{
RootModuleDir: op.ConfigDir, RootModuleDir: op.ConfigDir,
OriginalWorkingDir: b.ContextOpts.Meta.OriginalWorkingDir, OriginalWorkingDir: b.ContextOpts.Meta.OriginalWorkingDir,
@@ -206,7 +198,7 @@ func (b *Local) opPlanWithExperimentalRuntime(stopCtx context.Context, cancelCtx
return return
} }
plan, moreDiags := planning.PlanChanges(ctx, prevRoundState, configInst) plan, moreDiags := planning.PlanChanges(ctx, prevRoundState, configInst, plugins)
diags = diags.Append(moreDiags) diags = diags.Append(moreDiags)
// We intentionally continue with errors here because we make a best effort // We intentionally continue with errors here because we make a best effort
// to render a partial plan output even when we have errors, in case // to render a partial plan output even when we have errors, in case
@@ -321,254 +313,3 @@ func (n *newRuntimeModules) ModuleConfig(ctx context.Context, source addrs.Modul
return eval.PrepareTofu2024Module(source, mod), diags return eval.PrepareTofu2024Module(source, mod), diags
} }
type newRuntimePlugins struct {
providers map[addrs.Provider]providers.Factory
provisioners map[string]provisioners.Factory
// unconfiguredInsts is all of the provider instances we've created for
// unconfigured uses such as schema fetching and validation, which we
// currently just leave running for the remainder of the life of this
// object though perhaps we'll do something more clever eventually.
//
// Must hold a lock on mu throughout any access to this map.
unconfiguredInsts map[addrs.Provider]providers.Unconfigured
mu sync.Mutex
}
var _ eval.Providers = (*newRuntimePlugins)(nil)
var _ eval.Provisioners = (*newRuntimePlugins)(nil)
// NewConfiguredProvider implements evalglue.Providers.
func (n *newRuntimePlugins) NewConfiguredProvider(ctx context.Context, provider addrs.Provider, configVal cty.Value) (providers.Configured, tfdiags.Diagnostics) {
inst, diags := n.newProviderInst(ctx, provider)
if diags.HasErrors() {
return nil, diags
}
resp := inst.ConfigureProvider(ctx, providers.ConfigureProviderRequest{
Config: configVal,
// We aren't actually Terraform, so we'll just pretend to be a
// Terraform version that has roughly the same functionality that
// OpenTofu currently has, since providers are permitted to use this to
// adapt their behavior for older versions of Terraform.
TerraformVersion: "1.13.0",
})
diags = diags.Append(resp.Diagnostics)
if resp.Diagnostics.HasErrors() {
return nil, diags
}
return inst, diags
}
// ProviderConfigSchema implements evalglue.Providers.
func (n *newRuntimePlugins) ProviderConfigSchema(ctx context.Context, provider addrs.Provider) (*providers.Schema, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
inst, moreDiags := n.unconfiguredProviderInst(ctx, provider)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
return nil, diags
}
resp := inst.GetProviderSchema(ctx)
diags = diags.Append(resp.Diagnostics)
if resp.Diagnostics.HasErrors() {
return nil, diags
}
return &resp.Provider, diags
}
// ResourceTypeSchema implements evalglue.Providers.
func (n *newRuntimePlugins) ResourceTypeSchema(ctx context.Context, provider addrs.Provider, mode addrs.ResourceMode, typeName string) (*providers.Schema, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
inst, moreDiags := n.unconfiguredProviderInst(ctx, provider)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
return nil, diags
}
resp := inst.GetProviderSchema(ctx)
diags = diags.Append(resp.Diagnostics)
if resp.Diagnostics.HasErrors() {
return nil, diags
}
// NOTE: Callers expect us to return nil if we successfully fetch the
// provider schema and then find there is no matching resource type, because
// the caller is typically in a better position to return a useful error
// message than we are.
var types map[string]providers.Schema
switch mode {
case addrs.ManagedResourceMode:
types = resp.ResourceTypes
case addrs.DataResourceMode:
types = resp.DataSources
case addrs.EphemeralResourceMode:
types = resp.EphemeralResources
default:
// We don't support any other modes, so we'll just treat these as
// a request for a resource type that doesn't exist at all.
return nil, nil
}
ret, ok := types[typeName]
if !ok {
return nil, diags
}
return &ret, diags
}
// ValidateProviderConfig implements evalglue.Providers.
func (n *newRuntimePlugins) ValidateProviderConfig(ctx context.Context, provider addrs.Provider, configVal cty.Value) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
inst, moreDiags := n.unconfiguredProviderInst(ctx, provider)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
return diags
}
resp := inst.ValidateProviderConfig(ctx, providers.ValidateProviderConfigRequest{
Config: configVal,
})
diags = diags.Append(resp.Diagnostics)
return diags
}
// ValidateResourceConfig implements evalglue.Providers.
func (n *newRuntimePlugins) ValidateResourceConfig(ctx context.Context, provider addrs.Provider, mode addrs.ResourceMode, typeName string, configVal cty.Value) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
inst, moreDiags := n.unconfiguredProviderInst(ctx, provider)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
return diags
}
switch mode {
case addrs.ManagedResourceMode:
resp := inst.ValidateResourceConfig(ctx, providers.ValidateResourceConfigRequest{
TypeName: typeName,
Config: configVal,
})
diags = diags.Append(resp.Diagnostics)
case addrs.DataResourceMode:
resp := inst.ValidateDataResourceConfig(ctx, providers.ValidateDataResourceConfigRequest{
TypeName: typeName,
Config: configVal,
})
diags = diags.Append(resp.Diagnostics)
case addrs.EphemeralResourceMode:
resp := inst.ValidateEphemeralConfig(ctx, providers.ValidateEphemeralConfigRequest{
TypeName: typeName,
Config: configVal,
})
diags = diags.Append(resp.Diagnostics)
default:
// If we get here then it's a bug because the cases above should
// cover all valid values of [addrs.ResourceMode].
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Unsupported resource mode",
fmt.Sprintf("Attempted to validate resource of unsupported mode %s; this is a bug in OpenTofu.", mode),
))
}
return diags
}
func (m *newRuntimePlugins) unconfiguredProviderInst(ctx context.Context, provider addrs.Provider) (providers.Unconfigured, tfdiags.Diagnostics) {
m.mu.Lock()
defer m.mu.Unlock()
if running, ok := m.unconfiguredInsts[provider]; ok {
return running, nil
}
inst, diags := m.newProviderInst(ctx, provider)
if diags.HasErrors() {
return nil, diags
}
if m.unconfiguredInsts == nil {
m.unconfiguredInsts = make(map[addrs.Provider]providers.Unconfigured)
}
m.unconfiguredInsts[provider] = inst
return inst, diags
}
// newProviderInst creates a new instance of the given provider.
//
// The result is not retained anywhere inside the receiver. Each call to this
// function returns a new object. A successful result is always an unconfigured
// provider, but we return [providers.Interface] in case the caller would like
// to subsequently configure the result before returning it as
// [providers.Configured].
//
// If you intend to use the resulting instance only for "unconfigured"
// operations like fetching schema, use
// [newRuntimePlugins.unconfiguredProviderInst] instead to potentially reuse
// an already-active instance of the same provider.
func (m *newRuntimePlugins) newProviderInst(_ context.Context, provider addrs.Provider) (providers.Interface, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
factory, ok := m.providers[provider]
if !ok {
// FIXME: If this error remains reachable in the final version of this
// code (i.e. if some caller isn't already guaranteeing that all
// providers from the configuration and state are included here) then
// we should make this error message more actionable.
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Provider unavailable",
fmt.Sprintf("This configuration requires provider %q, but it isn't installed.", provider),
))
return nil, diags
}
inst, err := factory()
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Provider failed to start",
fmt.Sprintf("Failed to launch provider %q: %s.", provider, tfdiags.FormatError(err)),
))
return nil, diags
}
return inst, diags
}
// ProvisionerConfigSchema implements evalglue.Provisioners.
func (n *newRuntimePlugins) ProvisionerConfigSchema(ctx context.Context, typeName string) (*configschema.Block, tfdiags.Diagnostics) {
// TODO: Implement this in terms of [newRuntimePlugins.provisioners].
// But provisioners aren't in scope for our "walking skeleton" phase of
// development, so we'll skip this for now.
var diags tfdiags.Diagnostics
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Cannot use providers in new runtime codepath",
fmt.Sprintf("Can't use provisioner %q: new runtime codepath doesn't know how to instantiate provisioners yet", typeName),
))
return nil, diags
}
// Close terminates any plugins that are managed by this object and are still
// running.
func (n *newRuntimePlugins) Close(ctx context.Context) error {
n.mu.Lock()
defer n.mu.Unlock()
var errs error
for addr, p := range n.unconfiguredInsts {
err := p.Close(ctx)
if err != nil {
errs = errors.Join(errs, fmt.Errorf("closing provider %q: %w", addr, err))
}
}
n.unconfiguredInsts = nil // discard all of the memoized instances
return errs
}

View File

@@ -9,6 +9,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/opentofu/opentofu/internal/engine/plugins"
"github.com/opentofu/opentofu/internal/lang/eval" "github.com/opentofu/opentofu/internal/lang/eval"
"github.com/opentofu/opentofu/internal/lang/grapheval" "github.com/opentofu/opentofu/internal/lang/grapheval"
"github.com/opentofu/opentofu/internal/plans" "github.com/opentofu/opentofu/internal/plans"
@@ -45,10 +46,10 @@ import (
// implementation of how it decides what to plan and how to plan it, and less // implementation of how it decides what to plan and how to plan it, and less
// on where it gets the information to make those decisions and how it // on where it gets the information to make those decisions and how it
// represents those decisions in its return value. // represents those decisions in its return value.
func PlanChanges(ctx context.Context, prevRoundState *states.State, configInst *eval.ConfigInstance) (*plans.Plan, tfdiags.Diagnostics) { func PlanChanges(ctx context.Context, prevRoundState *states.State, configInst *eval.ConfigInstance, providers plugins.Providers) (*plans.Plan, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics var diags tfdiags.Diagnostics
planCtx := newPlanContext(configInst.EvalContext(), prevRoundState) planCtx := newPlanContext(configInst.EvalContext(), prevRoundState, providers)
// This configInst.DrivePlanning call blocks until the evaluator has // This configInst.DrivePlanning call blocks until the evaluator has
// visited all expressions in the configuration and calls // visited all expressions in the configuration and calls

View File

@@ -10,6 +10,7 @@ import (
"github.com/opentofu/opentofu/internal/addrs" "github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/engine/lifecycle" "github.com/opentofu/opentofu/internal/engine/lifecycle"
"github.com/opentofu/opentofu/internal/engine/plugins"
"github.com/opentofu/opentofu/internal/lang/eval" "github.com/opentofu/opentofu/internal/lang/eval"
"github.com/opentofu/opentofu/internal/plans" "github.com/opentofu/opentofu/internal/plans"
"github.com/opentofu/opentofu/internal/states" "github.com/opentofu/opentofu/internal/states"
@@ -40,12 +41,14 @@ type planContext struct {
providerInstances *providerInstances providerInstances *providerInstances
providers plugins.Providers
// TODO: something to track which ephemeral resource instances are currently // TODO: something to track which ephemeral resource instances are currently
// open? (Do we actually need that, or can we just rely on a background // open? (Do we actually need that, or can we just rely on a background
// goroutine to babysit those based on the completion tracker?) // goroutine to babysit those based on the completion tracker?)
} }
func newPlanContext(evalCtx *eval.EvalContext, prevRoundState *states.State) *planContext { func newPlanContext(evalCtx *eval.EvalContext, prevRoundState *states.State, providers plugins.Providers) *planContext {
if prevRoundState == nil { if prevRoundState == nil {
prevRoundState = states.NewState() prevRoundState = states.NewState()
} }
@@ -61,6 +64,7 @@ func newPlanContext(evalCtx *eval.EvalContext, prevRoundState *states.State) *pl
refreshedState: refreshedState.SyncWrapper(), refreshedState: refreshedState.SyncWrapper(),
completion: completion, completion: completion,
providerInstances: newProviderInstances(completion), providerInstances: newProviderInstances(completion),
providers: providers,
} }
} }

View File

@@ -23,6 +23,12 @@ func (p *planGlue) planDesiredDataResourceInstance(ctx context.Context, inst *ev
defer p.planCtx.reportResourceInstancePlanCompletion(inst.Addr) defer p.planCtx.reportResourceInstancePlanCompletion(inst.Addr)
var diags tfdiags.Diagnostics 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, diags
}
if inst.ProviderInstance == nil { if inst.ProviderInstance == nil {
// TODO: Record that this was deferred because we don't yet know which // TODO: Record that this was deferred because we don't yet know which
// provider instance it belongs to. // provider instance it belongs to.

View File

@@ -16,6 +16,13 @@ import (
func (p *planGlue) planDesiredEphemeralResourceInstance(ctx context.Context, inst *eval.DesiredResourceInstance) (cty.Value, tfdiags.Diagnostics) { func (p *planGlue) planDesiredEphemeralResourceInstance(ctx context.Context, inst *eval.DesiredResourceInstance) (cty.Value, tfdiags.Diagnostics) {
// Regardless of outcome we'll always report that we completed planning. // Regardless of outcome we'll always report that we completed planning.
defer p.planCtx.reportResourceInstancePlanCompletion(inst.Addr) 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, diags
}
// TODO: Implement // TODO: Implement
panic("unimplemented") panic("unimplemented")

View File

@@ -35,6 +35,11 @@ type planGlue struct {
var _ eval.PlanGlue = (*planGlue)(nil) var _ eval.PlanGlue = (*planGlue)(nil)
// I'm not sure that this belongs here
func (p *planGlue) ValidateProviderConfig(ctx context.Context, provider addrs.Provider, configVal cty.Value) tfdiags.Diagnostics {
return p.planCtx.providers.ValidateProviderConfig(ctx, provider, configVal)
}
// PlanDesiredResourceInstance implements eval.PlanGlue. // PlanDesiredResourceInstance implements eval.PlanGlue.
// //
// This is called each time the evaluation system discovers a new resource // This is called each time the evaluation system discovers a new resource

View File

@@ -66,6 +66,12 @@ func (p *planGlue) planDesiredManagedResourceInstance(ctx context.Context, inst
return cty.DynamicVal, diags return cty.DynamicVal, diags
} }
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, diags
}
var prevRoundVal cty.Value var prevRoundVal cty.Value
var prevRoundPrivate []byte var prevRoundPrivate []byte
prevRoundState := p.planCtx.prevRoundState.ResourceInstance(inst.Addr) prevRoundState := p.planCtx.prevRoundState.ResourceInstance(inst.Addr)

View File

@@ -71,7 +71,6 @@ func (pi *providerInstances) ProviderClient(ctx context.Context, addr addrs.AbsP
oracle := planGlue.oracle oracle := planGlue.oracle
once := pi.active.Get(addr) once := pi.active.Get(addr)
return once.Do(ctx, func(ctx context.Context) (providers.Configured, tfdiags.Diagnostics) { return once.Do(ctx, func(ctx context.Context) (providers.Configured, tfdiags.Diagnostics) {
evalCtx := oracle.EvalContext(ctx)
configVal := oracle.ProviderInstanceConfig(ctx, addr) configVal := oracle.ProviderInstanceConfig(ctx, addr)
if configVal == cty.NilVal { if configVal == cty.NilVal {
// This suggests that the provider instance has an invalid // This suggests that the provider instance has an invalid
@@ -93,7 +92,7 @@ func (pi *providerInstances) ProviderClient(ctx context.Context, addr addrs.AbsP
// then this should return "nil, nil" in the error case so that the // then this should return "nil, nil" in the error case so that the
// caller will treat it the same as a "configuration not valid enough" // caller will treat it the same as a "configuration not valid enough"
// problem. // problem.
ret, diags := evalCtx.Providers.NewConfiguredProvider(ctx, addr.Config.Config.Provider, configVal) ret, diags := planGlue.planCtx.providers.NewConfiguredProvider(ctx, addr.Config.Config.Provider, configVal)
// This background goroutine deals with closing the provider once it's // This background goroutine deals with closing the provider once it's
// no longer needed, and with asking it to gracefully stop if our // no longer needed, and with asking it to gracefully stop if our

View File

@@ -0,0 +1,338 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package plugins
import (
"context"
"errors"
"fmt"
"sync"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/configs/configschema"
"github.com/opentofu/opentofu/internal/lang/eval"
"github.com/opentofu/opentofu/internal/providers"
"github.com/opentofu/opentofu/internal/provisioners"
"github.com/opentofu/opentofu/internal/tfdiags"
"github.com/zclconf/go-cty/cty"
)
type Plugins interface {
Providers
Provisioners
}
// Providers is implemented by callers of this package to provide access
// to the providers needed by a configuration without this package needing
// to know anything about how provider plugins work, or whether plugins are
// even being used.
type Providers interface {
eval.ProvidersSchema
// ValidateProviderConfig runs provider-specific logic to check whether
// the given configuration is valid. Returns at least one error diagnostic
// if the configuration is not valid, and may also return warning
// diagnostics regardless of whether the configuration is valid.
//
// The given config value is guaranteed to be an object conforming to
// the schema returned by a previous call to ProviderConfigSchema for
// the same provider.
ValidateProviderConfig(ctx context.Context, provider addrs.Provider, configVal cty.Value) tfdiags.Diagnostics
// ValidateResourceConfig runs provider-specific logic to check whether
// the given configuration is valid. Returns at least one error diagnostic
// if the configuration is not valid, and may also return warning
// diagnostics regardless of whether the configuration is valid.
//
// The given config value is guaranteed to be an object conforming to
// the schema returned by a previous call to ResourceTypeSchema for
// the same resource type.
ValidateResourceConfig(ctx context.Context, provider addrs.Provider, mode addrs.ResourceMode, typeName string, configVal cty.Value) tfdiags.Diagnostics
// NewConfiguredProvider starts a _configured_ instance of the given
// provider using the given configuration value.
//
// The evaluation system itself makes no use of configured providers, but
// higher-level processes wrapping it (e.g. the plan and apply engines)
// need to use configured providers for actions related to resources, etc,
// and so this is for their benefit to help ensure that they are definitely
// creating a configured instance of the same provider that other methods
// would be using to return schema information and validation results.
//
// It's the caller's responsibility to ensure that the given configuration
// value is valid according to the provider's schema and validation rules.
// That's usually achieved by taking a value provided by the evaluation
// system, which would then have already been processed using the results
// from [Providers.ProviderConfigSchema] and
// [Providers.ValidateProviderConfig]. If the returned diagnostics contains
// errors then the [providers.Configured] result is invalid and must not be
// used.
NewConfiguredProvider(ctx context.Context, provider addrs.Provider, configVal cty.Value) (providers.Configured, tfdiags.Diagnostics)
Close(ctx context.Context) error
}
type Provisioners interface {
eval.ProvisionersSchema
}
type newRuntimePlugins struct {
providers map[addrs.Provider]providers.Factory
provisioners map[string]provisioners.Factory
// unconfiguredInsts is all of the provider instances we've created for
// unconfigured uses such as schema fetching and validation, which we
// currently just leave running for the remainder of the life of this
// object though perhaps we'll do something more clever eventually.
//
// Must hold a lock on mu throughout any access to this map.
unconfiguredInsts map[addrs.Provider]providers.Unconfigured
mu sync.Mutex
}
var _ Providers = (*newRuntimePlugins)(nil)
var _ Provisioners = (*newRuntimePlugins)(nil)
func NewRuntimePlugins(providers map[addrs.Provider]providers.Factory, provisioners map[string]provisioners.Factory) Plugins {
return &newRuntimePlugins{
providers: providers,
provisioners: provisioners,
}
}
// NewConfiguredProvider implements evalglue.Providers.
func (n *newRuntimePlugins) NewConfiguredProvider(ctx context.Context, provider addrs.Provider, configVal cty.Value) (providers.Configured, tfdiags.Diagnostics) {
inst, diags := n.newProviderInst(ctx, provider)
if diags.HasErrors() {
return nil, diags
}
resp := inst.ConfigureProvider(ctx, providers.ConfigureProviderRequest{
Config: configVal,
// We aren't actually Terraform, so we'll just pretend to be a
// Terraform version that has roughly the same functionality that
// OpenTofu currently has, since providers are permitted to use this to
// adapt their behavior for older versions of Terraform.
TerraformVersion: "1.13.0",
})
diags = diags.Append(resp.Diagnostics)
if resp.Diagnostics.HasErrors() {
return nil, diags
}
return inst, diags
}
// ProviderConfigSchema implements evalglue.Providers.
func (n *newRuntimePlugins) ProviderConfigSchema(ctx context.Context, provider addrs.Provider) (*providers.Schema, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
inst, moreDiags := n.unconfiguredProviderInst(ctx, provider)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
return nil, diags
}
resp := inst.GetProviderSchema(ctx)
diags = diags.Append(resp.Diagnostics)
if resp.Diagnostics.HasErrors() {
return nil, diags
}
return &resp.Provider, diags
}
// ResourceTypeSchema implements evalglue.Providers.
func (n *newRuntimePlugins) ResourceTypeSchema(ctx context.Context, provider addrs.Provider, mode addrs.ResourceMode, typeName string) (*providers.Schema, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
inst, moreDiags := n.unconfiguredProviderInst(ctx, provider)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
return nil, diags
}
resp := inst.GetProviderSchema(ctx)
diags = diags.Append(resp.Diagnostics)
if resp.Diagnostics.HasErrors() {
return nil, diags
}
// NOTE: Callers expect us to return nil if we successfully fetch the
// provider schema and then find there is no matching resource type, because
// the caller is typically in a better position to return a useful error
// message than we are.
var types map[string]providers.Schema
switch mode {
case addrs.ManagedResourceMode:
types = resp.ResourceTypes
case addrs.DataResourceMode:
types = resp.DataSources
case addrs.EphemeralResourceMode:
types = resp.EphemeralResources
default:
// We don't support any other modes, so we'll just treat these as
// a request for a resource type that doesn't exist at all.
return nil, nil
}
ret, ok := types[typeName]
if !ok {
return nil, diags
}
return &ret, diags
}
// ValidateProviderConfig implements evalglue.Providers.
func (n *newRuntimePlugins) ValidateProviderConfig(ctx context.Context, provider addrs.Provider, configVal cty.Value) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
inst, moreDiags := n.unconfiguredProviderInst(ctx, provider)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
return diags
}
resp := inst.ValidateProviderConfig(ctx, providers.ValidateProviderConfigRequest{
Config: configVal,
})
diags = diags.Append(resp.Diagnostics)
return diags
}
// ValidateResourceConfig implements evalglue.Providers.
func (n *newRuntimePlugins) ValidateResourceConfig(ctx context.Context, provider addrs.Provider, mode addrs.ResourceMode, typeName string, configVal cty.Value) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
inst, moreDiags := n.unconfiguredProviderInst(ctx, provider)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
return diags
}
switch mode {
case addrs.ManagedResourceMode:
resp := inst.ValidateResourceConfig(ctx, providers.ValidateResourceConfigRequest{
TypeName: typeName,
Config: configVal,
})
diags = diags.Append(resp.Diagnostics)
case addrs.DataResourceMode:
resp := inst.ValidateDataResourceConfig(ctx, providers.ValidateDataResourceConfigRequest{
TypeName: typeName,
Config: configVal,
})
diags = diags.Append(resp.Diagnostics)
case addrs.EphemeralResourceMode:
resp := inst.ValidateEphemeralConfig(ctx, providers.ValidateEphemeralConfigRequest{
TypeName: typeName,
Config: configVal,
})
diags = diags.Append(resp.Diagnostics)
default:
// If we get here then it's a bug because the cases above should
// cover all valid values of [addrs.ResourceMode].
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Unsupported resource mode",
fmt.Sprintf("Attempted to validate resource of unsupported mode %s; this is a bug in OpenTofu.", mode),
))
}
return diags
}
func (m *newRuntimePlugins) unconfiguredProviderInst(ctx context.Context, provider addrs.Provider) (providers.Unconfigured, tfdiags.Diagnostics) {
m.mu.Lock()
defer m.mu.Unlock()
if running, ok := m.unconfiguredInsts[provider]; ok {
return running, nil
}
inst, diags := m.newProviderInst(ctx, provider)
if diags.HasErrors() {
return nil, diags
}
if m.unconfiguredInsts == nil {
m.unconfiguredInsts = make(map[addrs.Provider]providers.Unconfigured)
}
m.unconfiguredInsts[provider] = inst
return inst, diags
}
// newProviderInst creates a new instance of the given provider.
//
// The result is not retained anywhere inside the receiver. Each call to this
// function returns a new object. A successful result is always an unconfigured
// provider, but we return [providers.Interface] in case the caller would like
// to subsequently configure the result before returning it as
// [providers.Configured].
//
// If you intend to use the resulting instance only for "unconfigured"
// operations like fetching schema, use
// [newRuntimePlugins.unconfiguredProviderInst] instead to potentially reuse
// an already-active instance of the same provider.
func (m *newRuntimePlugins) newProviderInst(_ context.Context, provider addrs.Provider) (providers.Interface, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
factory, ok := m.providers[provider]
if !ok {
// FIXME: If this error remains reachable in the final version of this
// code (i.e. if some caller isn't already guaranteeing that all
// providers from the configuration and state are included here) then
// we should make this error message more actionable.
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Provider unavailable",
fmt.Sprintf("This configuration requires provider %q, but it isn't installed.", provider),
))
return nil, diags
}
inst, err := factory()
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Provider failed to start",
fmt.Sprintf("Failed to launch provider %q: %s.", provider, tfdiags.FormatError(err)),
))
return nil, diags
}
return inst, diags
}
// ProvisionerConfigSchema implements evalglue.Provisioners.
func (n *newRuntimePlugins) ProvisionerConfigSchema(ctx context.Context, typeName string) (*configschema.Block, tfdiags.Diagnostics) {
// TODO: Implement this in terms of [newRuntimePlugins.provisioners].
// But provisioners aren't in scope for our "walking skeleton" phase of
// development, so we'll skip this for now.
var diags tfdiags.Diagnostics
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Cannot use providers in new runtime codepath",
fmt.Sprintf("Can't use provisioner %q: new runtime codepath doesn't know how to instantiate provisioners yet", typeName),
))
return nil, diags
}
// Close terminates any plugins that are managed by this object and are still
// running.
func (n *newRuntimePlugins) Close(ctx context.Context) error {
n.mu.Lock()
defer n.mu.Unlock()
var errs error
for addr, p := range n.unconfiguredInsts {
err := p.Close(ctx)
if err != nil {
errs = errors.Join(errs, fmt.Errorf("closing provider %q: %w", addr, err))
}
}
n.unconfiguredInsts = nil // discard all of the memoized instances
return errs
}

View File

@@ -26,6 +26,9 @@ import (
// each other, and so implementations must use suitable synchronization to // each other, and so implementations must use suitable synchronization to
// avoid data races between calls. // avoid data races between calls.
type PlanGlue interface { type PlanGlue interface {
// I'm not sure that this belongs here
ValidateProviderConfig(ctx context.Context, provider addrs.Provider, configVal cty.Value) tfdiags.Diagnostics
// Creates planned action(s) for the given resource instance and return // Creates planned action(s) for the given resource instance and return
// the planned new state that would result from those actions. // the planned new state that would result from those actions.
// //
@@ -215,6 +218,11 @@ type planningEvalGlue struct {
var _ evalglue.Glue = (*planningEvalGlue)(nil) var _ evalglue.Glue = (*planningEvalGlue)(nil)
// ValidateProviderConfig implements evalglue.Glue.
func (p *planningEvalGlue) ValidateProviderConfig(ctx context.Context, provider addrs.Provider, configVal cty.Value) tfdiags.Diagnostics {
return p.planEngineGlue.ValidateProviderConfig(ctx, provider, configVal)
}
// ResourceInstanceValue implements evalglue.Glue. // ResourceInstanceValue implements evalglue.Glue.
func (p *planningEvalGlue) ResourceInstanceValue(ctx context.Context, ri *configgraph.ResourceInstance, configVal cty.Value, providerInst configgraph.Maybe[*configgraph.ProviderInstance]) (cty.Value, tfdiags.Diagnostics) { func (p *planningEvalGlue) ResourceInstanceValue(ctx context.Context, ri *configgraph.ResourceInstance, configVal cty.Value, providerInst configgraph.Maybe[*configgraph.ProviderInstance]) (cty.Value, tfdiags.Diagnostics) {
desired := &DesiredResourceInstance{ desired := &DesiredResourceInstance{

View File

@@ -318,13 +318,18 @@ func TestPlan_managedResourceUnknownCount(t *testing.T) {
type planGlueCallLog struct { type planGlueCallLog struct {
oracle *eval.PlanningOracle oracle *eval.PlanningOracle
providers eval.Providers providers eval.ProvidersSchema
resourceInstanceRequests addrs.Map[addrs.AbsResourceInstance, *eval.DesiredResourceInstance] resourceInstanceRequests addrs.Map[addrs.AbsResourceInstance, *eval.DesiredResourceInstance]
providerInstanceConfigs addrs.Map[addrs.AbsProviderInstanceCorrect, cty.Value] providerInstanceConfigs addrs.Map[addrs.AbsProviderInstanceCorrect, cty.Value]
mu sync.Mutex mu sync.Mutex
} }
// ValidateProviderConfig implements eval.PlanGlue
func (p *planGlueCallLog) ValidateProviderConfig(ctx context.Context, provider addrs.Provider, configVal cty.Value) tfdiags.Diagnostics {
return nil
}
// PlanDesiredResourceInstance implements eval.PlanGlue. // PlanDesiredResourceInstance implements eval.PlanGlue.
func (p *planGlueCallLog) PlanDesiredResourceInstance(ctx context.Context, inst *eval.DesiredResourceInstance) (cty.Value, tfdiags.Diagnostics) { func (p *planGlueCallLog) PlanDesiredResourceInstance(ctx context.Context, inst *eval.DesiredResourceInstance) (cty.Value, tfdiags.Diagnostics) {
p.mu.Lock() p.mu.Lock()

View File

@@ -165,7 +165,14 @@ type preparationGlue struct {
// preparationGlue uses provider schema information to prepare placeholder // preparationGlue uses provider schema information to prepare placeholder
// "final state" values for resource instances because validation does // "final state" values for resource instances because validation does
// not use information from the state. // not use information from the state.
providers Providers providers ProvidersSchema
}
// ValidateProviderConfig implements evalglue.Glue.
func (v *preparationGlue) ValidateProviderConfig(ctx context.Context, provider addrs.Provider, configVal cty.Value) tfdiags.Diagnostics {
// TODO maybe this is why we create validation glue?
//return v.providers.ValidateProviderConfig(ctx, provider, configVal)
return nil
} }
// ResourceInstanceValue implements evaluationGlue. // ResourceInstanceValue implements evaluationGlue.
@@ -181,6 +188,14 @@ func (v *preparationGlue) ResourceInstanceValue(ctx context.Context, ri *configg
return cty.DynamicVal, diags return cty.DynamicVal, diags
} }
/* TODO maybe this is why we create validation glue?
validateDiags := v.providers.ValidateResourceConfig(ctx, ri.Provider, ri.Addr.Resource.Resource.Mode, ri.Addr.Resource.Resource.Type, configVal)
diags = diags.Append(validateDiags)
if diags.HasErrors() {
// Provider indicated an invalid resource configuration
return cty.DynamicVal, diags
}*/
// FIXME: If we have a managed or data resource instance, as opposed to // FIXME: If we have a managed or data resource instance, as opposed to
// an ephemeral resource instance, then we should check to make sure // an ephemeral resource instance, then we should check to make sure
// that ephemeral-marked values only appear in parts of the configVal // that ephemeral-marked values only appear in parts of the configVal

View File

@@ -20,8 +20,8 @@ import (
// here so that other parts of OpenTofu can interact with the evaluator. // here so that other parts of OpenTofu can interact with the evaluator.
type EvalContext = evalglue.EvalContext type EvalContext = evalglue.EvalContext
type Providers = evalglue.Providers type ProvidersSchema = evalglue.ProvidersSchema
type Provisioners = evalglue.Provisioners type ProvisionersSchema = evalglue.ProvisionersSchema
type ExternalModules = evalglue.ExternalModules type ExternalModules = evalglue.ExternalModules
type UncompiledModule = evalglue.UncompiledModule type UncompiledModule = evalglue.UncompiledModule
@@ -33,10 +33,10 @@ func ModulesForTesting(modules map[addrs.ModuleSourceLocal]*configs.Module) Exte
return tofu2024.ModulesForTesting(modules) return tofu2024.ModulesForTesting(modules)
} }
func ProvidersForTesting(schemas map[addrs.Provider]*providers.GetProviderSchemaResponse) Providers { func ProvidersForTesting(schemas map[addrs.Provider]*providers.GetProviderSchemaResponse) ProvidersSchema {
return evalglue.ProvidersForTesting(schemas) return evalglue.ProvidersForTesting(schemas)
} }
func ProvisionersForTesting(schemas map[string]*configschema.Block) Provisioners { func ProvisionersForTesting(schemas map[string]*configschema.Block) ProvisionersSchema {
return evalglue.ProvisionersForTesting(schemas) return evalglue.ProvisionersForTesting(schemas)
} }

View File

@@ -92,15 +92,6 @@ func (ri *ResourceInstance) Value(ctx context.Context) (cty.Value, tfdiags.Diagn
return exprs.AsEvalError(cty.DynamicVal), diags return exprs.AsEvalError(cty.DynamicVal), diags
} }
// We need some help from our caller to validate the configuration,
// which typically involves asking whatever provider this type of
// resource belongs to.
moreDiags := ri.Glue.ValidateConfig(ctx, configVal)
diags = diags.Append(moreDiags)
if diags.HasErrors() {
return exprs.AsEvalError(cty.DynamicVal), diags
}
providerInst, providerInstMarks, moreDiags := ri.ProviderInstance(ctx) providerInst, providerInstMarks, moreDiags := ri.ProviderInstance(ctx)
diags = diags.Append(moreDiags) diags = diags.Append(moreDiags)
if diags.HasErrors() { if diags.HasErrors() {

View File

@@ -27,11 +27,6 @@ import (
// with the self-dependency detection used by this package to prevent // with the self-dependency detection used by this package to prevent
// deadlocks. // deadlocks.
type ResourceInstanceGlue interface { type ResourceInstanceGlue interface {
// ValidateConfig determines whether the given value is a valid
// configuration for a resource instance of the expected type, presumably
// by asking the provider that the resource type belongs to.
ValidateConfig(ctx context.Context, configVal cty.Value) tfdiags.Diagnostics
// ResultValue returns the results of whatever side-effects are happening // ResultValue returns the results of whatever side-effects are happening
// for this resource in the current phase, such as getting the "planned new // for this resource in the current phase, such as getting the "planned new
// state" of the resource instance during the plan phase, while keeping this // state" of the resource instance during the plan phase, while keeping this

View File

@@ -32,11 +32,11 @@ type EvalContext struct {
// Providers gives access to all of the providers available for use // Providers gives access to all of the providers available for use
// in this context. // in this context.
Providers Providers Providers ProvidersSchema
// Provisioners gives access to all of the provisioners available for // Provisioners gives access to all of the provisioners available for
// use in this context. // use in this context.
Provisioners Provisioners Provisioners ProvisionersSchema
// RootModuleDir and OriginalWorkingDir both represent local filesystem // RootModuleDir and OriginalWorkingDir both represent local filesystem
// directories whose paths are exposed in various ways to expressions // directories whose paths are exposed in various ways to expressions

View File

@@ -9,7 +9,6 @@ import (
"context" "context"
"github.com/apparentlymart/go-versions/versions" "github.com/apparentlymart/go-versions/versions"
"github.com/zclconf/go-cty/cty"
"github.com/opentofu/opentofu/internal/addrs" "github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/configs/configschema" "github.com/opentofu/opentofu/internal/configs/configschema"
@@ -34,10 +33,10 @@ type ExternalModules interface {
} }
// Providers is implemented by callers of this package to provide access // Providers is implemented by callers of this package to provide access
// to the providers needed by a configuration without this package needing // to the provider schemas needed by a configuration without this package needing
// to know anything about how provider plugins work, or whether plugins are // to know anything about how provider plugins work, or whether plugins are
// even being used. // even being used.
type Providers interface { type ProvidersSchema interface {
// ProviderConfigSchema returns the schema that should be used to evaluate // ProviderConfigSchema returns the schema that should be used to evaluate
// a "provider" block associated with the given provider. // a "provider" block associated with the given provider.
// //
@@ -46,16 +45,6 @@ type Providers interface {
// configuration is needed. // configuration is needed.
ProviderConfigSchema(ctx context.Context, provider addrs.Provider) (*providers.Schema, tfdiags.Diagnostics) ProviderConfigSchema(ctx context.Context, provider addrs.Provider) (*providers.Schema, tfdiags.Diagnostics)
// ValidateProviderConfig runs provider-specific logic to check whether
// the given configuration is valid. Returns at least one error diagnostic
// if the configuration is not valid, and may also return warning
// diagnostics regardless of whether the configuration is valid.
//
// The given config value is guaranteed to be an object conforming to
// the schema returned by a previous call to ProviderConfigSchema for
// the same provider.
ValidateProviderConfig(ctx context.Context, provider addrs.Provider, configVal cty.Value) tfdiags.Diagnostics
// ResourceTypeSchema returns the schema for configuration and state of // ResourceTypeSchema returns the schema for configuration and state of
// a resource of the given type, or nil if the given provider does not // a resource of the given type, or nil if the given provider does not
// offer any such resource type. // offer any such resource type.
@@ -63,41 +52,11 @@ type Providers interface {
// Returns error diagnostics if the given provider isn't available for use // Returns error diagnostics if the given provider isn't available for use
// at all, regardless of the resource type. // at all, regardless of the resource type.
ResourceTypeSchema(ctx context.Context, provider addrs.Provider, mode addrs.ResourceMode, typeName string) (*providers.Schema, tfdiags.Diagnostics) ResourceTypeSchema(ctx context.Context, provider addrs.Provider, mode addrs.ResourceMode, typeName string) (*providers.Schema, tfdiags.Diagnostics)
// ValidateResourceConfig runs provider-specific logic to check whether
// the given configuration is valid. Returns at least one error diagnostic
// if the configuration is not valid, and may also return warning
// diagnostics regardless of whether the configuration is valid.
//
// The given config value is guaranteed to be an object conforming to
// the schema returned by a previous call to ResourceTypeSchema for
// the same resource type.
ValidateResourceConfig(ctx context.Context, provider addrs.Provider, mode addrs.ResourceMode, typeName string, configVal cty.Value) tfdiags.Diagnostics
// NewConfiguredProvider starts a _configured_ instance of the given
// provider using the given configuration value.
//
// The evaluation system itself makes no use of configured providers, but
// higher-level processes wrapping it (e.g. the plan and apply engines)
// need to use configured providers for actions related to resources, etc,
// and so this is for their benefit to help ensure that they are definitely
// creating a configured instance of the same provider that other methods
// would be using to return schema information and validation results.
//
// It's the caller's responsibility to ensure that the given configuration
// value is valid according to the provider's schema and validation rules.
// That's usually achieved by taking a value provided by the evaluation
// system, which would then have already been processed using the results
// from [Providers.ProviderConfigSchema] and
// [Providers.ValidateProviderConfig]. If the returned diagnostics contains
// errors then the [providers.Configured] result is invalid and must not be
// used.
NewConfiguredProvider(ctx context.Context, provider addrs.Provider, configVal cty.Value) (providers.Configured, tfdiags.Diagnostics)
} }
// Providers is implemented by callers of this package to provide access // Providers is implemented by callers of this package to provide access
// to the provisioners needed by a configuration. // to the provisioners needed by a configuration.
type Provisioners interface { type ProvisionersSchema interface {
// ProvisionerConfigSchema returns the schema that should be used to // ProvisionerConfigSchema returns the schema that should be used to
// evaluate a "provisioner" block associated with the given provisioner // evaluate a "provisioner" block associated with the given provisioner
// type, or nil if there is no known provisioner of the given name. // type, or nil if there is no known provisioner of the given name.
@@ -124,14 +83,14 @@ func ensureExternalModules(given ExternalModules) ExternalModules {
return given return given
} }
func ensureProviders(given Providers) Providers { func ensureProviders(given ProvidersSchema) ProvidersSchema {
if given == nil { if given == nil {
return emptyDependencies{} return emptyDependencies{}
} }
return given return given
} }
func ensureProvisioners(given Provisioners) Provisioners { func ensureProvisioners(given ProvisionersSchema) ProvisionersSchema {
if given == nil { if given == nil {
return emptyDependencies{} return emptyDependencies{}
} }
@@ -160,14 +119,6 @@ func (e emptyDependencies) ProviderConfigSchema(ctx context.Context, provider ad
return nil, diags return nil, diags
} }
// ValidateProviderConfig implements Providers.
func (e emptyDependencies) ValidateProviderConfig(ctx context.Context, provider addrs.Provider, configVal cty.Value) tfdiags.Diagnostics {
// Because our ResourceTypeSchema implementation never succeeds, there
// can never be a call to this function in practice and so we'll just
// do nothing here.
return nil
}
// ResourceTypeSchema implements Providers. // ResourceTypeSchema implements Providers.
func (e emptyDependencies) ResourceTypeSchema(ctx context.Context, provider addrs.Provider, mode addrs.ResourceMode, typeName string) (*providers.Schema, tfdiags.Diagnostics) { func (e emptyDependencies) ResourceTypeSchema(ctx context.Context, provider addrs.Provider, mode addrs.ResourceMode, typeName string) (*providers.Schema, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics var diags tfdiags.Diagnostics
@@ -179,25 +130,6 @@ func (e emptyDependencies) ResourceTypeSchema(ctx context.Context, provider addr
return nil, diags return nil, diags
} }
// ValidateResourceConfig implements Providers.
func (e emptyDependencies) ValidateResourceConfig(ctx context.Context, provider addrs.Provider, mode addrs.ResourceMode, typeName string, configVal cty.Value) tfdiags.Diagnostics {
// Because our ResourceTypeSchema implementation never succeeds, there
// can never be a call to this function in practice and so we'll just
// do nothing here.
return nil
}
// NewConfiguredProvider implements Providers.
func (e emptyDependencies) NewConfiguredProvider(ctx context.Context, provider addrs.Provider, configVal cty.Value) (providers.Configured, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"No providers are available",
"There are no providers available for use in this context.",
))
return nil, diags
}
// ProvisionerConfigSchema implements Provisioners. // ProvisionerConfigSchema implements Provisioners.
func (e emptyDependencies) ProvisionerConfigSchema(ctx context.Context, typeName string) (*configschema.Block, tfdiags.Diagnostics) { func (e emptyDependencies) ProvisionerConfigSchema(ctx context.Context, typeName string) (*configschema.Block, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics var diags tfdiags.Diagnostics

View File

@@ -21,15 +21,15 @@ import (
// information directly from the given map. // information directly from the given map.
// //
// This is intended for unit testing only. // This is intended for unit testing only.
func ProvidersForTesting(schemas map[addrs.Provider]*providers.GetProviderSchemaResponse) Providers { func ProvidersForTesting(schemas map[addrs.Provider]*providers.GetProviderSchemaResponse) ProvidersSchema {
return providersStatic{schemas} return providersStatic{schemas}
} }
// ProvisionersForTesting returns a [Provisioners] implementation that just // ProvisionersForTesting returns a [ProvisionersSchema] implementation that just
// returns information directly from the given map. // returns information directly from the given map.
// //
// This is intended for unit testing only. // This is intended for unit testing only.
func ProvisionersForTesting(schemas map[string]*configschema.Block) Provisioners { func ProvisionersForTesting(schemas map[string]*configschema.Block) ProvisionersSchema {
return provisionersStatic{schemas} return provisionersStatic{schemas}
} }

View File

@@ -10,6 +10,7 @@ import (
"github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/lang/eval/internal/configgraph" "github.com/opentofu/opentofu/internal/lang/eval/internal/configgraph"
"github.com/opentofu/opentofu/internal/tfdiags" "github.com/opentofu/opentofu/internal/tfdiags"
) )
@@ -24,6 +25,9 @@ import (
// of [Glue] to adapt that into the minimal set of operations // of [Glue] to adapt that into the minimal set of operations
// that are needed regardless of what overall operation we're currently driving. // that are needed regardless of what overall operation we're currently driving.
type Glue interface { type Glue interface {
// I'm not sure that this belongs here
ValidateProviderConfig(ctx context.Context, provider addrs.Provider, configVal cty.Value) tfdiags.Diagnostics
// ResourceInstanceValue returns the result value for the given resource // ResourceInstanceValue returns the result value for the given resource
// instance. // instance.
// //

View File

@@ -98,6 +98,7 @@ func CompileModuleInstance(
module.ProviderRequirements.RequiredProviders, module.ProviderRequirements.RequiredProviders,
call.CalleeAddr, call.CalleeAddr,
call.EvalContext.Providers, call.EvalContext.Providers,
call.EvaluationGlue.ValidateProviderConfig,
) )
providersSidechannel := compileModuleProvidersSidechannel(ctx, call.ProvidersFromParent, ret.providerConfigNodes) providersSidechannel := compileModuleProvidersSidechannel(ctx, call.ProvidersFromParent, ret.providerConfigNodes)

View File

@@ -28,7 +28,8 @@ func compileModuleInstanceProviderConfigs(
declScope exprs.Scope, declScope exprs.Scope,
reqdProviders map[string]*configs.RequiredProvider, reqdProviders map[string]*configs.RequiredProvider,
moduleInstanceAddr addrs.ModuleInstance, moduleInstanceAddr addrs.ModuleInstance,
providers evalglue.Providers, providers evalglue.ProvidersSchema,
validateProviderConfig func(context.Context, addrs.Provider, cty.Value) tfdiags.Diagnostics,
) map[addrs.LocalProviderConfig]*configgraph.ProviderConfig { ) map[addrs.LocalProviderConfig]*configgraph.ProviderConfig {
// FIXME: The following is just enough to make simple examples work, but // FIXME: The following is just enough to make simple examples work, but
// doesn't closely match the rather complicated way that OpenTofu has // doesn't closely match the rather complicated way that OpenTofu has
@@ -89,7 +90,7 @@ func compileModuleInstanceProviderConfigs(
exprs.NewClosure(configEvalable, instanceScope), exprs.NewClosure(configEvalable, instanceScope),
), ),
ValidateConfig: func(ctx context.Context, v cty.Value) tfdiags.Diagnostics { ValidateConfig: func(ctx context.Context, v cty.Value) tfdiags.Diagnostics {
return providers.ValidateProviderConfig(ctx, providerAddr, v) return validateProviderConfig(ctx, providerAddr, v)
}, },
} }
}, },
@@ -156,7 +157,7 @@ func compileModuleInstanceProviderConfigs(
exprs.NewClosure(configEvalable, instanceScope), exprs.NewClosure(configEvalable, instanceScope),
), ),
ValidateConfig: func(ctx context.Context, v cty.Value) tfdiags.Diagnostics { ValidateConfig: func(ctx context.Context, v cty.Value) tfdiags.Diagnostics {
return providers.ValidateProviderConfig(ctx, providerAddr, v) return validateProviderConfig(ctx, providerAddr, v)
}, },
} }
}, },

View File

@@ -27,7 +27,7 @@ func compileModuleInstanceResources(
declScope exprs.Scope, declScope exprs.Scope,
providersSideChannel *moduleProvidersSideChannel, providersSideChannel *moduleProvidersSideChannel,
moduleInstanceAddr addrs.ModuleInstance, moduleInstanceAddr addrs.ModuleInstance,
providers evalglue.Providers, providers evalglue.ProvidersSchema,
getResultValue func(context.Context, *configgraph.ResourceInstance, cty.Value, configgraph.Maybe[*configgraph.ProviderInstance]) (cty.Value, tfdiags.Diagnostics), getResultValue func(context.Context, *configgraph.ResourceInstance, cty.Value, configgraph.Maybe[*configgraph.ProviderInstance]) (cty.Value, tfdiags.Diagnostics),
) map[addrs.Resource]*configgraph.Resource { ) map[addrs.Resource]*configgraph.Resource {
ret := make(map[addrs.Resource]*configgraph.Resource, len(managedConfigs)+len(dataConfigs)+len(ephemeralConfigs)) ret := make(map[addrs.Resource]*configgraph.Resource, len(managedConfigs)+len(dataConfigs)+len(ephemeralConfigs))
@@ -52,7 +52,7 @@ func compileModuleInstanceResource(
declScope exprs.Scope, declScope exprs.Scope,
providersSideChannel *moduleProvidersSideChannel, providersSideChannel *moduleProvidersSideChannel,
moduleInstanceAddr addrs.ModuleInstance, moduleInstanceAddr addrs.ModuleInstance,
providers evalglue.Providers, providers evalglue.ProvidersSchema,
getResultValue func(context.Context, *configgraph.ResourceInstance, cty.Value, configgraph.Maybe[*configgraph.ProviderInstance]) (cty.Value, tfdiags.Diagnostics), getResultValue func(context.Context, *configgraph.ResourceInstance, cty.Value, configgraph.Maybe[*configgraph.ProviderInstance]) (cty.Value, tfdiags.Diagnostics),
) (addrs.Resource, *configgraph.Resource) { ) (addrs.Resource, *configgraph.Resource) {
resourceAddr := config.Addr() resourceAddr := config.Addr()
@@ -105,9 +105,6 @@ func compileModuleInstanceResource(
// in the current phase. (The planned new state during the plan // in the current phase. (The planned new state during the plan
// phase, for example.) // phase, for example.)
inst.Glue = &resourceInstanceGlue{ inst.Glue = &resourceInstanceGlue{
validateConfig: func(ctx context.Context, configVal cty.Value) tfdiags.Diagnostics {
return providers.ValidateResourceConfig(ctx, config.Provider, resourceAddr.Mode, resourceAddr.Type, configVal)
},
getResultValue: func(ctx context.Context, configVal cty.Value, providerInst configgraph.Maybe[*configgraph.ProviderInstance]) (cty.Value, tfdiags.Diagnostics) { getResultValue: func(ctx context.Context, configVal cty.Value, providerInst configgraph.Maybe[*configgraph.ProviderInstance]) (cty.Value, tfdiags.Diagnostics) {
return getResultValue(ctx, inst, configVal, providerInst) return getResultValue(ctx, inst, configVal, providerInst)
}, },
@@ -123,15 +120,9 @@ func compileModuleInstanceResource(
// to us for needs that require interacting with outside concerns like // to us for needs that require interacting with outside concerns like
// provider plugins, an active plan or apply process, etc. // provider plugins, an active plan or apply process, etc.
type resourceInstanceGlue struct { type resourceInstanceGlue struct {
validateConfig func(context.Context, cty.Value) tfdiags.Diagnostics
getResultValue func(context.Context, cty.Value, configgraph.Maybe[*configgraph.ProviderInstance]) (cty.Value, tfdiags.Diagnostics) getResultValue func(context.Context, cty.Value, configgraph.Maybe[*configgraph.ProviderInstance]) (cty.Value, tfdiags.Diagnostics)
} }
// ValidateConfig implements configgraph.ResourceInstanceGlue.
func (r *resourceInstanceGlue) ValidateConfig(ctx context.Context, configVal cty.Value) tfdiags.Diagnostics {
return r.validateConfig(ctx, configVal)
}
// ResultValue implements configgraph.ResourceInstanceGlue. // ResultValue implements configgraph.ResourceInstanceGlue.
func (r *resourceInstanceGlue) ResultValue(ctx context.Context, configVal cty.Value, providerInst configgraph.Maybe[*configgraph.ProviderInstance]) (cty.Value, tfdiags.Diagnostics) { func (r *resourceInstanceGlue) ResultValue(ctx context.Context, configVal cty.Value, providerInst configgraph.Maybe[*configgraph.ProviderInstance]) (cty.Value, tfdiags.Diagnostics) {
return r.getResultValue(ctx, configVal, providerInst) return r.getResultValue(ctx, configVal, providerInst)