Initial tofu package update for plugin manager

Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
Christian Mesh
2025-12-09 08:10:13 -05:00
parent f169484362
commit 5c66f389ae
30 changed files with 498 additions and 923 deletions

View File

@@ -579,3 +579,16 @@ func ResourceModeBlockName(rm ResourceMode) string {
return "unknown"
}
}
func (r ResourceMode) HumanString() string {
switch r {
case ManagedResourceMode:
return "resource"
case DataResourceMode:
return "data source"
case EphemeralResourceMode:
return "ephemeral resource"
default:
return "unknown"
}
}

View File

@@ -0,0 +1,32 @@
package plugins
import (
"context"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/providers"
"github.com/opentofu/opentofu/internal/provisioners"
)
type PluginSchemas interface {
ProvisionerSchemas
ProviderSchemas
}
type PluginManager interface {
ProvisionerManager
ProviderManager
}
func NewPluginManager(ctx context.Context,
providerFactories map[addrs.Provider]providers.Factory,
provisionerFactories map[string]provisioners.Factory,
) PluginManager {
return struct {
ProvisionerManager
ProviderManager
}{
ProvisionerManager: NewProvisionerManager(provisionerFactories),
ProviderManager: NewProviderManager(ctx, providerFactories),
}
}

View File

@@ -12,6 +12,8 @@ import (
type ProviderSchema = providers.ProviderSchema
type ProviderSchemas interface {
HasProvider(addr addrs.Provider) bool
// GetMetadata is not yet implemented or used at this time
GetProviderSchema(ctx context.Context, addr addrs.Provider) ProviderSchema
@@ -56,10 +58,12 @@ type ProviderManager interface {
ValidateResourceConfig(ctx context.Context, addr addrs.Provider, mode addrs.ResourceMode, typeName string, cfgVal cty.Value) tfdiags.Diagnostics
MoveResourceState(ctx context.Context, addr addrs.Provider, req providers.MoveResourceStateRequest) providers.MoveResourceStateResponse
CallFunction(ctx context.Context, addr addrs.Provider, name string, arguments []cty.Value) (cty.Value, error)
ConfigureProvider(ctx context.Context, addr addrs.AbsProviderInstanceCorrect, cfgVal cty.Value) tfdiags.Diagnostics
IsProviderConfigured(addr addrs.AbsProviderInstanceCorrect) bool
ConfiguredProvider(addr addrs.AbsProviderInstanceCorrect) providers.Configured
UpgradeResourceState(ctx context.Context, addr addrs.AbsProviderInstanceCorrect, req providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse
ReadResource(ctx context.Context, addr addrs.AbsProviderInstanceCorrect, req providers.ReadResourceRequest) providers.ReadResourceResponse
PlanResourceChange(ctx context.Context, addr addrs.AbsProviderInstanceCorrect, req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse
@@ -69,7 +73,10 @@ type ProviderManager interface {
OpenEphemeralResource(ctx context.Context, addr addrs.AbsProviderInstanceCorrect, req providers.OpenEphemeralResourceRequest) providers.OpenEphemeralResourceResponse
RenewEphemeralResource(ctx context.Context, addr addrs.AbsProviderInstanceCorrect, req providers.RenewEphemeralResourceRequest) providers.RenewEphemeralResourceResponse
CloseEphemeralResource(ctx context.Context, addr addrs.AbsProviderInstanceCorrect, req providers.CloseEphemeralResourceRequest) providers.CloseEphemeralResourceResponse
// These are weird due to
GetFunctions(ctx context.Context, addr addrs.AbsProviderInstanceCorrect) providers.GetFunctionsResponse
CallFunction(ctx context.Context, addr addrs.AbsProviderInstanceCorrect, name string, arguments []cty.Value) (cty.Value, error)
CloseProvider(ctx context.Context, addr addrs.AbsProviderInstanceCorrect) error

View File

@@ -35,10 +35,11 @@ type providerUnconfigured struct {
active atomic.Int32
}
func NewProviderMananger(ctx context.Context, factories map[addrs.Provider]providers.Factory) ProviderManager {
func NewProviderManager(ctx context.Context, factories map[addrs.Provider]providers.Factory) ProviderManager {
manager := &providerManager{
factories: factories,
schemas: map[addrs.Provider]ProviderSchema{},
unconfigured: map[addrs.Provider]*providerUnconfigured{},
configured: map[string]providers.Configured{},
}
@@ -79,6 +80,11 @@ func NewProviderMananger(ctx context.Context, factories map[addrs.Provider]provi
return manager
}
func (p *providerManager) HasProvider(addr addrs.Provider) bool {
_, ok := p.factories[addr]
return ok
}
// newProviderInst creates a new instance of the given provider.
//
// The result is not retained anywhere inside the receiver. Each call to this
@@ -248,6 +254,15 @@ func (p *providerManager) ResourceTypeSchema(ctx context.Context, addr addrs.Pro
if !ok {
return nil, diags
}
// TODO ret.Block == nil error
/*
schema, currentVersion := (providerSchema).SchemaForResourceAddr(addr.Resource.ContainingResource())
if schema == nil {
// Shouldn't happen since we should've failed long ago if no schema is present
return nil, diags.Append(fmt.Errorf("no schema available for %s while reading state; this is a bug in OpenTofu and should be reported", addr))
}*/
return &ret, diags
}
@@ -273,15 +288,41 @@ func (p *providerManager) ValidateResourceConfig(ctx context.Context, addr addrs
if err != nil {
return tfdiags.Diagnostics{}.Append(err)
}
return inst.ValidateResourceConfig(ctx, providers.ValidateResourceConfigRequest{Config: cfgVal}).Diagnostics
switch mode {
case addrs.ManagedResourceMode:
return inst.ValidateResourceConfig(ctx, providers.ValidateResourceConfigRequest{
TypeName: typeName,
Config: cfgVal,
}).Diagnostics
case addrs.DataResourceMode:
return inst.ValidateDataResourceConfig(ctx, providers.ValidateDataResourceConfigRequest{
TypeName: typeName,
Config: cfgVal,
}).Diagnostics
case addrs.EphemeralResourceMode:
return inst.ValidateEphemeralConfig(ctx, providers.ValidateEphemeralConfigRequest{
TypeName: typeName,
Config: cfgVal,
}).Diagnostics
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
}
}
func (p *providerManager) MoveResourceState(ctx context.Context, addr addrs.Provider, req providers.MoveResourceStateRequest) providers.MoveResourceStateResponse {
panic("not implemented") // TODO: Implement
}
func (p *providerManager) CallFunction(ctx context.Context, addr addrs.Provider, name string, arguments []cty.Value) (cty.Value, error) {
panic("not implemented") // TODO: Implement
func (p *providerManager) IsProviderConfigured(addr addrs.AbsProviderInstanceCorrect) bool {
p.configuredLock.Lock()
defer p.configuredLock.Unlock()
_, ok := p.configured[addr.String()]
return ok
}
func (p *providerManager) ConfigureProvider(ctx context.Context, addr addrs.AbsProviderInstanceCorrect, cfgVal cty.Value) tfdiags.Diagnostics {
@@ -319,7 +360,8 @@ func (p *providerManager) ConfigureProvider(ctx context.Context, addr addrs.AbsP
return diags
}
// tfplugin5 may return a different PreparedConfig, but we throw that away in all code paths?
// If the provider returns something different, log a warning to help
// indicate to provider developers that the value is not used.
if validate.PreparedConfig != cty.NilVal && !validate.PreparedConfig.IsNull() && !validate.PreparedConfig.RawEquals(cfgVal) {
log.Printf("[WARN] ValidateProviderConfig from %q changed the config value, but that value is unused", addr)
}
@@ -343,7 +385,7 @@ func (p *providerManager) ConfigureProvider(ctx context.Context, addr addrs.AbsP
return diags
}
func (p *providerManager) configuredProvider(addr addrs.AbsProviderInstanceCorrect) providers.Configured {
func (p *providerManager) ConfiguredProvider(addr addrs.AbsProviderInstanceCorrect) providers.Configured {
p.configuredLock.Lock()
defer p.configuredLock.Unlock()
@@ -357,53 +399,82 @@ func (p *providerManager) configuredProvider(addr addrs.AbsProviderInstanceCorre
}
func (p *providerManager) UpgradeResourceState(ctx context.Context, addr addrs.AbsProviderInstanceCorrect, req providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse {
configured := p.configuredProvider(addr)
configured := p.ConfiguredProvider(addr)
return configured.UpgradeResourceState(ctx, req)
}
func (p *providerManager) ReadResource(ctx context.Context, addr addrs.AbsProviderInstanceCorrect, req providers.ReadResourceRequest) providers.ReadResourceResponse {
configured := p.configuredProvider(addr)
configured := p.ConfiguredProvider(addr)
return configured.ReadResource(ctx, req)
}
func (p *providerManager) PlanResourceChange(ctx context.Context, addr addrs.AbsProviderInstanceCorrect, req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
configured := p.configuredProvider(addr)
configured := p.ConfiguredProvider(addr)
return configured.PlanResourceChange(ctx, req)
}
func (p *providerManager) ApplyResourceChange(ctx context.Context, addr addrs.AbsProviderInstanceCorrect, req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
configured := p.configuredProvider(addr)
configured := p.ConfiguredProvider(addr)
return configured.ApplyResourceChange(ctx, req)
}
func (p *providerManager) ImportResourceState(ctx context.Context, addr addrs.AbsProviderInstanceCorrect, req providers.ImportResourceStateRequest) providers.ImportResourceStateResponse {
configured := p.configuredProvider(addr)
configured := p.ConfiguredProvider(addr)
return configured.ImportResourceState(ctx, req)
}
func (p *providerManager) ReadDataSource(ctx context.Context, addr addrs.AbsProviderInstanceCorrect, req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
configured := p.configuredProvider(addr)
configured := p.ConfiguredProvider(addr)
return configured.ReadDataSource(ctx, req)
}
func (p *providerManager) OpenEphemeralResource(ctx context.Context, addr addrs.AbsProviderInstanceCorrect, req providers.OpenEphemeralResourceRequest) providers.OpenEphemeralResourceResponse {
configured := p.configuredProvider(addr)
configured := p.ConfiguredProvider(addr)
return configured.OpenEphemeralResource(ctx, req)
}
func (p *providerManager) RenewEphemeralResource(ctx context.Context, addr addrs.AbsProviderInstanceCorrect, req providers.RenewEphemeralResourceRequest) providers.RenewEphemeralResourceResponse {
configured := p.configuredProvider(addr)
configured := p.ConfiguredProvider(addr)
return configured.RenewEphemeralResource(ctx, req)
}
func (p *providerManager) CloseEphemeralResource(ctx context.Context, addr addrs.AbsProviderInstanceCorrect, req providers.CloseEphemeralResourceRequest) providers.CloseEphemeralResourceResponse {
configured := p.configuredProvider(addr)
configured := p.ConfiguredProvider(addr)
return configured.CloseEphemeralResource(ctx, req)
}
func (p *providerManager) CallFunction(ctx context.Context, addr addrs.AbsProviderInstanceCorrect, name string, arguments []cty.Value) (cty.Value, error) {
req := providers.CallFunctionRequest{
Name: name,
Arguments: arguments,
}
var resp providers.CallFunctionResponse
if p.IsProviderConfigured(addr) {
configured := p.ConfiguredProvider(addr)
resp = configured.CallFunction(ctx, req)
} else {
unconfigured, done, diags := p.unconfiguredProvider(ctx, addr.Config.Config.Provider)
defer done()
if diags.HasErrors() {
return cty.NilVal, diags.Err()
}
resp = unconfigured.(providers.Interface).CallFunction(ctx, req)
}
return resp.Result, resp.Error
}
func (p *providerManager) GetFunctions(ctx context.Context, addr addrs.AbsProviderInstanceCorrect) providers.GetFunctionsResponse {
configured := p.configuredProvider(addr)
return configured.GetFunctions(ctx)
if p.IsProviderConfigured(addr) {
configured := p.ConfiguredProvider(addr)
return configured.GetFunctions(ctx)
}
unconfigured, done, diags := p.unconfiguredProvider(ctx, addr.Config.Config.Provider)
defer done()
if diags.HasErrors() {
return providers.GetFunctionsResponse{Diagnostics: diags}
}
return unconfigured.(providers.Interface).GetFunctions(ctx)
}
func (p *providerManager) CloseProvider(ctx context.Context, addr addrs.AbsProviderInstanceCorrect) error {

View File

@@ -0,0 +1,121 @@
package plugins
import (
"context"
"fmt"
"sync"
"github.com/opentofu/opentofu/internal/configs/configschema"
"github.com/opentofu/opentofu/internal/provisioners"
"github.com/opentofu/opentofu/internal/tfdiags"
"github.com/zclconf/go-cty/cty"
)
type ProvisionerSchemas interface {
HasProvisioner(typ string) bool
ProvisionerSchema(typ string) (*configschema.Block, error)
}
type ProvisionerManager interface {
ProvisionerSchemas
ValidateProvisionerConfig(ctx context.Context, typ string, config cty.Value) tfdiags.Diagnostics
ProvisionResource(ctx context.Context, typ string, config cty.Value, connection cty.Value, output provisioners.UIOutput) tfdiags.Diagnostics
}
type provisionerManager struct {
factories map[string]provisioners.Factory
instancesLock sync.Mutex
instances map[string]provisioners.Interface
}
func NewProvisionerManager(factories map[string]provisioners.Factory) ProvisionerManager {
return &provisionerManager{
factories: factories,
instances: map[string]provisioners.Interface{},
}
}
func (p *provisionerManager) HasProvisioner(typ string) bool {
_, ok := p.factories[typ]
return ok
}
func (p *provisionerManager) provisioner(typ string) (provisioners.Interface, error) {
p.instancesLock.Lock()
defer p.instancesLock.Unlock()
instance, ok := p.instances[typ]
if !ok {
f, ok := p.factories[typ]
if !ok {
return nil, fmt.Errorf("unavailable provisioner %q", typ)
}
var err error
instance, err = f()
if err != nil {
return nil, err
}
p.instances[typ] = instance
}
return instance, nil
}
// ProvisionerSchema uses a temporary instance of the provisioner with the
// given type name to obtain the schema for that provisioner's configuration.
//
// ProvisionerSchema memoizes results by provisioner type name, so it's fine
// to repeatedly call this method with the same name if various different
// parts of OpenTofu all need the same schema information.
func (p *provisionerManager) ProvisionerSchema(typ string) (*configschema.Block, error) {
provisioner, err := p.provisioner(typ)
if err != nil {
return nil, fmt.Errorf("failed to instantiate provisioner %q to obtain schema: %w", typ, err)
}
defer provisioner.Close()
resp := provisioner.GetSchema()
if resp.Diagnostics.HasErrors() {
return nil, fmt.Errorf("failed to retrieve schema from provisioner %q: %w", typ, resp.Diagnostics.Err())
}
return resp.Provisioner, nil
}
func (p *provisionerManager) ValidateProvisionerConfig(ctx context.Context, typ string, config cty.Value) tfdiags.Diagnostics {
provisioner, err := p.provisioner(typ)
if err != nil {
return tfdiags.Diagnostics{}.Append(fmt.Errorf("failed to instantiate provisioner %q to validate config: %w", typ, err))
}
return provisioner.ValidateProvisionerConfig(provisioners.ValidateProvisionerConfigRequest{
Config: config,
}).Diagnostics
}
func (p *provisionerManager) ProvisionResource(ctx context.Context, typ string, config cty.Value, connection cty.Value, output provisioners.UIOutput) tfdiags.Diagnostics {
provisioner, err := p.provisioner(typ)
if err != nil {
return tfdiags.Diagnostics{}.Append(fmt.Errorf("failed to instantiate provisioner %q to validate config: %w", typ, err))
}
return provisioner.ProvisionResource(provisioners.ProvisionResourceRequest{
Config: config,
Connection: connection,
UIOutput: output,
}).Diagnostics
}
func (p *provisionerManager) CloseProvisioners() error {
p.instancesLock.Lock()
defer p.instancesLock.Unlock()
var diags tfdiags.Diagnostics
for name, prov := range p.instances {
err := prov.Close()
if err != nil {
diags = diags.Append(fmt.Errorf("provisioner.Close %s: %w", name, err))
}
}
return diags.Err()
}

View File

@@ -18,6 +18,7 @@ import (
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/logging"
"github.com/opentofu/opentofu/internal/plugins"
"github.com/opentofu/opentofu/internal/providers"
"github.com/opentofu/opentofu/internal/provisioners"
"github.com/opentofu/opentofu/internal/states"
@@ -80,7 +81,7 @@ type Context struct {
// operations.
meta *ContextMeta
plugins *contextPlugins
plugins plugins.PluginManager
hooks []Hook
sh *stopHook
@@ -135,7 +136,8 @@ func NewContext(opts *ContextOpts) (*Context, tfdiags.Diagnostics) {
par = 10
}
plugins := newContextPlugins(opts.Providers, opts.Provisioners)
// TODO move up
plugins := plugins.NewPluginManager(context.TODO(), opts.Providers, opts.Provisioners)
log.Printf("[TRACE] tofu.NewContext: complete")

View File

@@ -15,17 +15,18 @@ import (
"github.com/zclconf/go-cty/cty/function"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/plugins"
"github.com/opentofu/opentofu/internal/providers"
"github.com/opentofu/opentofu/internal/tfdiags"
)
// This builds a provider function using an EvalContext and some additional information
// This is split out of BuiltinEvalContext for testing
func evalContextProviderFunction(ctx context.Context, provider providers.Interface, op walkOperation, pf addrs.ProviderFunction, rng tfdiags.SourceRange) (*function.Function, tfdiags.Diagnostics) {
func evalContextProviderFunction(ctx context.Context, providers plugins.ProviderManager, providerAddr addrs.AbsProviderInstanceCorrect, op walkOperation, pf addrs.ProviderFunction, rng tfdiags.SourceRange) (*function.Function, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
// First try to look up the function from provider schema
schema := provider.GetProviderSchema(ctx)
schema := providers.GetProviderSchema(ctx, providerAddr.Config.Config.Provider)
if schema.Diagnostics.HasErrors() {
return nil, schema.Diagnostics
}
@@ -54,7 +55,7 @@ func evalContextProviderFunction(ctx context.Context, provider providers.Interfa
}
// The provider may be configured and present additional functions via GetFunctions
specs := provider.GetFunctions(ctx)
specs := providers.GetFunctions(ctx, providerAddr)
if specs.Diagnostics.HasErrors() {
return nil, specs.Diagnostics
}
@@ -71,7 +72,7 @@ func evalContextProviderFunction(ctx context.Context, provider providers.Interfa
}
}
fn := providerFunction(ctx, pf.Function, spec, provider)
fn := providerFunction(ctx, pf.Function, spec, providers, providerAddr)
return &fn, nil
@@ -80,7 +81,7 @@ func evalContextProviderFunction(ctx context.Context, provider providers.Interfa
// Turn a provider function spec into a cty callable function
// This will use the instance factory to get a provider to support the
// function call.
func providerFunction(ctx context.Context, name string, spec providers.FunctionSpec, provider providers.Interface) function.Function {
func providerFunction(ctx context.Context, name string, spec providers.FunctionSpec, pm plugins.ProviderManager, providerAddr addrs.AbsProviderInstanceCorrect) function.Function {
params := make([]function.Parameter, len(spec.Parameters))
for i, param := range spec.Parameters {
params[i] = providerFunctionParameter(param)
@@ -93,17 +94,14 @@ func providerFunction(ctx context.Context, name string, spec providers.FunctionS
}
impl := func(args []cty.Value, retType cty.Type) (cty.Value, error) {
resp := provider.CallFunction(ctx, providers.CallFunctionRequest{
Name: name,
Arguments: args,
})
result, err := pm.CallFunction(ctx, providerAddr, name, args)
if argError, ok := resp.Error.(*providers.CallFunctionArgumentError); ok {
if argError, ok := err.(*providers.CallFunctionArgumentError); ok {
// Convert ArgumentError to cty error
return resp.Result, function.NewArgError(argError.FunctionArgument, errors.New(argError.Text))
return result, function.NewArgError(argError.FunctionArgument, errors.New(argError.Text))
}
return resp.Result, resp.Error
return result, err
}
return function.New(&function.Spec{

View File

@@ -1,193 +0,0 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package tofu
import (
"context"
"fmt"
"log"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/configs/configschema"
"github.com/opentofu/opentofu/internal/providers"
"github.com/opentofu/opentofu/internal/provisioners"
)
// contextPlugins represents a library of available plugins (providers and
// provisioners) which we assume will all be used with the same
// tofu.Context, and thus it'll be safe to cache certain information
// about the providers for performance reasons.
type contextPlugins struct {
providerFactories map[addrs.Provider]providers.Factory
provisionerFactories map[string]provisioners.Factory
}
func newContextPlugins(providerFactories map[addrs.Provider]providers.Factory, provisionerFactories map[string]provisioners.Factory) *contextPlugins {
return &contextPlugins{
providerFactories: providerFactories,
provisionerFactories: provisionerFactories,
}
}
func (cp *contextPlugins) HasProvider(addr addrs.Provider) bool {
_, ok := cp.providerFactories[addr]
return ok
}
func (cp *contextPlugins) NewProviderInstance(addr addrs.Provider) (providers.Interface, error) {
f, ok := cp.providerFactories[addr]
if !ok {
return nil, fmt.Errorf("unavailable provider %q", addr.String())
}
return f()
}
func (cp *contextPlugins) HasProvisioner(typ string) bool {
_, ok := cp.provisionerFactories[typ]
return ok
}
func (cp *contextPlugins) NewProvisionerInstance(typ string) (provisioners.Interface, error) {
f, ok := cp.provisionerFactories[typ]
if !ok {
return nil, fmt.Errorf("unavailable provisioner %q", typ)
}
return f()
}
// ProviderSchema uses a temporary instance of the provider with the given
// address to obtain the full schema for all aspects of that provider.
//
// ProviderSchema memoizes results by unique provider address, so it's fine
// to repeatedly call this method with the same address if various different
// parts of OpenTofu all need the same schema information.
func (cp *contextPlugins) ProviderSchema(ctx context.Context, addr addrs.Provider) (providers.ProviderSchema, error) {
// Check the global schema cache first.
// This cache is only written by the provider client, and transparently
// used by GetProviderSchema, but we check it here because at this point we
// may be able to avoid spinning up the provider instance at all.
//
// It's worth noting that ServerCapabilities.GetProviderSchemaOptional is ignored here.
// That is because we're checking *prior* to the provider's instantiation.
// GetProviderSchemaOptional only says that *if we instantiate a provider*,
// then we need to run the get schema call at least once.
// BUG This SHORT CIRCUITS the logic below and is not the only code which inserts provider schemas into the cache!!
schemas, ok := providers.SchemaCache.Get(addr)
if ok {
log.Printf("[TRACE] tofu.contextPlugins: Serving provider %q schema from global schema cache", addr)
return schemas, nil
}
log.Printf("[TRACE] tofu.contextPlugins: Initializing provider %q to read its schema", addr)
provider, err := cp.NewProviderInstance(addr)
if err != nil {
return schemas, fmt.Errorf("failed to instantiate provider %q to obtain schema: %w", addr, err)
}
defer provider.Close(ctx)
resp := provider.GetProviderSchema(ctx)
if resp.Diagnostics.HasErrors() {
return resp, fmt.Errorf("failed to retrieve schema from provider %q: %w", addr, resp.Diagnostics.Err())
}
if resp.Provider.Version < 0 {
// We're not using the version numbers here yet, but we'll check
// for validity anyway in case we start using them in future.
return resp, fmt.Errorf("provider %s has invalid negative schema version for its configuration blocks,which is a bug in the provider ", addr)
}
for t, r := range resp.ResourceTypes {
if err := r.Block.InternalValidate(); err != nil {
return resp, fmt.Errorf("provider %s has invalid schema for managed resource type %q, which is a bug in the provider: %w", addr, t, err)
}
if r.Version < 0 {
return resp, fmt.Errorf("provider %s has invalid negative schema version for managed resource type %q, which is a bug in the provider", addr, t)
}
}
for t, d := range resp.DataSources {
if err := d.Block.InternalValidate(); err != nil {
return resp, fmt.Errorf("provider %s has invalid schema for data resource type %q, which is a bug in the provider: %w", addr, t, err)
}
if d.Version < 0 {
// We're not using the version numbers here yet, but we'll check
// for validity anyway in case we start using them in future.
return resp, fmt.Errorf("provider %s has invalid negative schema version for data resource type %q, which is a bug in the provider", addr, t)
}
}
for t, d := range resp.EphemeralResources {
if err := d.Block.InternalValidate(); err != nil {
return resp, fmt.Errorf("provider %s has invalid schema for ephemeral resource type %q, which is a bug in the provider: %w", addr, t, err)
}
if d.Version < 0 {
// We're not using the version numbers here yet, but we'll check
// for validity anyway in case we start using them in future.
return resp, fmt.Errorf("provider %s has invalid negative schema version for ephemeral resource type %q, which is a bug in the provider", addr, t)
}
}
return resp, nil
}
// ProviderConfigSchema is a helper wrapper around ProviderSchema which first
// reads the full schema of the given provider and then extracts just the
// provider's configuration schema, which defines what's expected in a
// "provider" block in the configuration when configuring this provider.
func (cp *contextPlugins) ProviderConfigSchema(ctx context.Context, providerAddr addrs.Provider) (*configschema.Block, error) {
providerSchema, err := cp.ProviderSchema(ctx, providerAddr)
if err != nil {
return nil, err
}
return providerSchema.Provider.Block, nil
}
// ResourceTypeSchema is a helper wrapper around ProviderSchema which first
// reads the schema of the given provider and then tries to find the schema
// for the resource type of the given resource mode in that provider.
//
// ResourceTypeSchema will return an error if the provider schema lookup
// fails, but will return nil if the provider schema lookup succeeds but then
// the provider doesn't have a resource of the requested type.
//
// Managed resource types have versioned schemas, so the second return value
// is the current schema version number for the requested resource. The version
// is irrelevant for other resource modes.
func (cp *contextPlugins) ResourceTypeSchema(ctx context.Context, providerAddr addrs.Provider, resourceMode addrs.ResourceMode, resourceType string) (*configschema.Block, uint64, error) {
providerSchema, err := cp.ProviderSchema(ctx, providerAddr)
if err != nil {
return nil, 0, err
}
schema, version := providerSchema.SchemaForResourceType(resourceMode, resourceType)
return schema, version, nil
}
// ProvisionerSchema uses a temporary instance of the provisioner with the
// given type name to obtain the schema for that provisioner's configuration.
//
// ProvisionerSchema memoizes results by provisioner type name, so it's fine
// to repeatedly call this method with the same name if various different
// parts of OpenTofu all need the same schema information.
func (cp *contextPlugins) ProvisionerSchema(typ string) (*configschema.Block, error) {
log.Printf("[TRACE] tofu.contextPlugins: Initializing provisioner %q to read its schema", typ)
provisioner, err := cp.NewProvisionerInstance(typ)
if err != nil {
return nil, fmt.Errorf("failed to instantiate provisioner %q to obtain schema: %w", typ, err)
}
defer provisioner.Close()
resp := provisioner.GetSchema()
if resp.Diagnostics.HasErrors() {
return nil, fmt.Errorf("failed to retrieve schema from provisioner %q: %w", typ, resp.Diagnostics.Err())
}
return resp.Provisioner, nil
}

View File

@@ -1,87 +0,0 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package tofu
import (
"github.com/zclconf/go-cty/cty"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/configs/configschema"
"github.com/opentofu/opentofu/internal/providers"
"github.com/opentofu/opentofu/internal/provisioners"
)
// simpleMockPluginLibrary returns a plugin library pre-configured with
// one provider and one provisioner, both called "test".
//
// The provider is built with simpleMockProvider and the provisioner with
// simpleMockProvisioner, and all schemas used in both are as built by
// function simpleTestSchema.
//
// Each call to this function produces an entirely-separate set of objects,
// so the caller can feel free to modify the returned value to further
// customize the mocks contained within.
func simpleMockPluginLibrary() *contextPlugins {
// We create these out here, rather than in the factory functions below,
// because we want each call to the factory to return the _same_ instance,
// so that test code can customize it before passing this component
// factory into real code under test.
provider := simpleMockProvider()
provisioner := simpleMockProvisioner()
ret := &contextPlugins{
providerFactories: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): func() (providers.Interface, error) {
return provider, nil
},
},
provisionerFactories: map[string]provisioners.Factory{
"test": func() (provisioners.Interface, error) {
return provisioner, nil
},
},
}
return ret
}
// simpleTestSchema returns a block schema that contains a few optional
// attributes for use in tests.
//
// The returned schema contains the following optional attributes:
//
// - test_string, of type string
// - test_number, of type number
// - test_bool, of type bool
// - test_list, of type list(string)
// - test_map, of type map(string)
//
// Each call to this function produces an entirely new schema instance, so
// callers can feel free to modify it once returned.
func simpleTestSchema() *configschema.Block {
return &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"test_string": {
Type: cty.String,
Optional: true,
},
"test_number": {
Type: cty.Number,
Optional: true,
},
"test_bool": {
Type: cty.Bool,
Optional: true,
},
"test_list": {
Type: cty.List(cty.String),
Optional: true,
},
"test_map": {
Type: cty.Map(cty.String),
Optional: true,
},
},
}
}

View File

@@ -18,8 +18,7 @@ import (
"github.com/opentofu/opentofu/internal/instances"
"github.com/opentofu/opentofu/internal/lang"
"github.com/opentofu/opentofu/internal/plans"
"github.com/opentofu/opentofu/internal/providers"
"github.com/opentofu/opentofu/internal/provisioners"
"github.com/opentofu/opentofu/internal/plugins"
"github.com/opentofu/opentofu/internal/refactoring"
"github.com/opentofu/opentofu/internal/states"
"github.com/opentofu/opentofu/internal/tfdiags"
@@ -41,44 +40,9 @@ type EvalContext interface {
// Input is the UIInput object for interacting with the UI.
Input() UIInput
// InitProvider initializes the provider with the given address, and returns
// the implementation of the resource provider or an error.
//
// It is an error to initialize the same provider more than once. This
// method will panic if the module instance address of the given provider
// configuration does not match the Path() of the EvalContext.
InitProvider(ctx context.Context, addr addrs.AbsProviderConfig, key addrs.InstanceKey) (providers.Interface, error)
Provisioners() plugins.ProvisionerManager
// Provider gets the provider instance with the given address (already
// initialized) or returns nil if the provider isn't initialized.
//
// This method expects an _absolute_ provider configuration address, since
// resources in one module are able to use providers from other modules.
// InitProvider must've been called on the EvalContext of the module
// that owns the given provider before calling this method.
Provider(context.Context, addrs.AbsProviderConfig, addrs.InstanceKey) providers.Interface
// ProviderSchema retrieves the schema for a particular provider, which
// must have already been initialized with InitProvider.
//
// This method expects an _absolute_ provider configuration address, since
// resources in one module are able to use providers from other modules.
ProviderSchema(context.Context, addrs.AbsProviderConfig) (providers.ProviderSchema, error)
// CloseProvider closes provider connections that aren't needed anymore.
//
// This method will panic if the module instance address of the given
// provider configuration does not match the Path() of the EvalContext.
CloseProvider(context.Context, addrs.AbsProviderConfig) error
// ConfigureProvider configures the provider with the given
// configuration. This is a separate context call because this call
// is used to store the provider configuration for inheritance lookups
// with ParentProviderConfig().
//
// This method will panic if the module instance address of the given
// provider configuration does not match the Path() of the EvalContext.
ConfigureProvider(context.Context, addrs.AbsProviderConfig, addrs.InstanceKey, cty.Value) tfdiags.Diagnostics
Providers() plugins.ProviderManager
// ProviderInput and SetProviderInput are used to configure providers
// from user input.
@@ -88,17 +52,6 @@ type EvalContext interface {
ProviderInput(context.Context, addrs.AbsProviderConfig) map[string]cty.Value
SetProviderInput(context.Context, addrs.AbsProviderConfig, map[string]cty.Value)
// Provisioner gets the provisioner instance with the given name.
Provisioner(string) (provisioners.Interface, error)
// ProvisionerSchema retrieves the main configuration schema for a
// particular provisioner, which must have already been initialized with
// InitProvisioner.
ProvisionerSchema(string) (*configschema.Block, error)
// CloseProvisioner closes all provisioner plugins.
CloseProvisioners() error
// EvaluateBlock takes the given raw configuration block and associated
// schema and evaluates it to produce a value of an object type that
// conforms to the implied type of the schema.

View File

@@ -22,12 +22,10 @@ import (
"github.com/opentofu/opentofu/internal/instances"
"github.com/opentofu/opentofu/internal/lang"
"github.com/opentofu/opentofu/internal/plans"
"github.com/opentofu/opentofu/internal/providers"
"github.com/opentofu/opentofu/internal/provisioners"
"github.com/opentofu/opentofu/internal/plugins"
"github.com/opentofu/opentofu/internal/refactoring"
"github.com/opentofu/opentofu/internal/states"
"github.com/opentofu/opentofu/internal/tfdiags"
"github.com/opentofu/opentofu/version"
)
// BuiltinEvalContext is an EvalContext implementation that is used by
@@ -61,18 +59,14 @@ type BuiltinEvalContext struct {
// Plugins is a library of plugin components (providers and provisioners)
// available for use during a graph walk.
Plugins *contextPlugins
Plugins plugins.PluginManager
Hooks []Hook
InputValue UIInput
ProviderLock *sync.Mutex
ProviderCache map[string]map[addrs.InstanceKey]providers.Interface
ProviderInputConfig map[string]map[string]cty.Value
ProvisionerLock *sync.Mutex
ProvisionerCache map[string]provisioners.Interface
ChangesValue *plans.ChangesSync
StateValue *states.SyncState
ChecksValue *checks.State
@@ -128,95 +122,12 @@ func (c *BuiltinEvalContext) Input() UIInput {
return c.InputValue
}
func (c *BuiltinEvalContext) InitProvider(ctx context.Context, addr addrs.AbsProviderConfig, providerInstanceKey addrs.InstanceKey) (providers.Interface, error) {
c.ProviderLock.Lock()
defer c.ProviderLock.Unlock()
providerAddrKey := addr.String()
if c.ProviderCache[providerAddrKey] == nil {
c.ProviderCache[providerAddrKey] = make(map[addrs.InstanceKey]providers.Interface)
}
// If we have already initialized, it is an error
if _, ok := c.ProviderCache[providerAddrKey][providerInstanceKey]; ok {
return nil, fmt.Errorf("%s is already initialized", addr)
}
p, err := c.Plugins.NewProviderInstance(addr.Provider)
if err != nil {
return nil, err
}
log.Printf("[TRACE] BuiltinEvalContext: Initialized %q%s provider for %s", addr.String(), providerInstanceKey, addr)
c.ProviderCache[providerAddrKey][providerInstanceKey] = p
return p, nil
func (c *BuiltinEvalContext) Providers() plugins.ProviderManager {
return c.Plugins
}
func (c *BuiltinEvalContext) Provider(_ context.Context, addr addrs.AbsProviderConfig, key addrs.InstanceKey) providers.Interface {
c.ProviderLock.Lock()
defer c.ProviderLock.Unlock()
providerAddrKey := addr.String()
pm, ok := c.ProviderCache[providerAddrKey]
if !ok {
return nil
}
return pm[key]
}
func (c *BuiltinEvalContext) ProviderSchema(ctx context.Context, addr addrs.AbsProviderConfig) (providers.ProviderSchema, error) {
return c.Plugins.ProviderSchema(ctx, addr.Provider)
}
func (c *BuiltinEvalContext) CloseProvider(ctx context.Context, addr addrs.AbsProviderConfig) error {
c.ProviderLock.Lock()
defer c.ProviderLock.Unlock()
var diags tfdiags.Diagnostics
providerAddrKey := addr.String()
providerMap := c.ProviderCache[providerAddrKey]
if providerMap != nil {
for _, provider := range providerMap {
err := provider.Close(ctx)
if err != nil {
diags = diags.Append(err)
}
}
delete(c.ProviderCache, providerAddrKey)
}
if diags.HasErrors() {
return diags.Err()
}
return nil
}
func (c *BuiltinEvalContext) ConfigureProvider(ctx context.Context, addr addrs.AbsProviderConfig, providerKey addrs.InstanceKey, cfg cty.Value) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
if !c.Path().IsForModule(addr.Module) {
// This indicates incorrect use of ConfigureProvider: it should be used
// only from the module that the provider configuration belongs to.
panic(fmt.Sprintf("%s configured by wrong module %s", addr, c.Path()))
}
p := c.Provider(ctx, addr, providerKey)
if p == nil {
diags = diags.Append(fmt.Errorf("%s not initialized", addr.InstanceString(providerKey)))
return diags
}
req := providers.ConfigureProviderRequest{
TerraformVersion: version.String(),
Config: cfg,
}
resp := p.ConfigureProvider(ctx, req)
return resp.Diagnostics
func (c *BuiltinEvalContext) Provisioners() plugins.ProvisionerManager {
return c.Plugins
}
func (c *BuiltinEvalContext) ProviderInput(_ context.Context, pc addrs.AbsProviderConfig) map[string]cty.Value {
@@ -251,43 +162,6 @@ func (c *BuiltinEvalContext) SetProviderInput(_ context.Context, pc addrs.AbsPro
c.ProviderLock.Unlock()
}
func (c *BuiltinEvalContext) Provisioner(n string) (provisioners.Interface, error) {
c.ProvisionerLock.Lock()
defer c.ProvisionerLock.Unlock()
p, ok := c.ProvisionerCache[n]
if !ok {
var err error
p, err = c.Plugins.NewProvisionerInstance(n)
if err != nil {
return nil, err
}
c.ProvisionerCache[n] = p
}
return p, nil
}
func (c *BuiltinEvalContext) ProvisionerSchema(n string) (*configschema.Block, error) {
return c.Plugins.ProvisionerSchema(n)
}
func (c *BuiltinEvalContext) CloseProvisioners() error {
var diags tfdiags.Diagnostics
c.ProvisionerLock.Lock()
defer c.ProvisionerLock.Unlock()
for name, prov := range c.ProvisionerCache {
err := prov.Close()
if err != nil {
diags = diags.Append(fmt.Errorf("provisioner.Close %s: %w", name, err))
}
}
return diags.Err()
}
func (c *BuiltinEvalContext) EvaluateBlock(ctx context.Context, body hcl.Body, schema *configschema.Block, self addrs.Referenceable, keyData InstanceKeyEvalData) (cty.Value, hcl.Body, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
scope := c.EvaluationScope(self, nil, keyData)
@@ -381,15 +255,12 @@ func (c *BuiltinEvalContext) EvaluateReplaceTriggeredBy(ctx context.Context, exp
// Since we have a traversal after the resource reference, we will need to
// decode the changes, which means we need a schema.
providerAddr := change.ProviderAddr
schema, err := c.ProviderSchema(ctx, providerAddr)
if err != nil {
diags = diags.Append(err)
return nil, false, diags
}
resAddr := change.Addr.ContainingResource().Resource
resSchema, _ := schema.SchemaForResourceType(resAddr.Mode, resAddr.Type)
ty := resSchema.ImpliedType()
resSchema, resDiags := c.Plugins.ResourceTypeSchema(ctx, providerAddr.Provider, resAddr.Mode, resAddr.Type)
if resDiags.HasErrors() {
return nil, false, diags.Append(resDiags)
}
ty := resSchema.Block.ImpliedType()
before, err := change.ChangeSrc.Before.Decode(ty)
if err != nil {
@@ -467,19 +338,7 @@ func (c *BuiltinEvalContext) EvaluationScope(self addrs.Referenceable, source ad
}
}
provider := c.Provider(ctx, providedBy.Provider, providerKey)
if provider == nil {
// This should not be possible if references are tracked correctly
return nil, tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Uninitialized function provider",
Detail: fmt.Sprintf("Provider %q has not yet been initialized", providedBy.Provider.String()),
Subject: rng.ToHCL().Ptr(),
})
}
return evalContextProviderFunction(ctx, provider, c.Evaluator.Operation, pf, rng)
return evalContextProviderFunction(ctx, c.Providers(), providedBy.Provider.InstanceCorrect(providerKey), c.Evaluator.Operation, pf, rng)
})
scope.SetActiveExperiments(mc.Module.ActiveExperiments)

View File

@@ -20,6 +20,7 @@ import (
"github.com/opentofu/opentofu/internal/instances"
"github.com/opentofu/opentofu/internal/lang"
"github.com/opentofu/opentofu/internal/plans"
"github.com/opentofu/opentofu/internal/plugins"
"github.com/opentofu/opentofu/internal/providers"
"github.com/opentofu/opentofu/internal/provisioners"
"github.com/opentofu/opentofu/internal/refactoring"
@@ -186,39 +187,12 @@ func (c *MockEvalContext) Input() UIInput {
return c.InputInput
}
func (c *MockEvalContext) InitProvider(_ context.Context, addr addrs.AbsProviderConfig, _ addrs.InstanceKey) (providers.Interface, error) {
c.InitProviderCalled = true
c.InitProviderType = addr.String()
c.InitProviderAddr = addr
return c.InitProviderProvider, c.InitProviderError
func (m *MockEvalContext) Providers() plugins.ProviderManager {
panic("not implemented") // TODO: Implement
}
func (c *MockEvalContext) Provider(_ context.Context, addr addrs.AbsProviderConfig, _ addrs.InstanceKey) providers.Interface {
c.ProviderCalled = true
c.ProviderAddr = addr
return c.ProviderProvider
}
func (c *MockEvalContext) ProviderSchema(_ context.Context, addr addrs.AbsProviderConfig) (providers.ProviderSchema, error) {
c.ProviderSchemaCalled = true
c.ProviderSchemaAddr = addr
return c.ProviderSchemaSchema, c.ProviderSchemaError
}
func (c *MockEvalContext) CloseProvider(_ context.Context, addr addrs.AbsProviderConfig) error {
c.CloseProviderCalled = true
c.CloseProviderAddr = addr
return nil
}
func (c *MockEvalContext) ConfigureProvider(_ context.Context, addr addrs.AbsProviderConfig, _ addrs.InstanceKey, cfg cty.Value) tfdiags.Diagnostics {
c.ConfigureProviderCalled = true
c.ConfigureProviderAddr = addr
c.ConfigureProviderConfig = cfg
if c.ConfigureProviderFn != nil {
return c.ConfigureProviderFn(addr, cfg)
}
return c.ConfigureProviderDiags
func (m *MockEvalContext) Provisioners() plugins.ProvisionerManager {
panic("not implemented") // TODO: Implement
}
func (c *MockEvalContext) ProviderInput(_ context.Context, addr addrs.AbsProviderConfig) map[string]cty.Value {
@@ -233,18 +207,6 @@ func (c *MockEvalContext) SetProviderInput(_ context.Context, addr addrs.AbsProv
c.SetProviderInputValues = vals
}
func (c *MockEvalContext) Provisioner(n string) (provisioners.Interface, error) {
c.ProvisionerCalled = true
c.ProvisionerName = n
return c.ProvisionerProvisioner, nil
}
func (c *MockEvalContext) ProvisionerSchema(n string) (*configschema.Block, error) {
c.ProvisionerSchemaCalled = true
c.ProvisionerSchemaName = n
return c.ProvisionerSchemaSchema, c.ProvisionerSchemaError
}
func (c *MockEvalContext) CloseProvisioners() error {
c.CloseProvisionersCalled = true
return nil

View File

@@ -144,15 +144,15 @@ func getProvider(ctx context.Context, evalCtx EvalContext, addr addrs.AbsProvide
// Should never happen
panic("GetProvider used with uninitialized provider configuration address")
}
provider := evalCtx.Provider(ctx, addr, providerKey)
provider := evalCtx.Providers().ConfiguredProvider(addr.InstanceCorrect(providerKey))
if provider == nil {
return nil, providers.ProviderSchema{}, fmt.Errorf("provider %s not initialized", addr.InstanceString(providerKey))
}
// Not all callers require a schema, so we will leave checking for a nil
// schema to the callers.
schema, err := evalCtx.ProviderSchema(ctx, addr)
if err != nil {
return nil, providers.ProviderSchema{}, fmt.Errorf("failed to read schema for provider %s: %w", addr, err)
schema := evalCtx.Providers().GetProviderSchema(ctx, addr.Provider)
if schema.Diagnostics.HasErrors() {
return nil, providers.ProviderSchema{}, schema.Diagnostics.Err()
}
return provider, schema, nil
}

View File

@@ -25,6 +25,7 @@ import (
"github.com/opentofu/opentofu/internal/lang"
"github.com/opentofu/opentofu/internal/lang/marks"
"github.com/opentofu/opentofu/internal/plans"
"github.com/opentofu/opentofu/internal/plugins"
"github.com/opentofu/opentofu/internal/states"
"github.com/opentofu/opentofu/internal/tfdiags"
)
@@ -58,7 +59,7 @@ type Evaluator struct {
//
// From this we only access the schemas of the plugins, and don't otherwise
// interact with plugin instances.
Plugins *contextPlugins
Plugins plugins.PluginManager
// State is the current state, embedded in a wrapper that ensures that
// it can be safely accessed and modified concurrently.
@@ -1015,14 +1016,14 @@ func (d *evaluationStateData) GetResource(ctx context.Context, addr addrs.Resour
func (d *evaluationStateData) getResourceSchema(ctx context.Context, addr addrs.Resource, providerAddr addrs.Provider) *configschema.Block {
// TODO: Plumb a useful context.Context through to here.
schema, _, err := d.Evaluator.Plugins.ResourceTypeSchema(ctx, providerAddr, addr.Mode, addr.Type)
if err != nil {
schema, diags := d.Evaluator.Plugins.ResourceTypeSchema(ctx, providerAddr, addr.Mode, addr.Type)
if diags.HasErrors() {
// We have plenty of other codepaths that will detect and report
// schema lookup errors before we'd reach this point, so we'll just
// treat a failure here the same as having no schema.
return nil
}
return schema
return schema.Block
}
func (d *evaluationStateData) GetTerraformAttr(_ context.Context, addr addrs.TerraformAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {

View File

@@ -234,15 +234,16 @@ func (d *evaluationStateData) staticValidateResourceReference(ctx context.Contex
// TODO: Plugin a suitable context.Context through to here.
providerFqn := modCfg.Module.ProviderForLocalConfig(cfg.ProviderConfigAddr())
schema, _, err := d.Evaluator.Plugins.ResourceTypeSchema(ctx, providerFqn, addr.Mode, addr.Type)
if err != nil {
schema, schemaDiags := d.Evaluator.Plugins.ResourceTypeSchema(ctx, providerFqn, addr.Mode, addr.Type)
diags = diags.Append(schemaDiags)
if schemaDiags.HasErrors() {
// Prior validation should've taken care of a schema lookup error,
// so we should never get here but we'll handle it here anyway for
// robustness.
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: `Failed provider schema lookup`,
Detail: fmt.Sprintf(`Couldn't load schema for %s resource type %q in %s: %s.`, modeAdjective, addr.Type, providerFqn.String(), err),
Detail: fmt.Sprintf(`Couldn't load schema for %s resource type %q in %s: %s.`, modeAdjective, addr.Type, providerFqn.String(), schemaDiags),
Subject: rng.ToHCL().Ptr(),
})
}
@@ -278,7 +279,7 @@ func (d *evaluationStateData) staticValidateResourceReference(ctx context.Contex
// If we got this far then we'll try to validate the remaining traversal
// steps against our schema.
moreDiags := schema.StaticValidateTraversal(remain)
moreDiags := schema.Block.StaticValidateTraversal(remain)
diags = diags.Append(moreDiags)
return diags

View File

@@ -12,6 +12,7 @@ import (
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/dag"
"github.com/opentofu/opentofu/internal/plans"
"github.com/opentofu/opentofu/internal/plugins"
"github.com/opentofu/opentofu/internal/states"
"github.com/opentofu/opentofu/internal/tfdiags"
)
@@ -40,7 +41,7 @@ type ApplyGraphBuilder struct {
// Plugins is a library of the plug-in components (providers and
// provisioners) available for use.
Plugins *contextPlugins
Plugins plugins.PluginManager
// Targets are resources to target. This is only required to make sure
// unnecessary outputs aren't included in the apply graph. The plan

View File

@@ -11,6 +11,7 @@ import (
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/dag"
"github.com/opentofu/opentofu/internal/plugins"
"github.com/opentofu/opentofu/internal/states"
"github.com/opentofu/opentofu/internal/tfdiags"
)
@@ -44,7 +45,7 @@ type EvalGraphBuilder struct {
// Plugins is a library of plug-in components (providers and
// provisioners) available for use.
Plugins *contextPlugins
Plugins plugins.PluginManager
ProviderFunctionTracker ProviderFunctionMapping
}

View File

@@ -12,6 +12,7 @@ import (
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/dag"
"github.com/opentofu/opentofu/internal/plugins"
"github.com/opentofu/opentofu/internal/refactoring"
"github.com/opentofu/opentofu/internal/states"
"github.com/opentofu/opentofu/internal/tfdiags"
@@ -43,7 +44,7 @@ type PlanGraphBuilder struct {
// Plugins is a library of plug-in components (providers and
// provisioners) available for use.
Plugins *contextPlugins
Plugins plugins.PluginManager
// Targets are resources to target
Targets []addrs.Targetable

View File

@@ -110,11 +110,8 @@ func (w *ContextGraphWalker) EvalContext() EvalContext {
Plugins: w.Context.plugins,
MoveResultsValue: w.MoveResults,
ImportResolverValue: w.ImportResolver,
ProviderCache: w.providerCache,
ProviderInputConfig: w.Context.providerInputConfig,
ProviderLock: &w.providerLock,
ProvisionerCache: w.provisionerCache,
ProvisionerLock: &w.provisionerLock,
ChangesValue: w.Changes,
ChecksValue: w.Checks,
StateValue: w.State,

View File

@@ -222,10 +222,6 @@ func (n *nodeCloseModule) Execute(_ context.Context, evalCtx EvalContext, op wal
return
}
// If this is the root module, we are cleaning up the walk, so close
// any running provisioners
diags = diags.Append(evalCtx.CloseProvisioners())
switch op {
case walkApply, walkDestroy:
state := evalCtx.State().Lock()

View File

@@ -11,11 +11,8 @@ import (
"log"
"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/configs/configschema"
"github.com/opentofu/opentofu/internal/providers"
"github.com/opentofu/opentofu/internal/tfdiags"
"github.com/opentofu/opentofu/internal/tracing"
"github.com/opentofu/opentofu/internal/tracing/traceattrs"
@@ -57,73 +54,44 @@ var (
// GraphNodeExecutable
func (n *NodeApplyableProvider) Execute(ctx context.Context, evalCtx EvalContext, op walkOperation) tfdiags.Diagnostics {
instances, diags := n.initInstances(ctx, evalCtx, op)
for key, provider := range instances {
diags = diags.Append(n.executeInstance(ctx, evalCtx, op, key, provider))
}
return diags
}
func (n *NodeApplyableProvider) initInstances(ctx context.Context, evalCtx EvalContext, op walkOperation) (map[addrs.InstanceKey]providers.Interface, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
var initKeys []addrs.InstanceKey
// config -> init (different due to validate skipping most for_each logic)
instanceKeys := make(map[addrs.InstanceKey]addrs.InstanceKey)
if n.Config == nil || n.Config.Instances == nil {
initKeys = append(initKeys, addrs.NoKey)
instanceKeys[addrs.NoKey] = addrs.NoKey
} else if op == walkValidate {
// Instances are set AND we are validating
initKeys = append(initKeys, addrs.NoKey)
for key := range n.Config.Instances {
instanceKeys[key] = addrs.NoKey
}
} else {
// Instances are set AND we are not validating
for key := range n.Config.Instances {
initKeys = append(initKeys, key)
instanceKeys[key] = key
}
}
for _, key := range initKeys {
_, err := evalCtx.InitProvider(ctx, n.Addr, key)
diags = diags.Append(err)
}
if diags.HasErrors() {
return nil, diags
}
instances := make(map[addrs.InstanceKey]providers.Interface)
for configKey, initKey := range instanceKeys {
provider, _, err := getProvider(ctx, evalCtx, n.Addr, initKey)
diags = diags.Append(err)
instances[configKey] = provider
}
if diags.HasErrors() {
return nil, diags
}
return instances, diags
}
func (n *NodeApplyableProvider) executeInstance(ctx context.Context, evalCtx EvalContext, op walkOperation, providerKey addrs.InstanceKey, provider providers.Interface) tfdiags.Diagnostics {
switch op {
case walkValidate:
log.Printf("[TRACE] NodeApplyableProvider: validating configuration for %s", n.Addr)
return n.ValidateProvider(ctx, evalCtx, providerKey, provider)
return n.validateProviderInstances(ctx, evalCtx)
case walkPlan, walkPlanDestroy, walkApply, walkDestroy:
log.Printf("[TRACE] NodeApplyableProvider: configuring %s", n.Addr)
return n.ConfigureProvider(ctx, evalCtx, providerKey, provider, false)
return n.configureProviderInstances(ctx, evalCtx, false)
case walkImport:
log.Printf("[TRACE] NodeApplyableProvider: configuring %s (requiring that configuration is wholly known)", n.Addr)
return n.ConfigureProvider(ctx, evalCtx, providerKey, provider, true)
return n.configureProviderInstances(ctx, evalCtx, true)
}
return nil
}
func (n *NodeApplyableProvider) ValidateProvider(ctx context.Context, evalCtx EvalContext, providerKey addrs.InstanceKey, provider providers.Interface) tfdiags.Diagnostics {
func (n *NodeApplyableProvider) validateProviderInstances(ctx context.Context, evalCtx EvalContext) tfdiags.Diagnostics {
if n.Config == nil || n.Config.Instances == nil {
return n.validateProviderInstance(ctx, evalCtx, addrs.NoKey)
}
var diags tfdiags.Diagnostics
for key := range n.Config.Instances {
diags = diags.Append(n.validateProviderInstance(ctx, evalCtx, key))
}
return diags
}
func (n *NodeApplyableProvider) configureProviderInstances(ctx context.Context, evalCtx EvalContext, verifyConfigIsKnown bool) tfdiags.Diagnostics {
if n.Config == nil || n.Config.Instances == nil {
return n.configureProviderInstance(ctx, evalCtx, addrs.NoKey, verifyConfigIsKnown)
}
var diags tfdiags.Diagnostics
for key := range n.Config.Instances {
diags = diags.Append(n.configureProviderInstance(ctx, evalCtx, key, verifyConfigIsKnown))
}
return diags
}
func (n *NodeApplyableProvider) validateProviderInstance(ctx context.Context, evalCtx EvalContext, providerKey addrs.InstanceKey) tfdiags.Diagnostics {
_, span := tracing.Tracer().Start(
ctx, "Validate provider configuration",
tracing.SpanAttributes(
@@ -149,7 +117,7 @@ func (n *NodeApplyableProvider) ValidateProvider(ctx context.Context, evalCtx Ev
return nil
}
schemaResp := provider.GetProviderSchema(ctx)
schemaResp := evalCtx.Providers().GetProviderSchema(ctx, n.Addr.Provider)
diags := schemaResp.Diagnostics.InConfigBody(configBody, n.Addr.InstanceString(providerKey))
if diags.HasErrors() {
tracing.SetSpanError(span, diags)
@@ -180,21 +148,17 @@ func (n *NodeApplyableProvider) ValidateProvider(ctx context.Context, evalCtx Ev
// stripped out before sending this to the provider
unmarkedConfigVal, _ := configVal.UnmarkDeep()
req := providers.ValidateProviderConfigRequest{
Config: unmarkedConfigVal,
}
validateResp := provider.ValidateProviderConfig(ctx, req)
diags = diags.Append(validateResp.Diagnostics.InConfigBody(configBody, n.Addr.InstanceString(providerKey)))
validateResp := evalCtx.Providers().ValidateProviderConfig(ctx, n.Addr.Provider, unmarkedConfigVal)
diags = diags.Append(validateResp.InConfigBody(configBody, n.Addr.InstanceString(providerKey)))
tracing.SetSpanError(span, diags)
return diags
}
// ConfigureProvider configures a provider that is already initialized and retrieved.
// configureProviderInstance configures a provider that is already initialized and retrieved.
// If verifyConfigIsKnown is true, ConfigureProvider will return an error if the
// provider configVal is not wholly known and is meant only for use during import.
func (n *NodeApplyableProvider) ConfigureProvider(ctx context.Context, evalCtx EvalContext, providerKey addrs.InstanceKey, provider providers.Interface, verifyConfigIsKnown bool) tfdiags.Diagnostics {
func (n *NodeApplyableProvider) configureProviderInstance(ctx context.Context, evalCtx EvalContext, providerKey addrs.InstanceKey, verifyConfigIsKnown bool) tfdiags.Diagnostics {
_, span := tracing.Tracer().Start(
ctx, "Configure provider",
tracing.SpanAttributes(
@@ -214,7 +178,7 @@ func (n *NodeApplyableProvider) ConfigureProvider(ctx context.Context, evalCtx E
configBody := buildProviderConfig(ctx, evalCtx, n.Addr, config)
resp := provider.GetProviderSchema(ctx)
resp := evalCtx.Providers().GetProviderSchema(ctx, n.Addr.Provider)
diags := resp.Diagnostics.InConfigBody(configBody, n.Addr.InstanceString(providerKey))
if diags.HasErrors() {
tracing.SetSpanError(span, diags)
@@ -249,40 +213,7 @@ func (n *NodeApplyableProvider) ConfigureProvider(ctx context.Context, evalCtx E
// stripped out before sending this to the provider
unmarkedConfigVal, _ := configVal.UnmarkDeep()
// Allow the provider to validate and insert any defaults into the full
// configuration.
req := providers.ValidateProviderConfigRequest{
Config: unmarkedConfigVal,
}
// ValidateProviderConfig is only used for validation. We are intentionally
// ignoring the PreparedConfig field to maintain existing behavior.
validateResp := provider.ValidateProviderConfig(ctx, req)
diags = diags.Append(validateResp.Diagnostics.InConfigBody(configBody, n.Addr.InstanceString(providerKey)))
if diags.HasErrors() && config == nil {
// If there isn't an explicit "provider" block in the configuration,
// this error message won't be very clear. Add some detail to the error
// message in this case.
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid provider configuration",
fmt.Sprintf(providerConfigErr, n.Addr.Provider),
))
}
if diags.HasErrors() {
tracing.SetSpanError(span, diags)
return diags
}
// If the provider returns something different, log a warning to help
// indicate to provider developers that the value is not used.
preparedCfg := validateResp.PreparedConfig
if preparedCfg != cty.NilVal && !preparedCfg.IsNull() && !preparedCfg.RawEquals(unmarkedConfigVal) {
log.Printf("[WARN] ValidateProviderConfig from %q changed the config value, but that value is unused", n.Addr)
}
configDiags := evalCtx.ConfigureProvider(ctx, n.Addr, providerKey, unmarkedConfigVal)
configDiags := evalCtx.Providers().ConfigureProvider(ctx, n.Addr.InstanceCorrect(providerKey), unmarkedConfigVal)
diags = diags.Append(configDiags.InConfigBody(configBody, n.Addr.InstanceString(providerKey)))
if diags.HasErrors() && config == nil {
// If there isn't an explicit "provider" block in the configuration,

View File

@@ -8,7 +8,6 @@ package tofu
import (
"context"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/tfdiags"
)
@@ -24,6 +23,6 @@ var _ GraphNodeExecutable = (*NodeEvalableProvider)(nil)
// GraphNodeExecutable
func (n *NodeEvalableProvider) Execute(ctx context.Context, evalCtx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) {
_, err := evalCtx.InitProvider(ctx, n.Addr, addrs.NoKey)
return diags.Append(err)
// TODO remove me
return nil
}

View File

@@ -616,7 +616,7 @@ func (n *NodeAbstractResourceInstance) readResourceInstanceState(ctx context.Con
}
if isResourceMovedToDifferentType(addr, prevAddr) {
src, diags = moveResourceState(transformArgs)
} else {
} else if !n.ResolvedProvider.IsMocked {
src, diags = upgradeResourceState(transformArgs)
}
@@ -675,7 +675,7 @@ func (n *NodeAbstractResourceInstance) readResourceInstanceStateDeposed(ctx cont
}
if isResourceMovedToDifferentType(addr, prevAddr) {
src, diags = moveResourceState(transformArgs)
} else {
} else if !n.ResolvedProvider.IsMocked {
src, diags = upgradeResourceState(transformArgs)
}

View File

@@ -30,7 +30,6 @@ import (
"github.com/opentofu/opentofu/internal/plans"
"github.com/opentofu/opentofu/internal/plans/objchange"
"github.com/opentofu/opentofu/internal/providers"
"github.com/opentofu/opentofu/internal/provisioners"
"github.com/opentofu/opentofu/internal/states"
"github.com/opentofu/opentofu/internal/tfdiags"
)
@@ -282,8 +281,12 @@ func (n *NodeAbstractResourceInstance) resolveProvider(ctx context.Context, eval
panic("EnsureProvider used with uninitialized provider configuration address")
}
provider := evalCtx.Provider(ctx, n.ResolvedProvider.ProviderConfig, n.ResolvedProviderKey)
if provider != nil {
if n.ResolvedProvider.IsMocked {
// All good
return nil
}
if evalCtx.Providers().IsProviderConfigured(n.ResolvedProvider.ProviderConfig.InstanceCorrect(n.ResolvedProviderKey)) {
// All good
return nil
}
@@ -2684,13 +2687,7 @@ func (n *NodeAbstractResourceInstance) applyProvisioners(ctx context.Context, ev
for _, prov := range provs {
log.Printf("[TRACE] applyProvisioners: provisioning %s with %q", n.Addr, prov.Type)
// Get the provisioner
provisioner, err := evalCtx.Provisioner(prov.Type)
if err != nil {
return diags.Append(err)
}
schema, err := evalCtx.ProvisionerSchema(prov.Type)
schema, err := evalCtx.Provisioners().ProvisionerSchema(prov.Type)
if err != nil {
// This error probably won't be a great diagnostic, but in practice
// we typically catch this problem long before we get here, so
@@ -2790,12 +2787,13 @@ func (n *NodeAbstractResourceInstance) applyProvisioners(ctx context.Context, ev
}
output := CallbackUIOutput{OutputFn: outputFn}
resp := provisioner.ProvisionResource(provisioners.ProvisionResourceRequest{
Config: unmarkedConfig,
Connection: unmarkedConnInfo,
UIOutput: &output,
})
applyDiags := resp.Diagnostics.InConfigBody(prov.Config, n.Addr.String())
applyDiags := evalCtx.Provisioners().ProvisionResource(
ctx,
prov.Type,
unmarkedConfig,
unmarkedConnInfo,
&output,
).InConfigBody(prov.Config, n.Addr.String())
// Call post hook
hookErr := evalCtx.Hook(func(h Hook) (HookAction, error) {
@@ -3215,10 +3213,7 @@ func resourceInstancePrevRunAddr(evalCtx EvalContext, currentAddr addrs.AbsResou
}
func (n *NodeAbstractResourceInstance) getProvider(ctx context.Context, evalCtx EvalContext) (providers.Interface, providers.ProviderSchema, error) {
underlyingProvider, schema, err := getProvider(ctx, evalCtx, n.ResolvedProvider.ProviderConfig, n.ResolvedProviderKey)
if err != nil {
return nil, providers.ProviderSchema{}, err
}
schema := evalCtx.Providers().GetProviderSchema(ctx, n.ResolvedProvider.ProviderConfig.Provider)
var isOverridden bool
var overrideValues map[string]cty.Value
@@ -3250,9 +3245,13 @@ func (n *NodeAbstractResourceInstance) getProvider(ctx context.Context, evalCtx
}
if isOverridden {
provider, err := newProviderForTestWithSchema(underlyingProvider, schema, overrideValues)
provider, err := newProviderForTestWithSchema(n.ResolvedProvider.ProviderConfig.Provider, evalCtx.Providers(), schema, overrideValues)
return provider, schema, err
}
underlyingProvider, _, err := getProvider(ctx, evalCtx, n.ResolvedProvider.ProviderConfig, n.ResolvedProviderKey)
if err != nil {
return nil, providers.ProviderSchema{}, err
}
return underlyingProvider, schema, err
}

View File

@@ -22,7 +22,6 @@ import (
"github.com/opentofu/opentofu/internal/instances"
"github.com/opentofu/opentofu/internal/lang"
"github.com/opentofu/opentofu/internal/providers"
"github.com/opentofu/opentofu/internal/provisioners"
"github.com/opentofu/opentofu/internal/tfdiags"
"github.com/opentofu/opentofu/internal/tracing"
"github.com/opentofu/opentofu/internal/tracing/traceattrs"
@@ -130,16 +129,7 @@ func (n *NodeValidatableResource) Execute(ctx context.Context, evalCtx EvalConte
func (n *NodeValidatableResource) validateProvisioner(ctx context.Context, evalCtx EvalContext, p *configs.Provisioner) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
provisioner, err := evalCtx.Provisioner(p.Type)
if err != nil {
diags = diags.Append(err)
return diags
}
if provisioner == nil {
return diags.Append(fmt.Errorf("provisioner %s not initialized", p.Type))
}
provisionerSchema, err := evalCtx.ProvisionerSchema(p.Type)
provisionerSchema, err := evalCtx.Provisioners().ProvisionerSchema(p.Type)
if err != nil {
return diags.Append(fmt.Errorf("failed to read schema for provisioner %s: %w", p.Type, err))
}
@@ -158,12 +148,8 @@ func (n *NodeValidatableResource) validateProvisioner(ctx context.Context, evalC
// Use unmarked value for validate request
unmarkedConfigVal, _ := configVal.UnmarkDeep()
req := provisioners.ValidateProvisionerConfigRequest{
Config: unmarkedConfigVal,
}
resp := provisioner.ValidateProvisionerConfig(req)
diags = diags.Append(resp.Diagnostics)
valDiags := evalCtx.Provisioners().ValidateProvisionerConfig(ctx, p.Type, unmarkedConfigVal)
diags = diags.Append(valDiags)
if p.Connection != nil {
// We can't comprehensively validate the connection config since its
@@ -187,12 +173,6 @@ func (n *NodeValidatableResource) evaluateBlock(ctx context.Context, evalCtx Eva
func (n *NodeValidatableResource) validateResource(ctx context.Context, evalCtx EvalContext) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
provider, providerSchema, err := getProvider(ctx, evalCtx, n.ResolvedProvider.ProviderConfig, addrs.NoKey) // Provider Instance Keys are ignored during validate
diags = diags.Append(err)
if diags.HasErrors() {
return diags
}
keyData := EvalDataForNoInstanceKey
switch {
@@ -256,130 +236,68 @@ func (n *NodeValidatableResource) validateResource(ctx context.Context, evalCtx
// because the ProviderAddr for the resource isn't available on the EvalValidate
// struct.
// Provider entry point varies depending on resource mode, because
// managed resources and data resources are two distinct concepts
// in the provider abstraction.
switch n.Config.Mode {
case addrs.ManagedResourceMode:
schema, _ := providerSchema.SchemaForResourceType(n.Config.Mode, n.Config.Type)
if schema == nil {
suggestion := n.noResourceSchemaSuggestion(providerSchema)
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid resource type",
Detail: fmt.Sprintf("The provider %s does not support resource type %q.%s", n.Provider().ForDisplay(), n.Config.Type, suggestion),
Subject: &n.Config.TypeRange,
})
return diags
}
providerSchema := evalCtx.Providers().GetProviderSchema(ctx, n.ResolvedProvider.ProviderConfig.Provider)
diags = diags.Append(providerSchema.Diagnostics)
if diags.HasErrors() {
return diags
}
configVal, _, valDiags := evalCtx.EvaluateBlock(ctx, n.Config.Config, schema, nil, keyData)
diags = diags.Append(valDiags.InConfigBody(n.Config.Config, n.Addr.String()))
if valDiags.HasErrors() {
return diags
}
schema, _ := providerSchema.SchemaForResourceType(n.Config.Mode, n.Config.Type)
if schema == nil {
suggestion := n.noResourceSchemaSuggestion(providerSchema)
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Invalid %s type", n.Config.Mode.HumanString()),
Detail: fmt.Sprintf("The provider %s does not support %s type %q.%s", n.Provider().ForDisplay(), n.Config.Mode.HumanString(), n.Config.Type, suggestion),
Subject: &n.Config.TypeRange,
})
return diags
}
if n.Config.Managed != nil { // can be nil only in tests with poorly-configured mocks
for _, traversal := range n.Config.Managed.IgnoreChanges {
// validate the ignore_changes traversals apply.
moreDiags := schema.StaticValidateTraversal(traversal)
diags = diags.Append(moreDiags)
configVal, _, valDiags := evalCtx.EvaluateBlock(ctx, n.Config.Config, schema, nil, keyData)
diags = diags.Append(valDiags.InConfigBody(n.Config.Config, n.Addr.String()))
if valDiags.HasErrors() {
return diags
}
// ignore_changes cannot be used for Computed attributes,
// unless they are also Optional.
// If the traversal was valid, convert it to a cty.Path and
// use that to check whether the Attribute is Computed and
// non-Optional.
if !diags.HasErrors() {
path := traversalToPath(traversal)
if n.Config.Managed != nil { // can be nil only in tests with poorly-configured mocks
for _, traversal := range n.Config.Managed.IgnoreChanges {
// validate the ignore_changes traversals apply.
moreDiags := schema.StaticValidateTraversal(traversal)
diags = diags.Append(moreDiags)
attrSchema := schema.AttributeByPath(path)
// ignore_changes cannot be used for Computed attributes,
// unless they are also Optional.
// If the traversal was valid, convert it to a cty.Path and
// use that to check whether the Attribute is Computed and
// non-Optional.
if !diags.HasErrors() {
path := traversalToPath(traversal)
if attrSchema != nil && !attrSchema.Optional && attrSchema.Computed {
// ignore_changes uses absolute traversal syntax in config despite
// using relative traversals, so we strip the leading "." added by
// FormatCtyPath for a better error message.
attrDisplayPath := strings.TrimPrefix(tfdiags.FormatCtyPath(path), ".")
attrSchema := schema.AttributeByPath(path)
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Redundant ignore_changes element",
Detail: fmt.Sprintf("Adding an attribute name to ignore_changes tells OpenTofu to ignore future changes to the argument in configuration after the object has been created, retaining the value originally configured.\n\nThe attribute %s is decided by the provider alone and therefore there can be no configured value to compare with. Including this attribute in ignore_changes has no effect. Remove the attribute from ignore_changes to quiet this warning.", attrDisplayPath),
Subject: &n.Config.TypeRange,
})
}
if attrSchema != nil && !attrSchema.Optional && attrSchema.Computed {
// ignore_changes uses absolute traversal syntax in config despite
// using relative traversals, so we strip the leading "." added by
// FormatCtyPath for a better error message.
attrDisplayPath := strings.TrimPrefix(tfdiags.FormatCtyPath(path), ".")
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Redundant ignore_changes element",
Detail: fmt.Sprintf("Adding an attribute name to ignore_changes tells OpenTofu to ignore future changes to the argument in configuration after the object has been created, retaining the value originally configured.\n\nThe attribute %s is decided by the provider alone and therefore there can be no configured value to compare with. Including this attribute in ignore_changes has no effect. Remove the attribute from ignore_changes to quiet this warning.", attrDisplayPath),
Subject: &n.Config.TypeRange,
})
}
}
}
// Use unmarked value for validate request
unmarkedConfigVal, _ := configVal.UnmarkDeep()
req := providers.ValidateResourceConfigRequest{
TypeName: n.Config.Type,
Config: unmarkedConfigVal,
}
resp := provider.ValidateResourceConfig(ctx, req)
diags = diags.Append(resp.Diagnostics.InConfigBody(n.Config.Config, n.Addr.String()))
case addrs.DataResourceMode:
schema, _ := providerSchema.SchemaForResourceType(n.Config.Mode, n.Config.Type)
if schema == nil {
suggestion := n.noResourceSchemaSuggestion(providerSchema)
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid data source",
Detail: fmt.Sprintf("The provider %s does not support data source %q.%s", n.Provider().ForDisplay(), n.Config.Type, suggestion),
Subject: &n.Config.TypeRange,
})
return diags
}
configVal, _, valDiags := evalCtx.EvaluateBlock(ctx, n.Config.Config, schema, nil, keyData)
diags = diags.Append(valDiags.InConfigBody(n.Config.Config, n.Addr.String()))
if valDiags.HasErrors() {
return diags
}
// Use unmarked value for validate request
unmarkedConfigVal, _ := configVal.UnmarkDeep()
req := providers.ValidateDataResourceConfigRequest{
TypeName: n.Config.Type,
Config: unmarkedConfigVal,
}
resp := provider.ValidateDataResourceConfig(ctx, req)
diags = diags.Append(resp.Diagnostics.InConfigBody(n.Config.Config, n.Addr.String()))
case addrs.EphemeralResourceMode:
schema, _ := providerSchema.SchemaForResourceType(n.Config.Mode, n.Config.Type)
if schema == nil {
suggestion := n.noResourceSchemaSuggestion(providerSchema)
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid ephemeral resource",
Detail: fmt.Sprintf("The provider %s does not support ephemeral resource %q.%s", n.Provider().ForDisplay(), n.Config.Type, suggestion),
Subject: &n.Config.TypeRange,
})
return diags
}
configVal, _, valDiags := evalCtx.EvaluateBlock(ctx, n.Config.Config, schema, nil, keyData)
diags = diags.Append(valDiags)
if valDiags.HasErrors() {
return diags
}
// Use unmarked value for validate request
unmarkedConfigVal, _ := configVal.UnmarkDeep()
req := providers.ValidateEphemeralConfigRequest{
TypeName: n.Config.Type,
Config: unmarkedConfigVal,
}
resp := provider.ValidateEphemeralConfig(ctx, req)
diags = diags.Append(resp.Diagnostics.InConfigBody(n.Config.Config, n.Addr.String()))
}
// Use unmarked value for validate request
unmarkedConfigVal, _ := configVal.UnmarkDeep()
resp := evalCtx.Providers().ValidateResourceConfig(ctx, n.ResolvedProvider.ProviderConfig.Provider, n.Config.Mode, n.Config.Type, unmarkedConfigVal)
diags = diags.Append(resp.InConfigBody(n.Config.Config, n.Addr.String()))
return diags
}

View File

@@ -12,6 +12,7 @@ import (
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/configs/hcl2shim"
"github.com/opentofu/opentofu/internal/plugins"
"github.com/opentofu/opentofu/internal/providers"
"github.com/opentofu/opentofu/internal/tfdiags"
"github.com/zclconf/go-cty/cty"
@@ -24,21 +25,22 @@ var _ providers.Interface = &providerForTest{}
// and overrides for that one specific resource instance, any other usage is a bug in OpenTofu and should
// be corrected.
type providerForTest struct {
// providers.Interface is not embedded to make it safer to extend
// the interface without silently breaking providerForTest functionality.
internal providers.Interface
schema providers.ProviderSchema
addr addrs.Provider
providers plugins.ProviderManager
schema providers.ProviderSchema
overrideValues map[string]cty.Value
}
func newProviderForTestWithSchema(internal providers.Interface, schema providers.ProviderSchema, overrideValues map[string]cty.Value) (providerForTest, error) {
func newProviderForTestWithSchema(addr addrs.Provider, providers plugins.ProviderManager, schema providers.ProviderSchema, overrideValues map[string]cty.Value) (providerForTest, error) {
if schema.Diagnostics.HasErrors() {
return providerForTest{}, fmt.Errorf("invalid provider schema for test wrapper: %w", schema.Diagnostics.Err())
}
return providerForTest{
internal: internal,
addr: addr,
providers: providers,
schema: schema,
overrideValues: overrideValues,
}, nil
@@ -120,10 +122,7 @@ func (p providerForTest) ValidateProviderConfig(_ context.Context, _ providers.V
// accessible so it is safe to wipe metadata as well. See Config.transformProviderConfigsForTest
// for more details.
func (p providerForTest) GetProviderSchema(ctx context.Context) providers.GetProviderSchemaResponse {
providerSchema := p.internal.GetProviderSchema(ctx)
providerSchema.Provider = providers.Schema{}
providerSchema.ProviderMeta = providers.Schema{}
return providerSchema
return p.schema
}
// providerForTest doesn't configure its internal provider because it is mocked.
@@ -144,35 +143,41 @@ func (p providerForTest) MoveResourceState(context.Context, providers.MoveResour
// if called via providerForTest because importing is not supported in testing framework.
func (p providerForTest) ValidateResourceConfig(ctx context.Context, r providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse {
return p.internal.ValidateResourceConfig(ctx, r)
return providers.ValidateResourceConfigResponse{
Diagnostics: p.providers.ValidateResourceConfig(ctx, p.addr, addrs.ManagedResourceMode, r.TypeName, r.Config),
}
}
func (p providerForTest) ValidateDataResourceConfig(ctx context.Context, r providers.ValidateDataResourceConfigRequest) providers.ValidateDataResourceConfigResponse {
return p.internal.ValidateDataResourceConfig(ctx, r)
return providers.ValidateDataResourceConfigResponse{
Diagnostics: p.providers.ValidateResourceConfig(ctx, p.addr, addrs.DataResourceMode, r.TypeName, r.Config),
}
}
func (p providerForTest) UpgradeResourceState(ctx context.Context, r providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse {
return p.internal.UpgradeResourceState(ctx, r)
panic("Upgrading is not supported in testing context. providerForTest must not be used to call UpgradeResourceState")
}
func (p providerForTest) ValidateEphemeralConfig(ctx context.Context, request providers.ValidateEphemeralConfigRequest) providers.ValidateEphemeralConfigResponse {
return p.internal.ValidateEphemeralConfig(ctx, request)
func (p providerForTest) ValidateEphemeralConfig(ctx context.Context, r providers.ValidateEphemeralConfigRequest) providers.ValidateEphemeralConfigResponse {
return providers.ValidateEphemeralConfigResponse{
Diagnostics: p.providers.ValidateResourceConfig(ctx, p.addr, addrs.EphemeralResourceMode, r.TypeName, r.Config),
}
}
func (p providerForTest) Stop(ctx context.Context) error {
return p.internal.Stop(ctx)
panic("Stopping is not supported in testing context. providerForTest must not be used to call Stop")
}
func (p providerForTest) GetFunctions(ctx context.Context) providers.GetFunctionsResponse {
return p.internal.GetFunctions(ctx)
panic("Functions are not supported in testing context. providerForTest must not be used to call GetFunctions")
}
func (p providerForTest) CallFunction(ctx context.Context, r providers.CallFunctionRequest) providers.CallFunctionResponse {
return p.internal.CallFunction(ctx, r)
panic("Functions are not supported in testing context. providerForTest must not be used to call CallFunction")
}
func (p providerForTest) Close(ctx context.Context) error {
return p.internal.Close(ctx)
panic("Closing is not supported in testing context. providerForTest must not be used to call Close")
}
func newMockValueComposer(typeName string) hcl2shim.MockValueComposer {

View File

@@ -14,6 +14,7 @@ import (
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/configs/configschema"
"github.com/opentofu/opentofu/internal/plugins"
"github.com/opentofu/opentofu/internal/providers"
"github.com/opentofu/opentofu/internal/states"
"github.com/opentofu/opentofu/internal/tfdiags"
@@ -71,7 +72,7 @@ func (ss *Schemas) ProvisionerConfig(name string) *configschema.Block {
// either misbehavior on the part of one of the providers or of the provider
// protocol itself. When returned with errors, the returned schemas object is
// still valid but may be incomplete.
func loadSchemas(ctx context.Context, config *configs.Config, state *states.State, plugins *contextPlugins) (*Schemas, error) {
func loadSchemas(ctx context.Context, config *configs.Config, state *states.State, plugins plugins.PluginSchemas) (*Schemas, error) {
var diags tfdiags.Diagnostics
provisioners, provisionerDiags := loadProvisionerSchemas(ctx, config, plugins)
@@ -86,7 +87,7 @@ func loadSchemas(ctx context.Context, config *configs.Config, state *states.Stat
}, diags.Err()
}
func loadProviderSchemas(ctx context.Context, config *configs.Config, state *states.State, plugins *contextPlugins) (map[addrs.Provider]providers.ProviderSchema, tfdiags.Diagnostics) {
func loadProviderSchemas(ctx context.Context, config *configs.Config, state *states.State, plugins plugins.ProviderSchemas) (map[addrs.Provider]providers.ProviderSchema, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
schemas := map[addrs.Provider]providers.ProviderSchema{}
@@ -109,14 +110,14 @@ func loadProviderSchemas(ctx context.Context, config *configs.Config, state *sta
for fqn := range schemas {
wg.Go(func() {
log.Printf("[TRACE] LoadSchemas: retrieving schema for provider type %q", fqn.String())
schema, err := plugins.ProviderSchema(ctx, fqn)
schema := plugins.GetProviderSchema(ctx, fqn)
// Ensure that we don't race on diags or schemas now that the hard work is done
lock.Lock()
defer lock.Unlock()
if err != nil {
diags = diags.Append(err)
if schema.Diagnostics != nil {
diags = diags.Append(schema.Diagnostics)
return
}
@@ -133,7 +134,7 @@ func loadProviderSchemas(ctx context.Context, config *configs.Config, state *sta
return schemas, diags
}
func loadProvisionerSchemas(ctx context.Context, config *configs.Config, plugins *contextPlugins) (map[string]*configschema.Block, tfdiags.Diagnostics) {
func loadProvisionerSchemas(ctx context.Context, config *configs.Config, plugins plugins.ProvisionerSchemas) (map[string]*configschema.Block, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
schemas := map[string]*configschema.Block{}

View File

@@ -8,7 +8,6 @@ package tofu
import (
"context"
"fmt"
"log"
"sync"
"github.com/hashicorp/hcl/v2"
@@ -22,7 +21,6 @@ import (
"github.com/opentofu/opentofu/internal/lang/marks"
"github.com/opentofu/opentofu/internal/moduletest"
"github.com/opentofu/opentofu/internal/plans"
"github.com/opentofu/opentofu/internal/providers"
"github.com/opentofu/opentofu/internal/states"
"github.com/opentofu/opentofu/internal/tfdiags"
)
@@ -100,49 +98,13 @@ func (tc *TestContext) evaluate(state *states.SyncState, changes *plans.ChangesS
PlanTimestamp: tc.Plan.Timestamp,
// InstanceExpander is intentionally nil for test contexts
// The GetModule function will fall back to using state/changes when it's nil
InstanceExpander: nil,
InstanceExpander: nil,
},
ModulePath: nil, // nil for the root module
InstanceKeyData: EvalDataForNoInstanceKey,
Operation: operation,
}
var providerInstanceLock sync.Mutex
providerInstances := make(map[addrs.Provider]providers.Interface)
defer func() {
for addr, inst := range providerInstances {
log.Printf("[INFO] Shutting down test provider %s", addr)
inst.Close(context.TODO())
}
}()
providerSupplier := func(addr addrs.Provider) providers.Interface {
providerInstanceLock.Lock()
defer providerInstanceLock.Unlock()
if inst, ok := providerInstances[addr]; ok {
return inst
}
factory, ok := tc.plugins.providerFactories[addr]
if !ok {
log.Printf("[WARN] Unable to find provider %s in test context", addr)
providerInstances[addr] = nil
return nil
}
log.Printf("[INFO] Starting test provider %s", addr)
inst, err := factory()
if err != nil {
log.Printf("[WARN] Unable to start provider %s in test context", addr)
providerInstances[addr] = nil
return nil
} else {
log.Printf("[INFO] Shutting down test provider %s", addr)
providerInstances[addr] = inst
return inst
}
}
scope := &lang.Scope{
Data: data,
BaseDir: ".",
@@ -160,10 +122,17 @@ func (tc *TestContext) evaluate(state *states.SyncState, changes *plans.ChangesS
Subject: rng.ToHCL().Ptr(),
})
}
provider := providerSupplier(pr.Type)
return evalContextProviderFunction(ctx, provider, walkPlan, pf, rng)
addr := addrs.AbsProviderInstanceCorrect{
Config: addrs.AbsProviderConfigCorrect{
Module: addrs.RootModuleInstance,
Config: addrs.ProviderConfigCorrect{
Provider: pr.Type,
Alias: "",
},
},
Key: addrs.NoKey,
}
return evalContextProviderFunction(ctx, tc.plugins, addr, walkPlan, pf, rng)
},
}

View File

@@ -13,6 +13,7 @@ import (
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/configs/configschema"
"github.com/opentofu/opentofu/internal/dag"
"github.com/opentofu/opentofu/internal/plugins"
)
// GraphNodeAttachResourceSchema is an interface implemented by node types
@@ -49,7 +50,7 @@ type GraphNodeAttachProvisionerSchema interface {
// GraphNodeAttachProvisionerSchema, looks up the needed schemas for each
// and then passes them to a method implemented by the node.
type AttachSchemaTransformer struct {
Plugins *contextPlugins
Plugins plugins.PluginManager
Config *configs.Config
}
@@ -69,7 +70,7 @@ func (t *AttachSchemaTransformer) Transform(ctx context.Context, g *Graph) error
providerFqn := tv.Provider()
// TODO: Plumb a useful context.Context through to here.
schema, version, err := t.Plugins.ResourceTypeSchema(ctx, providerFqn, mode, typeName)
schema, err := t.Plugins.ResourceTypeSchema(ctx, providerFqn, mode, typeName)
if err != nil {
return fmt.Errorf("failed to read schema for %s in %s: %w", addr, providerFqn, err)
}
@@ -78,7 +79,7 @@ func (t *AttachSchemaTransformer) Transform(ctx context.Context, g *Graph) error
continue
}
log.Printf("[TRACE] AttachSchemaTransformer: attaching resource schema to %s", dag.VertexName(v))
tv.AttachResourceSchema(schema, version)
tv.AttachResourceSchema(schema.Block, uint64(schema.Version))
}
if tv, ok := v.(GraphNodeAttachProviderConfigSchema); ok {
@@ -93,7 +94,7 @@ func (t *AttachSchemaTransformer) Transform(ctx context.Context, g *Graph) error
continue
}
log.Printf("[TRACE] AttachSchemaTransformer: attaching provider config schema to %s", dag.VertexName(v))
tv.AttachProviderConfigSchema(schema)
tv.AttachProviderConfigSchema(schema.Block)
}
if tv, ok := v.(GraphNodeAttachProvisionerSchema); ok {

View File

@@ -488,7 +488,7 @@ func (t *CloseProviderTransformer) Transform(_ context.Context, g *Graph) error
if closer == nil {
// create a closer for this provider type
closer = &graphNodeCloseProvider{Addr: p.ProviderAddr()}
closer = &graphNodeCloseProvider{Provider: p}
g.Add(closer)
cpm[key] = closer
}
@@ -629,7 +629,7 @@ func providerVertexMap(g *Graph) map[string]GraphNodeProvider {
}
type graphNodeCloseProvider struct {
Addr addrs.AbsProviderConfig
Provider GraphNodeProvider
}
var (
@@ -638,21 +638,37 @@ var (
)
func (n *graphNodeCloseProvider) Name() string {
return n.Addr.String() + " (close)"
return n.Provider.ProviderAddr().String() + " (close)"
}
// GraphNodeModulePath
func (n *graphNodeCloseProvider) ModulePath() addrs.Module {
return n.Addr.Module
return n.Provider.ProviderAddr().Module
}
// GraphNodeExecutable impl.
func (n *graphNodeCloseProvider) Execute(ctx context.Context, evalCtx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) {
return diags.Append(evalCtx.CloseProvider(ctx, n.Addr))
// TODO This whole function is a bad hack now, we should probably send a close function into here instead of the whole provider + inspection
if configured, ok := n.Provider.(*NodeApplyableProvider); ok && configured.Config != nil && configured.Config.Instances != nil {
for key := range configured.Config.Instances {
addr := n.Provider.ProviderAddr().InstanceCorrect(key)
if evalCtx.Providers().IsProviderConfigured(addr) {
diags = diags.Append(evalCtx.Providers().CloseProvider(ctx, addr))
}
}
return diags
} else {
addr := n.Provider.ProviderAddr().InstanceCorrect(addrs.NoKey)
if evalCtx.Providers().IsProviderConfigured(addr) {
return diags.Append(evalCtx.Providers().CloseProvider(ctx, addr))
}
return diags
}
}
func (n *graphNodeCloseProvider) CloseProviderAddr() addrs.AbsProviderConfig {
return n.Addr
return n.Provider.ProviderAddr()
}
// GraphNodeDotter impl.