blob: cd3cc8de915ba7f31e9809ed8a3eac77bfe09558 [file] [edit]
// 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/plugins"
"github.com/opentofu/opentofu/internal/providers"
"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.Providers
Close(ctx context.Context) error
}
type Provisioners interface {
eval.Provisioners
// [provisioners.Interface.ProvisionResource]
ProvisionResource(ctx context.Context, typ string, config cty.Value, connection cty.Value, output ProvisionerOutput) tfdiags.Diagnostics
}
type newRuntimePlugins struct {
providers plugins.ProviderManager
provisioners plugins.ProvisionerManager
// 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 NewRuntimePluginsTemp(providerManager plugins.ProviderManager, provisionerManager plugins.ProvisionerManager) Plugins {
return &newRuntimePlugins{
providers: providerManager,
provisioners: provisionerManager,
unconfiguredInsts: map[addrs.Provider]providers.Unconfigured{},
}
}
func NewRuntimePlugins(plugins plugins.Library) Plugins {
return &newRuntimePlugins{
providers: plugins.NewProviderManager(),
provisioners: plugins.NewProvisionerManager(),
unconfiguredInsts: map[addrs.Provider]providers.Unconfigured{},
}
}
// NewConfiguredProvider implements evalglue.Providers.
func (n *newRuntimePlugins) NewConfiguredProvider(ctx context.Context, provider addrs.Provider, configVal cty.Value) (providers.Configured, tfdiags.Diagnostics) {
return n.providers.NewConfiguredProvider(ctx, provider, configVal)
}
// ProviderConfigSchema implements evalglue.Providers.
func (n *newRuntimePlugins) ProviderConfigSchema(ctx context.Context, provider addrs.Provider) (*providers.Schema, tfdiags.Diagnostics) {
schema, diags := n.providers.GetProviderSchema(ctx, provider)
if diags.HasErrors() {
return nil, diags
}
return &schema.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) {
schema, diags := n.providers.GetProviderSchema(ctx, provider)
if diags.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 = schema.ResourceTypes
case addrs.DataResourceMode:
types = schema.DataSources
case addrs.EphemeralResourceMode:
types = schema.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
}
unmarkedConfigVal, _ := configVal.UnmarkDeep()
resp := inst.ValidateProviderConfig(ctx, providers.ValidateProviderConfigRequest{
Config: unmarkedConfigVal,
})
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 (n *newRuntimePlugins) unconfiguredProviderInst(ctx context.Context, provider addrs.Provider) (providers.Unconfigured, tfdiags.Diagnostics) {
n.mu.Lock()
defer n.mu.Unlock()
if running, ok := n.unconfiguredInsts[provider]; ok {
return running, nil
}
inst, diags := n.providers.NewProvider(ctx, provider)
if diags.HasErrors() {
return nil, diags
}
if n.unconfiguredInsts == nil {
n.unconfiguredInsts = make(map[addrs.Provider]providers.Unconfigured)
}
n.unconfiguredInsts[provider] = inst
return inst, diags
}
// ProvisionerConfigSchema implements evalglue.Provisioners.
func (n *newRuntimePlugins) ProvisionerConfigSchema(ctx context.Context, typeName string) (*configschema.Block, tfdiags.Diagnostics) {
schema, err := n.provisioners.ProvisionerSchema(typeName)
return schema, tfdiags.New(err)
}
func (n *newRuntimePlugins) ValidateProvisionerConfig(ctx context.Context, typ string, config cty.Value) tfdiags.Diagnostics {
return n.provisioners.ValidateProvisionerConfig(ctx, typ, config)
}
type ProvisionerOutput func(string)
func (fn ProvisionerOutput) Output(str string) {
fn(str)
}
func (n *newRuntimePlugins) ProvisionResource(ctx context.Context, typ string, config cty.Value, connection cty.Value, output ProvisionerOutput) tfdiags.Diagnostics {
return n.provisioners.ProvisionResource(ctx, typ, config, connection, output)
}
// 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()
n.unconfiguredInsts = nil // discard all of the memoized instances
return errors.Join(n.providers.CloseAll(ctx), n.provisioners.CloseAll())
}