mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-19 17:59:05 -05:00
Split out provider schemas vs instances in new engine (#3530)
Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
@@ -7,28 +7,23 @@ package local
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/apparentlymart/go-versions/versions"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/opentofu/opentofu/internal/addrs"
|
||||
"github.com/opentofu/opentofu/internal/backend"
|
||||
"github.com/opentofu/opentofu/internal/configs"
|
||||
"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/plugins"
|
||||
"github.com/opentofu/opentofu/internal/lang/eval"
|
||||
"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/statemgr"
|
||||
"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
|
||||
}
|
||||
|
||||
plugins := &newRuntimePlugins{
|
||||
providers: b.ContextOpts.Providers,
|
||||
provisioners: b.ContextOpts.Provisioners,
|
||||
}
|
||||
plugins := plugins.NewRuntimePlugins(b.ContextOpts.Providers, b.ContextOpts.Provisioners)
|
||||
evalCtx := &eval.EvalContext{
|
||||
RootModuleDir: op.ConfigDir,
|
||||
OriginalWorkingDir: b.ContextOpts.Meta.OriginalWorkingDir,
|
||||
@@ -206,7 +198,7 @@ func (b *Local) opPlanWithExperimentalRuntime(stopCtx context.Context, cancelCtx
|
||||
return
|
||||
}
|
||||
|
||||
plan, moreDiags := planning.PlanChanges(ctx, prevRoundState, configInst)
|
||||
plan, moreDiags := planning.PlanChanges(ctx, prevRoundState, configInst, plugins)
|
||||
diags = diags.Append(moreDiags)
|
||||
// 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
|
||||
@@ -321,254 +313,3 @@ func (n *newRuntimeModules) ModuleConfig(ctx context.Context, source addrs.Modul
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/opentofu/opentofu/internal/engine/plugins"
|
||||
"github.com/opentofu/opentofu/internal/lang/eval"
|
||||
"github.com/opentofu/opentofu/internal/lang/grapheval"
|
||||
"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
|
||||
// on where it gets the information to make those decisions and how it
|
||||
// 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
|
||||
|
||||
planCtx := newPlanContext(configInst.EvalContext(), prevRoundState)
|
||||
planCtx := newPlanContext(configInst.EvalContext(), prevRoundState, providers)
|
||||
|
||||
// This configInst.DrivePlanning call blocks until the evaluator has
|
||||
// visited all expressions in the configuration and calls
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/opentofu/opentofu/internal/addrs"
|
||||
"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/plans"
|
||||
"github.com/opentofu/opentofu/internal/states"
|
||||
@@ -40,12 +41,14 @@ type planContext struct {
|
||||
|
||||
providerInstances *providerInstances
|
||||
|
||||
providers plugins.Providers
|
||||
|
||||
// TODO: something to track which ephemeral resource instances are currently
|
||||
// open? (Do we actually need that, or can we just rely on a background
|
||||
// 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 {
|
||||
prevRoundState = states.NewState()
|
||||
}
|
||||
@@ -61,6 +64,7 @@ func newPlanContext(evalCtx *eval.EvalContext, prevRoundState *states.State) *pl
|
||||
refreshedState: refreshedState.SyncWrapper(),
|
||||
completion: completion,
|
||||
providerInstances: newProviderInstances(completion),
|
||||
providers: providers,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,12 @@ func (p *planGlue) planDesiredDataResourceInstance(ctx context.Context, inst *ev
|
||||
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
|
||||
}
|
||||
|
||||
if inst.ProviderInstance == nil {
|
||||
// TODO: Record that this was deferred because we don't yet know which
|
||||
// provider instance it belongs to.
|
||||
|
||||
@@ -16,6 +16,13 @@ import (
|
||||
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.
|
||||
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
|
||||
panic("unimplemented")
|
||||
|
||||
@@ -35,6 +35,11 @@ type planGlue struct {
|
||||
|
||||
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.
|
||||
//
|
||||
// This is called each time the evaluation system discovers a new resource
|
||||
|
||||
@@ -66,6 +66,12 @@ func (p *planGlue) planDesiredManagedResourceInstance(ctx context.Context, inst
|
||||
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 prevRoundPrivate []byte
|
||||
prevRoundState := p.planCtx.prevRoundState.ResourceInstance(inst.Addr)
|
||||
|
||||
@@ -71,7 +71,6 @@ func (pi *providerInstances) ProviderClient(ctx context.Context, addr addrs.AbsP
|
||||
oracle := planGlue.oracle
|
||||
once := pi.active.Get(addr)
|
||||
return once.Do(ctx, func(ctx context.Context) (providers.Configured, tfdiags.Diagnostics) {
|
||||
evalCtx := oracle.EvalContext(ctx)
|
||||
configVal := oracle.ProviderInstanceConfig(ctx, addr)
|
||||
if configVal == cty.NilVal {
|
||||
// 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
|
||||
// caller will treat it the same as a "configuration not valid enough"
|
||||
// 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
|
||||
// no longer needed, and with asking it to gracefully stop if our
|
||||
|
||||
338
internal/engine/plugins/plugins.go
Normal file
338
internal/engine/plugins/plugins.go
Normal 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
|
||||
}
|
||||
@@ -26,6 +26,9 @@ import (
|
||||
// each other, and so implementations must use suitable synchronization to
|
||||
// avoid data races between calls.
|
||||
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
|
||||
// the planned new state that would result from those actions.
|
||||
//
|
||||
@@ -215,6 +218,11 @@ type planningEvalGlue struct {
|
||||
|
||||
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.
|
||||
func (p *planningEvalGlue) ResourceInstanceValue(ctx context.Context, ri *configgraph.ResourceInstance, configVal cty.Value, providerInst configgraph.Maybe[*configgraph.ProviderInstance]) (cty.Value, tfdiags.Diagnostics) {
|
||||
desired := &DesiredResourceInstance{
|
||||
|
||||
@@ -318,13 +318,18 @@ func TestPlan_managedResourceUnknownCount(t *testing.T) {
|
||||
|
||||
type planGlueCallLog struct {
|
||||
oracle *eval.PlanningOracle
|
||||
providers eval.Providers
|
||||
providers eval.ProvidersSchema
|
||||
|
||||
resourceInstanceRequests addrs.Map[addrs.AbsResourceInstance, *eval.DesiredResourceInstance]
|
||||
providerInstanceConfigs addrs.Map[addrs.AbsProviderInstanceCorrect, cty.Value]
|
||||
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.
|
||||
func (p *planGlueCallLog) PlanDesiredResourceInstance(ctx context.Context, inst *eval.DesiredResourceInstance) (cty.Value, tfdiags.Diagnostics) {
|
||||
p.mu.Lock()
|
||||
|
||||
@@ -165,7 +165,14 @@ type preparationGlue struct {
|
||||
// preparationGlue uses provider schema information to prepare placeholder
|
||||
// "final state" values for resource instances because validation does
|
||||
// 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.
|
||||
@@ -181,6 +188,14 @@ func (v *preparationGlue) ResourceInstanceValue(ctx context.Context, ri *configg
|
||||
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
|
||||
// an ephemeral resource instance, then we should check to make sure
|
||||
// that ephemeral-marked values only appear in parts of the configVal
|
||||
|
||||
@@ -20,8 +20,8 @@ import (
|
||||
// here so that other parts of OpenTofu can interact with the evaluator.
|
||||
|
||||
type EvalContext = evalglue.EvalContext
|
||||
type Providers = evalglue.Providers
|
||||
type Provisioners = evalglue.Provisioners
|
||||
type ProvidersSchema = evalglue.ProvidersSchema
|
||||
type ProvisionersSchema = evalglue.ProvisionersSchema
|
||||
type ExternalModules = evalglue.ExternalModules
|
||||
type UncompiledModule = evalglue.UncompiledModule
|
||||
|
||||
@@ -33,10 +33,10 @@ func ModulesForTesting(modules map[addrs.ModuleSourceLocal]*configs.Module) Exte
|
||||
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)
|
||||
}
|
||||
|
||||
func ProvisionersForTesting(schemas map[string]*configschema.Block) Provisioners {
|
||||
func ProvisionersForTesting(schemas map[string]*configschema.Block) ProvisionersSchema {
|
||||
return evalglue.ProvisionersForTesting(schemas)
|
||||
}
|
||||
|
||||
@@ -92,15 +92,6 @@ func (ri *ResourceInstance) Value(ctx context.Context) (cty.Value, tfdiags.Diagn
|
||||
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)
|
||||
diags = diags.Append(moreDiags)
|
||||
if diags.HasErrors() {
|
||||
|
||||
@@ -27,11 +27,6 @@ import (
|
||||
// with the self-dependency detection used by this package to prevent
|
||||
// deadlocks.
|
||||
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
|
||||
// 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
|
||||
|
||||
@@ -32,11 +32,11 @@ type EvalContext struct {
|
||||
|
||||
// Providers gives access to all of the providers available for use
|
||||
// in this context.
|
||||
Providers Providers
|
||||
Providers ProvidersSchema
|
||||
|
||||
// Provisioners gives access to all of the provisioners available for
|
||||
// use in this context.
|
||||
Provisioners Provisioners
|
||||
Provisioners ProvisionersSchema
|
||||
|
||||
// RootModuleDir and OriginalWorkingDir both represent local filesystem
|
||||
// directories whose paths are exposed in various ways to expressions
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/apparentlymart/go-versions/versions"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/opentofu/opentofu/internal/addrs"
|
||||
"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
|
||||
// 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
|
||||
// even being used.
|
||||
type Providers interface {
|
||||
type ProvidersSchema interface {
|
||||
// ProviderConfigSchema returns the schema that should be used to evaluate
|
||||
// a "provider" block associated with the given provider.
|
||||
//
|
||||
@@ -46,16 +45,6 @@ type Providers interface {
|
||||
// configuration is needed.
|
||||
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
|
||||
// a resource of the given type, or nil if the given provider does not
|
||||
// offer any such resource type.
|
||||
@@ -63,41 +52,11 @@ type Providers interface {
|
||||
// Returns error diagnostics if the given provider isn't available for use
|
||||
// at all, regardless of the resource type.
|
||||
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
|
||||
// to the provisioners needed by a configuration.
|
||||
type Provisioners interface {
|
||||
type ProvisionersSchema interface {
|
||||
// ProvisionerConfigSchema returns the schema that should be used to
|
||||
// evaluate a "provisioner" block associated with the given provisioner
|
||||
// type, or nil if there is no known provisioner of the given name.
|
||||
@@ -124,14 +83,14 @@ func ensureExternalModules(given ExternalModules) ExternalModules {
|
||||
return given
|
||||
}
|
||||
|
||||
func ensureProviders(given Providers) Providers {
|
||||
func ensureProviders(given ProvidersSchema) ProvidersSchema {
|
||||
if given == nil {
|
||||
return emptyDependencies{}
|
||||
}
|
||||
return given
|
||||
}
|
||||
|
||||
func ensureProvisioners(given Provisioners) Provisioners {
|
||||
func ensureProvisioners(given ProvisionersSchema) ProvisionersSchema {
|
||||
if given == nil {
|
||||
return emptyDependencies{}
|
||||
}
|
||||
@@ -160,14 +119,6 @@ func (e emptyDependencies) ProviderConfigSchema(ctx context.Context, provider ad
|
||||
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.
|
||||
func (e emptyDependencies) ResourceTypeSchema(ctx context.Context, provider addrs.Provider, mode addrs.ResourceMode, typeName string) (*providers.Schema, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
@@ -179,25 +130,6 @@ func (e emptyDependencies) ResourceTypeSchema(ctx context.Context, provider addr
|
||||
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.
|
||||
func (e emptyDependencies) ProvisionerConfigSchema(ctx context.Context, typeName string) (*configschema.Block, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
@@ -21,15 +21,15 @@ import (
|
||||
// information directly from the given map.
|
||||
//
|
||||
// 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}
|
||||
}
|
||||
|
||||
// ProvisionersForTesting returns a [Provisioners] implementation that just
|
||||
// ProvisionersForTesting returns a [ProvisionersSchema] implementation that just
|
||||
// returns information directly from the given map.
|
||||
//
|
||||
// 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}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"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/tfdiags"
|
||||
)
|
||||
@@ -24,6 +25,9 @@ import (
|
||||
// of [Glue] to adapt that into the minimal set of operations
|
||||
// that are needed regardless of what overall operation we're currently driving.
|
||||
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
|
||||
// instance.
|
||||
//
|
||||
|
||||
@@ -98,6 +98,7 @@ func CompileModuleInstance(
|
||||
module.ProviderRequirements.RequiredProviders,
|
||||
call.CalleeAddr,
|
||||
call.EvalContext.Providers,
|
||||
call.EvaluationGlue.ValidateProviderConfig,
|
||||
)
|
||||
providersSidechannel := compileModuleProvidersSidechannel(ctx, call.ProvidersFromParent, ret.providerConfigNodes)
|
||||
|
||||
|
||||
@@ -28,7 +28,8 @@ func compileModuleInstanceProviderConfigs(
|
||||
declScope exprs.Scope,
|
||||
reqdProviders map[string]*configs.RequiredProvider,
|
||||
moduleInstanceAddr addrs.ModuleInstance,
|
||||
providers evalglue.Providers,
|
||||
providers evalglue.ProvidersSchema,
|
||||
validateProviderConfig func(context.Context, addrs.Provider, cty.Value) tfdiags.Diagnostics,
|
||||
) map[addrs.LocalProviderConfig]*configgraph.ProviderConfig {
|
||||
// FIXME: The following is just enough to make simple examples work, but
|
||||
// doesn't closely match the rather complicated way that OpenTofu has
|
||||
@@ -89,7 +90,7 @@ func compileModuleInstanceProviderConfigs(
|
||||
exprs.NewClosure(configEvalable, instanceScope),
|
||||
),
|
||||
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),
|
||||
),
|
||||
ValidateConfig: func(ctx context.Context, v cty.Value) tfdiags.Diagnostics {
|
||||
return providers.ValidateProviderConfig(ctx, providerAddr, v)
|
||||
return validateProviderConfig(ctx, providerAddr, v)
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@@ -27,7 +27,7 @@ func compileModuleInstanceResources(
|
||||
declScope exprs.Scope,
|
||||
providersSideChannel *moduleProvidersSideChannel,
|
||||
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),
|
||||
) map[addrs.Resource]*configgraph.Resource {
|
||||
ret := make(map[addrs.Resource]*configgraph.Resource, len(managedConfigs)+len(dataConfigs)+len(ephemeralConfigs))
|
||||
@@ -52,7 +52,7 @@ func compileModuleInstanceResource(
|
||||
declScope exprs.Scope,
|
||||
providersSideChannel *moduleProvidersSideChannel,
|
||||
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),
|
||||
) (addrs.Resource, *configgraph.Resource) {
|
||||
resourceAddr := config.Addr()
|
||||
@@ -105,9 +105,6 @@ func compileModuleInstanceResource(
|
||||
// in the current phase. (The planned new state during the plan
|
||||
// phase, for example.)
|
||||
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) {
|
||||
return getResultValue(ctx, inst, configVal, providerInst)
|
||||
},
|
||||
@@ -123,15 +120,9 @@ func compileModuleInstanceResource(
|
||||
// to us for needs that require interacting with outside concerns like
|
||||
// provider plugins, an active plan or apply process, etc.
|
||||
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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user