mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-19 17:59:05 -05:00
Remove global schema cache and clean up tofu schema/contextPlugins
Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
@@ -110,12 +110,8 @@ func (b *Local) opApply(
|
|||||||
// operation.
|
// operation.
|
||||||
runningOp.State = lr.InputState
|
runningOp.State = lr.InputState
|
||||||
|
|
||||||
schemas, moreDiags := lr.Core.Schemas(ctx, lr.Config, lr.InputState)
|
schemas := lr.Core.Schemas()
|
||||||
diags = diags.Append(moreDiags)
|
|
||||||
if moreDiags.HasErrors() {
|
|
||||||
op.ReportResult(runningOp, diags)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// stateHook uses schemas for when it periodically persists state to the
|
// stateHook uses schemas for when it periodically persists state to the
|
||||||
// persistent storage backend.
|
// persistent storage backend.
|
||||||
stateHook.Schemas = schemas
|
stateHook.Schemas = schemas
|
||||||
|
|||||||
@@ -203,12 +203,7 @@ func (b *Local) opPlan(
|
|||||||
|
|
||||||
// Render the plan, if we produced one.
|
// Render the plan, if we produced one.
|
||||||
// (This might potentially be a partial plan with Errored set to true)
|
// (This might potentially be a partial plan with Errored set to true)
|
||||||
schemas, moreDiags := lr.Core.Schemas(ctx, lr.Config, lr.InputState)
|
schemas := lr.Core.Schemas()
|
||||||
diags = diags.Append(moreDiags)
|
|
||||||
if moreDiags.HasErrors() {
|
|
||||||
op.ReportResult(runningOp, diags)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write out any generated config, before we render the plan.
|
// Write out any generated config, before we render the plan.
|
||||||
wroteConfig, moreDiags := maybeWriteGeneratedConfig(plan, op.GenerateConfigOut)
|
wroteConfig, moreDiags := maybeWriteGeneratedConfig(plan, op.GenerateConfigOut)
|
||||||
|
|||||||
@@ -95,12 +95,7 @@ func (b *Local) opRefresh(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get schemas before writing state
|
// get schemas before writing state
|
||||||
schemas, moreDiags := lr.Core.Schemas(ctx, lr.Config, lr.InputState)
|
schemas := lr.Core.Schemas()
|
||||||
diags = diags.Append(moreDiags)
|
|
||||||
if moreDiags.HasErrors() {
|
|
||||||
op.ReportResult(runningOp, diags)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform the refresh in a goroutine so we can be interrupted
|
// Perform the refresh in a goroutine so we can be interrupted
|
||||||
var newState *states.State
|
var newState *states.State
|
||||||
|
|||||||
@@ -575,7 +575,7 @@ func (m *Meta) RunOperation(ctx context.Context, b backend.Enhanced, opReq *back
|
|||||||
|
|
||||||
// contextOpts returns the options to use to initialize a OpenTofu
|
// contextOpts returns the options to use to initialize a OpenTofu
|
||||||
// context with the settings from this Meta.
|
// context with the settings from this Meta.
|
||||||
func (m *Meta) contextOpts(ctx context.Context) (*tofu.ContextOpts, error) {
|
func (m *Meta) contextOpts(ctx context.Context, config *configs.Config, state *states.State) (*tofu.ContextOpts, error) {
|
||||||
workspace, err := m.Workspace(ctx)
|
workspace, err := m.Workspace(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -599,6 +599,49 @@ func (m *Meta) contextOpts(ctx context.Context) (*tofu.ContextOpts, error) {
|
|||||||
opts.Provisioners = m.provisionerFactories()
|
opts.Provisioners = m.provisionerFactories()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only include the providers referenced by configuration or state
|
||||||
|
if config != nil || state != nil {
|
||||||
|
referenced := map[addrs.Provider]providers.Factory{}
|
||||||
|
if config != nil {
|
||||||
|
for _, fqn := range config.ProviderTypes() {
|
||||||
|
referenced[fqn] = opts.Providers[fqn]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if state != nil {
|
||||||
|
needed := providers.AddressedTypesAbs(state.ProviderAddrs())
|
||||||
|
for _, fqn := range needed {
|
||||||
|
referenced[fqn] = opts.Providers[fqn]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opts.Providers = referenced
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only include provisioners referenced by configuration
|
||||||
|
if config != nil {
|
||||||
|
referenced := map[string]provisioners.Factory{}
|
||||||
|
// Determine the full list of provisioners recursively
|
||||||
|
var addProvisionersToSchema func(config *configs.Config)
|
||||||
|
addProvisionersToSchema = func(config *configs.Config) {
|
||||||
|
if config == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, rc := range config.Module.ManagedResources {
|
||||||
|
for _, pc := range rc.Managed.Provisioners {
|
||||||
|
referenced[pc.Type] = opts.Provisioners[pc.Type]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must also visit our child modules, recursively.
|
||||||
|
for _, cc := range config.Children {
|
||||||
|
addProvisionersToSchema(cc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addProvisionersToSchema(config)
|
||||||
|
|
||||||
|
opts.Provisioners = referenced
|
||||||
|
}
|
||||||
|
|
||||||
opts.Meta = &tofu.ContextMeta{
|
opts.Meta = &tofu.ContextMeta{
|
||||||
Env: workspace,
|
Env: workspace,
|
||||||
OriginalWorkingDir: m.WorkingDir.OriginalWorkingDir(),
|
OriginalWorkingDir: m.WorkingDir.OriginalWorkingDir(),
|
||||||
@@ -951,7 +994,7 @@ func (c *Meta) MaybeGetSchemas(ctx context.Context, state *states.State, config
|
|||||||
}
|
}
|
||||||
|
|
||||||
if config != nil || state != nil {
|
if config != nil || state != nil {
|
||||||
opts, err := c.contextOpts(ctx)
|
opts, err := c.contextOpts(ctx, config, state)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
diags = diags.Append(err)
|
diags = diags.Append(err)
|
||||||
return nil, diags
|
return nil, diags
|
||||||
@@ -962,7 +1005,7 @@ func (c *Meta) MaybeGetSchemas(ctx context.Context, state *states.State, config
|
|||||||
return nil, diags
|
return nil, diags
|
||||||
}
|
}
|
||||||
var schemaDiags tfdiags.Diagnostics
|
var schemaDiags tfdiags.Diagnostics
|
||||||
schemas, schemaDiags := tfCtx.Schemas(ctx, config, state)
|
schemas := tfCtx.Schemas()
|
||||||
diags = diags.Append(schemaDiags)
|
diags = diags.Append(schemaDiags)
|
||||||
if schemaDiags.HasErrors() {
|
if schemaDiags.HasErrors() {
|
||||||
return nil, diags
|
return nil, diags
|
||||||
|
|||||||
@@ -404,7 +404,9 @@ func (m *Meta) BackendForLocalPlan(ctx context.Context, settings plans.Backend,
|
|||||||
// backendCLIOpts returns a backend.CLIOpts object that should be passed to
|
// backendCLIOpts returns a backend.CLIOpts object that should be passed to
|
||||||
// a backend that supports local CLI operations.
|
// a backend that supports local CLI operations.
|
||||||
func (m *Meta) backendCLIOpts(ctx context.Context) (*backend.CLIOpts, error) {
|
func (m *Meta) backendCLIOpts(ctx context.Context) (*backend.CLIOpts, error) {
|
||||||
contextOpts, err := m.contextOpts(ctx)
|
// TODO this does not allow for filtering schemas in it's current form
|
||||||
|
// This does not break anything per say, other than being slower and more verbose in some scenarios
|
||||||
|
contextOpts, err := m.contextOpts(ctx, nil, nil)
|
||||||
if contextOpts == nil && err != nil {
|
if contextOpts == nil && err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -361,10 +361,28 @@ func (m *Meta) internalProviders() map[string]providers.Factory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func providerSchemaCache() func(func() providers.ProviderSchema) providers.ProviderSchema {
|
||||||
|
var mu sync.Mutex
|
||||||
|
var schema providers.ProviderSchema
|
||||||
|
|
||||||
|
return func(getSchema func() providers.ProviderSchema) providers.ProviderSchema {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
if schema.Provider.Block != nil {
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
schema = getSchema()
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// providerFactory produces a provider factory that runs up the executable
|
// providerFactory produces a provider factory that runs up the executable
|
||||||
// file in the given cache package and uses go-plugin to implement
|
// file in the given cache package and uses go-plugin to implement
|
||||||
// providers.Interface against it.
|
// providers.Interface against it.
|
||||||
func providerFactory(meta *providercache.CachedProvider) providers.Factory {
|
func providerFactory(meta *providercache.CachedProvider) providers.Factory {
|
||||||
|
schemaCache := providerSchemaCache()
|
||||||
|
|
||||||
return func() (providers.Interface, error) {
|
return func() (providers.Interface, error) {
|
||||||
execFile, err := meta.ExecutableFile()
|
execFile, err := meta.ExecutableFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -395,7 +413,7 @@ func providerFactory(meta *providercache.CachedProvider) providers.Factory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protoVer := client.NegotiatedVersion()
|
protoVer := client.NegotiatedVersion()
|
||||||
p, err := initializeProviderInstance(raw, protoVer, client, meta.Provider)
|
p, err := initializeProviderInstance(raw, protoVer, client, schemaCache)
|
||||||
if errors.Is(err, errUnsupportedProtocolVersion) {
|
if errors.Is(err, errUnsupportedProtocolVersion) {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -406,18 +424,18 @@ func providerFactory(meta *providercache.CachedProvider) providers.Factory {
|
|||||||
|
|
||||||
// initializeProviderInstance uses the plugin dispensed by the RPC client, and initializes a plugin instance
|
// initializeProviderInstance uses the plugin dispensed by the RPC client, and initializes a plugin instance
|
||||||
// per the protocol version
|
// per the protocol version
|
||||||
func initializeProviderInstance(plugin interface{}, protoVer int, pluginClient *plugin.Client, pluginAddr addrs.Provider) (providers.Interface, error) {
|
func initializeProviderInstance(plugin interface{}, protoVer int, pluginClient *plugin.Client, schemaCache func(func() providers.ProviderSchema) providers.ProviderSchema) (providers.Interface, error) {
|
||||||
// store the client so that the plugin can kill the child process
|
// store the client so that the plugin can kill the child process
|
||||||
switch protoVer {
|
switch protoVer {
|
||||||
case 5:
|
case 5:
|
||||||
p := plugin.(*tfplugin.GRPCProvider)
|
p := plugin.(*tfplugin.GRPCProvider)
|
||||||
p.PluginClient = pluginClient
|
p.PluginClient = pluginClient
|
||||||
p.Addr = pluginAddr
|
p.SchemaCache = schemaCache
|
||||||
return p, nil
|
return p, nil
|
||||||
case 6:
|
case 6:
|
||||||
p := plugin.(*tfplugin6.GRPCProvider)
|
p := plugin.(*tfplugin6.GRPCProvider)
|
||||||
p.PluginClient = pluginClient
|
p.PluginClient = pluginClient
|
||||||
p.Addr = pluginAddr
|
p.SchemaCache = schemaCache
|
||||||
return p, nil
|
return p, nil
|
||||||
default:
|
default:
|
||||||
return nil, errUnsupportedProtocolVersion
|
return nil, errUnsupportedProtocolVersion
|
||||||
@@ -441,6 +459,8 @@ func devOverrideProviderFactory(provider addrs.Provider, localDir getproviders.P
|
|||||||
// reattach information to connect to go-plugin processes that are already
|
// reattach information to connect to go-plugin processes that are already
|
||||||
// running, and implements providers.Interface against it.
|
// running, and implements providers.Interface against it.
|
||||||
func unmanagedProviderFactory(provider addrs.Provider, reattach *plugin.ReattachConfig) providers.Factory {
|
func unmanagedProviderFactory(provider addrs.Provider, reattach *plugin.ReattachConfig) providers.Factory {
|
||||||
|
schemaCache := providerSchemaCache()
|
||||||
|
|
||||||
return func() (providers.Interface, error) {
|
return func() (providers.Interface, error) {
|
||||||
config := &plugin.ClientConfig{
|
config := &plugin.ClientConfig{
|
||||||
HandshakeConfig: tfplugin.Handshake,
|
HandshakeConfig: tfplugin.Handshake,
|
||||||
@@ -490,7 +510,7 @@ func unmanagedProviderFactory(provider addrs.Provider, reattach *plugin.Reattach
|
|||||||
protoVer = 5
|
protoVer = 5
|
||||||
}
|
}
|
||||||
|
|
||||||
return initializeProviderInstance(raw, protoVer, client, provider)
|
return initializeProviderInstance(raw, protoVer, client, schemaCache)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -120,12 +120,7 @@ func (c *ProvidersSchemaCommand) Run(args []string) int {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
schemas, moreDiags := lr.Core.Schemas(ctx, lr.Config, lr.InputState)
|
schemas := lr.Core.Schemas()
|
||||||
diags = diags.Append(moreDiags)
|
|
||||||
if moreDiags.HasErrors() {
|
|
||||||
c.showDiagnostics(diags)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonSchemas, err := jsonprovider.Marshal(schemas)
|
jsonSchemas, err := jsonprovider.Marshal(schemas)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -122,11 +122,7 @@ func (c *StateShowCommand) Run(args []string) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the schemas from the context
|
// Get the schemas from the context
|
||||||
schemas, diags := lr.Core.Schemas(ctx, lr.Config, lr.InputState)
|
schemas := lr.Core.Schemas()
|
||||||
if diags.HasErrors() {
|
|
||||||
c.View.Diagnostics(diags)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the state
|
// Get the state
|
||||||
env, err := c.Workspace(ctx)
|
env, err := c.Workspace(ctx)
|
||||||
@@ -149,6 +145,7 @@ func (c *StateShowCommand) Run(args []string) int {
|
|||||||
c.Streams.Eprintln(errStateNotFound)
|
c.Streams.Eprintln(errStateNotFound)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
var diags tfdiags.Diagnostics
|
||||||
migratedState, migrateDiags := tofumigrate.MigrateStateProviderAddresses(lr.Config, state)
|
migratedState, migrateDiags := tofumigrate.MigrateStateProviderAddresses(lr.Config, state)
|
||||||
diags = diags.Append(migrateDiags)
|
diags = diags.Append(migrateDiags)
|
||||||
if migrateDiags.HasErrors() {
|
if migrateDiags.HasErrors() {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"path"
|
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -242,7 +241,7 @@ func (c *TestCommand) Run(rawArgs []string) int {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
opts, err := c.contextOpts(ctx)
|
opts, err := c.contextOpts(ctx, config, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
diags = diags.Append(err)
|
diags = diags.Append(err)
|
||||||
view.Diagnostics(nil, nil, diags)
|
view.Diagnostics(nil, nil, diags)
|
||||||
@@ -591,28 +590,15 @@ func (runner *TestFileRunner) ExecuteTestRun(ctx context.Context, run *moduletes
|
|||||||
}
|
}
|
||||||
|
|
||||||
if runner.Suite.Verbose {
|
if runner.Suite.Verbose {
|
||||||
schemas, diags := planCtx.Schemas(ctx, config, plan.PlannedState)
|
schemas := planCtx.Schemas()
|
||||||
|
|
||||||
// If we're going to fail to render the plan, let's not fail the overall
|
run.Verbose = &moduletest.Verbose{
|
||||||
// test. It can still have succeeded. So we'll add the diagnostics, but
|
Plan: plan,
|
||||||
// still report the test status as a success.
|
State: plan.PlannedState,
|
||||||
if diags.HasErrors() {
|
Config: config,
|
||||||
// This is very unlikely.
|
Providers: schemas.Providers,
|
||||||
diags = diags.Append(tfdiags.Sourceless(
|
Provisioners: schemas.Provisioners,
|
||||||
tfdiags.Warning,
|
|
||||||
"Failed to print verbose output",
|
|
||||||
fmt.Sprintf("OpenTofu failed to print the verbose output for %s, other diagnostics will contain more details as to why.", path.Join(file.Name, run.Name))))
|
|
||||||
} else {
|
|
||||||
run.Verbose = &moduletest.Verbose{
|
|
||||||
Plan: plan,
|
|
||||||
State: plan.PlannedState,
|
|
||||||
Config: config,
|
|
||||||
Providers: schemas.Providers,
|
|
||||||
Provisioners: schemas.Provisioners,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
run.Diagnostics = run.Diagnostics.Append(diags)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
planCtx.TestContext(config, plan.PlannedState, plan, variables).EvaluateAgainstPlan(run)
|
planCtx.TestContext(config, plan.PlannedState, plan, variables).EvaluateAgainstPlan(run)
|
||||||
@@ -668,28 +654,14 @@ func (runner *TestFileRunner) ExecuteTestRun(ctx context.Context, run *moduletes
|
|||||||
}
|
}
|
||||||
|
|
||||||
if runner.Suite.Verbose {
|
if runner.Suite.Verbose {
|
||||||
schemas, diags := planCtx.Schemas(ctx, config, plan.PlannedState)
|
schemas := planCtx.Schemas()
|
||||||
|
run.Verbose = &moduletest.Verbose{
|
||||||
// If we're going to fail to render the plan, let's not fail the overall
|
Plan: plan,
|
||||||
// test. It can still have succeeded. So we'll add the diagnostics, but
|
State: updated,
|
||||||
// still report the test status as a success.
|
Config: config,
|
||||||
if diags.HasErrors() {
|
Providers: schemas.Providers,
|
||||||
// This is very unlikely.
|
Provisioners: schemas.Provisioners,
|
||||||
diags = diags.Append(tfdiags.Sourceless(
|
|
||||||
tfdiags.Warning,
|
|
||||||
"Failed to print verbose output",
|
|
||||||
fmt.Sprintf("OpenTofu failed to print the verbose output for %s, other diagnostics will contain more details as to why.", path.Join(file.Name, run.Name))))
|
|
||||||
} else {
|
|
||||||
run.Verbose = &moduletest.Verbose{
|
|
||||||
Plan: plan,
|
|
||||||
State: updated,
|
|
||||||
Config: config,
|
|
||||||
Providers: schemas.Providers,
|
|
||||||
Provisioners: schemas.Provisioners,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
run.Diagnostics = run.Diagnostics.Append(diags)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
applyCtx.TestContext(config, updated, plan, variables).EvaluateAgainstState(run)
|
applyCtx.TestContext(config, updated, plan, variables).EvaluateAgainstState(run)
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ func (c *ValidateCommand) validate(ctx context.Context, dir, testDir string, noT
|
|||||||
validate := func(cfg *configs.Config) tfdiags.Diagnostics {
|
validate := func(cfg *configs.Config) tfdiags.Diagnostics {
|
||||||
var diags tfdiags.Diagnostics
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
opts, err := c.contextOpts(ctx)
|
opts, err := c.contextOpts(ctx, cfg, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
diags = diags.Append(err)
|
diags = diags.Append(err)
|
||||||
return diags
|
return diags
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
|
||||||
|
|
||||||
plugin "github.com/hashicorp/go-plugin"
|
plugin "github.com/hashicorp/go-plugin"
|
||||||
"github.com/opentofu/opentofu/internal/plugin/validation"
|
"github.com/opentofu/opentofu/internal/plugin/validation"
|
||||||
@@ -18,7 +17,6 @@ import (
|
|||||||
"github.com/zclconf/go-cty/cty/msgpack"
|
"github.com/zclconf/go-cty/cty/msgpack"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/addrs"
|
|
||||||
"github.com/opentofu/opentofu/internal/logging"
|
"github.com/opentofu/opentofu/internal/logging"
|
||||||
"github.com/opentofu/opentofu/internal/plugin/convert"
|
"github.com/opentofu/opentofu/internal/plugin/convert"
|
||||||
"github.com/opentofu/opentofu/internal/providers"
|
"github.com/opentofu/opentofu/internal/providers"
|
||||||
@@ -27,6 +25,16 @@ import (
|
|||||||
|
|
||||||
var logger = logging.HCLogger()
|
var logger = logging.HCLogger()
|
||||||
|
|
||||||
|
// Some providers may generate quite large schemas, and the internal default
|
||||||
|
// grpc response size limit is 4MB. 64MB should cover most any use case, and
|
||||||
|
// if we get providers nearing that we may want to consider a finer-grained
|
||||||
|
// API to fetch individual resource schemas.
|
||||||
|
// Note: this option is marked as EXPERIMENTAL in the grpc API. We keep
|
||||||
|
// this for compatibility, but recent providers all set the max message
|
||||||
|
// size much higher on the server side, which is the supported method for
|
||||||
|
// determining payload size.
|
||||||
|
const maxRecvSize = 64 << 20
|
||||||
|
|
||||||
// GRPCProviderPlugin implements plugin.GRPCPlugin for the go-plugin package.
|
// GRPCProviderPlugin implements plugin.GRPCPlugin for the go-plugin package.
|
||||||
type GRPCProviderPlugin struct {
|
type GRPCProviderPlugin struct {
|
||||||
plugin.Plugin
|
plugin.Plugin
|
||||||
@@ -75,11 +83,6 @@ type GRPCProvider struct {
|
|||||||
// used in an end to end test of a provider.
|
// used in an end to end test of a provider.
|
||||||
TestServer *grpc.Server
|
TestServer *grpc.Server
|
||||||
|
|
||||||
// Addr uniquely identifies the type of provider.
|
|
||||||
// Normally executed providers will have this set during initialization,
|
|
||||||
// but it may not always be available for alternative execute modes.
|
|
||||||
Addr addrs.Provider
|
|
||||||
|
|
||||||
// Proto client use to make the grpc service calls.
|
// Proto client use to make the grpc service calls.
|
||||||
client proto.ProviderClient
|
client proto.ProviderClient
|
||||||
|
|
||||||
@@ -94,53 +97,44 @@ type GRPCProvider struct {
|
|||||||
// to use as the parent context for gRPC API calls.
|
// to use as the parent context for gRPC API calls.
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
|
||||||
mu sync.Mutex
|
// SchemaCache stores the schema for this provider. This is used to properly
|
||||||
// schema stores the schema for this provider. This is used to properly
|
// serialize the requests for schemas. This is shared between instances
|
||||||
// serialize the requests for schemas.
|
// of the provider.
|
||||||
schema providers.GetProviderSchemaResponse
|
SchemaCache func(func() providers.GetProviderSchemaResponse) providers.GetProviderSchemaResponse
|
||||||
|
hasFetchedSchema bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ providers.Interface = new(GRPCProvider)
|
var _ providers.Interface = new(GRPCProvider)
|
||||||
|
|
||||||
func (p *GRPCProvider) GetProviderSchema(ctx context.Context) (resp providers.GetProviderSchemaResponse) {
|
func (p *GRPCProvider) GetProviderSchema(ctx context.Context) (resp providers.GetProviderSchemaResponse) {
|
||||||
logger.Trace("GRPCProvider: GetProviderSchema")
|
logger.Trace("GRPCProvider: GetProviderSchema")
|
||||||
p.mu.Lock()
|
|
||||||
defer p.mu.Unlock()
|
|
||||||
|
|
||||||
// First, we check the global cache.
|
// For testing only
|
||||||
// The cache could contain this schema if an instance of this provider has previously been started.
|
if p.SchemaCache == nil {
|
||||||
if !p.Addr.IsZero() {
|
return p.getProviderSchema(ctx)
|
||||||
// Even if the schema is cached, GetProviderSchemaOptional could be false. This would indicate that once instantiated,
|
|
||||||
// this provider requires the get schema call to be made at least once, as it handles part of the provider's setup.
|
|
||||||
// At this point, we don't know if this is the first call to a provider instance or not, so we don't use the result in that case.
|
|
||||||
if schemaCached, ok := providers.SchemaCache.Get(p.Addr); ok && schemaCached.ServerCapabilities.GetProviderSchemaOptional {
|
|
||||||
logger.Trace("GRPCProvider: GetProviderSchema: serving from global schema cache", "address", p.Addr)
|
|
||||||
return schemaCached
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the local cache is non-zero, we know this instance has called
|
schema := p.SchemaCache(func() providers.GetProviderSchemaResponse {
|
||||||
// GetProviderSchema at least once, so has satisfied the possible requirement of `GetProviderSchemaOptional=false`.
|
return p.getProviderSchema(ctx)
|
||||||
// This means that we can return early now using the locally cached schema, without making this call again.
|
})
|
||||||
if p.schema.Provider.Block != nil {
|
|
||||||
return p.schema
|
if !p.hasFetchedSchema && !schema.ServerCapabilities.GetProviderSchemaOptional {
|
||||||
|
// Force call
|
||||||
|
p.client.GetSchema(ctx, new(proto.GetProviderSchema_Request), grpc.MaxRecvMsgSizeCallOption{MaxRecvMsgSize: maxRecvSize})
|
||||||
|
p.hasFetchedSchema = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
func (p *GRPCProvider) getProviderSchema(ctx context.Context) (resp providers.GetProviderSchemaResponse) {
|
||||||
resp.ResourceTypes = make(map[string]providers.Schema)
|
resp.ResourceTypes = make(map[string]providers.Schema)
|
||||||
resp.DataSources = make(map[string]providers.Schema)
|
resp.DataSources = make(map[string]providers.Schema)
|
||||||
resp.EphemeralResources = make(map[string]providers.Schema)
|
resp.EphemeralResources = make(map[string]providers.Schema)
|
||||||
resp.Functions = make(map[string]providers.FunctionSpec)
|
resp.Functions = make(map[string]providers.FunctionSpec)
|
||||||
|
|
||||||
// Some providers may generate quite large schemas, and the internal default
|
|
||||||
// grpc response size limit is 4MB. 64MB should cover most any use case, and
|
|
||||||
// if we get providers nearing that we may want to consider a finer-grained
|
|
||||||
// API to fetch individual resource schemas.
|
|
||||||
// Note: this option is marked as EXPERIMENTAL in the grpc API. We keep
|
|
||||||
// this for compatibility, but recent providers all set the max message
|
|
||||||
// size much higher on the server side, which is the supported method for
|
|
||||||
// determining payload size.
|
|
||||||
const maxRecvSize = 64 << 20
|
|
||||||
protoResp, err := p.client.GetSchema(ctx, new(proto.GetProviderSchema_Request), grpc.MaxRecvMsgSizeCallOption{MaxRecvMsgSize: maxRecvSize})
|
protoResp, err := p.client.GetSchema(ctx, new(proto.GetProviderSchema_Request), grpc.MaxRecvMsgSizeCallOption{MaxRecvMsgSize: maxRecvSize})
|
||||||
|
p.hasFetchedSchema = true
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err))
|
resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err))
|
||||||
return resp
|
return resp
|
||||||
@@ -188,23 +182,6 @@ func (p *GRPCProvider) GetProviderSchema(ctx context.Context) (resp providers.Ge
|
|||||||
resp.ServerCapabilities.GetProviderSchemaOptional = protoResp.ServerCapabilities.GetProviderSchemaOptional
|
resp.ServerCapabilities.GetProviderSchemaOptional = protoResp.ServerCapabilities.GetProviderSchemaOptional
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the global provider cache so that future calls to this provider can use the cached value.
|
|
||||||
// Crucially, this doesn't look at GetProviderSchemaOptional, because the layers above could use this cache
|
|
||||||
// *without* creating an instance of this provider. And if there is no instance,
|
|
||||||
// then we don't need to set up anything (cause there is nothing to set up), so we need no call
|
|
||||||
// to the providers GetSchema rpc.
|
|
||||||
if !p.Addr.IsZero() {
|
|
||||||
providers.SchemaCache.Set(p.Addr, resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always store this here in the client for providers that are not able to use GetProviderSchemaOptional.
|
|
||||||
// Crucially, this indicates that we've made at least one call to GetProviderSchema to this instance of the provider,
|
|
||||||
// which means in the future we'll be able to return using this cache
|
|
||||||
// (because the possible setup contained in the GetProviderSchema call has happened).
|
|
||||||
// If GetProviderSchemaOptional is true then this cache won't actually ever be used, because the calls to this method
|
|
||||||
// will be satisfied by the global provider cache.
|
|
||||||
p.schema = resp
|
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -22,7 +23,6 @@ import (
|
|||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/addrs"
|
|
||||||
"github.com/opentofu/opentofu/internal/legacy/hcl2shim"
|
"github.com/opentofu/opentofu/internal/legacy/hcl2shim"
|
||||||
mockproto "github.com/opentofu/opentofu/internal/plugin/mock_proto"
|
mockproto "github.com/opentofu/opentofu/internal/plugin/mock_proto"
|
||||||
"github.com/opentofu/opentofu/internal/providers"
|
"github.com/opentofu/opentofu/internal/providers"
|
||||||
@@ -32,6 +32,23 @@ import (
|
|||||||
|
|
||||||
var _ providers.Interface = (*GRPCProvider)(nil)
|
var _ providers.Interface = (*GRPCProvider)(nil)
|
||||||
|
|
||||||
|
// TODO this should probably live somewhere common
|
||||||
|
func providerSchemaCache() func(func() providers.ProviderSchema) providers.ProviderSchema {
|
||||||
|
var mu sync.Mutex
|
||||||
|
var schema providers.ProviderSchema
|
||||||
|
|
||||||
|
return func(getSchema func() providers.ProviderSchema) providers.ProviderSchema {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
if schema.Provider.Block != nil {
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
schema = getSchema()
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func mutateSchemaResponse(response *proto.GetProviderSchema_Response, mut ...func(schemaResponse *proto.GetProviderSchema_Response)) *proto.GetProviderSchema_Response {
|
func mutateSchemaResponse(response *proto.GetProviderSchema_Response, mut ...func(schemaResponse *proto.GetProviderSchema_Response)) *proto.GetProviderSchema_Response {
|
||||||
for _, f := range mut {
|
for _, f := range mut {
|
||||||
f(response)
|
f(response)
|
||||||
@@ -205,13 +222,8 @@ func TestGRPCProvider_GetSchema_GRPCError(t *testing.T) {
|
|||||||
func TestGRPCProvider_GetSchema_GlobalCacheEnabled(t *testing.T) {
|
func TestGRPCProvider_GetSchema_GlobalCacheEnabled(t *testing.T) {
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
client := mockproto.NewMockProviderClient(ctrl)
|
client := mockproto.NewMockProviderClient(ctrl)
|
||||||
// The SchemaCache is global and is saved between test runs
|
|
||||||
providers.SchemaCache = providers.NewMockSchemaCache()
|
|
||||||
|
|
||||||
providerAddr := addrs.Provider{
|
cache := providerSchemaCache()
|
||||||
Namespace: "namespace",
|
|
||||||
Type: "type",
|
|
||||||
}
|
|
||||||
|
|
||||||
mockedProviderResponse := &proto.Schema{Version: 2, Block: &proto.Schema_Block{}}
|
mockedProviderResponse := &proto.Schema{Version: 2, Block: &proto.Schema_Block{}}
|
||||||
|
|
||||||
@@ -227,8 +239,8 @@ func TestGRPCProvider_GetSchema_GlobalCacheEnabled(t *testing.T) {
|
|||||||
// Run GetProviderTwice, expect GetSchema to be called once
|
// Run GetProviderTwice, expect GetSchema to be called once
|
||||||
// Re-initialize the provider before each run to avoid usage of the local cache
|
// Re-initialize the provider before each run to avoid usage of the local cache
|
||||||
p := &GRPCProvider{
|
p := &GRPCProvider{
|
||||||
client: client,
|
client: client,
|
||||||
Addr: providerAddr,
|
SchemaCache: cache,
|
||||||
}
|
}
|
||||||
resp := p.GetProviderSchema(t.Context())
|
resp := p.GetProviderSchema(t.Context())
|
||||||
|
|
||||||
@@ -238,8 +250,8 @@ func TestGRPCProvider_GetSchema_GlobalCacheEnabled(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
p = &GRPCProvider{
|
p = &GRPCProvider{
|
||||||
client: client,
|
client: client,
|
||||||
Addr: providerAddr,
|
SchemaCache: cache,
|
||||||
}
|
}
|
||||||
resp = p.GetProviderSchema(t.Context())
|
resp = p.GetProviderSchema(t.Context())
|
||||||
|
|
||||||
@@ -252,13 +264,6 @@ func TestGRPCProvider_GetSchema_GlobalCacheEnabled(t *testing.T) {
|
|||||||
func TestGRPCProvider_GetSchema_GlobalCacheDisabled(t *testing.T) {
|
func TestGRPCProvider_GetSchema_GlobalCacheDisabled(t *testing.T) {
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
client := mockproto.NewMockProviderClient(ctrl)
|
client := mockproto.NewMockProviderClient(ctrl)
|
||||||
// The SchemaCache is global and is saved between test runs
|
|
||||||
providers.SchemaCache = providers.NewMockSchemaCache()
|
|
||||||
|
|
||||||
providerAddr := addrs.Provider{
|
|
||||||
Namespace: "namespace",
|
|
||||||
Type: "type",
|
|
||||||
}
|
|
||||||
|
|
||||||
mockedProviderResponse := &proto.Schema{Version: 2, Block: &proto.Schema_Block{}}
|
mockedProviderResponse := &proto.Schema{Version: 2, Block: &proto.Schema_Block{}}
|
||||||
|
|
||||||
@@ -274,8 +279,8 @@ func TestGRPCProvider_GetSchema_GlobalCacheDisabled(t *testing.T) {
|
|||||||
// Run GetProviderTwice, expect GetSchema to be called once
|
// Run GetProviderTwice, expect GetSchema to be called once
|
||||||
// Re-initialize the provider before each run to avoid usage of the local cache
|
// Re-initialize the provider before each run to avoid usage of the local cache
|
||||||
p := &GRPCProvider{
|
p := &GRPCProvider{
|
||||||
client: client,
|
client: client,
|
||||||
Addr: providerAddr,
|
SchemaCache: providerSchemaCache(),
|
||||||
}
|
}
|
||||||
resp := p.GetProviderSchema(t.Context())
|
resp := p.GetProviderSchema(t.Context())
|
||||||
|
|
||||||
@@ -285,8 +290,8 @@ func TestGRPCProvider_GetSchema_GlobalCacheDisabled(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
p = &GRPCProvider{
|
p = &GRPCProvider{
|
||||||
client: client,
|
client: client,
|
||||||
Addr: providerAddr,
|
SchemaCache: providerSchemaCache(),
|
||||||
}
|
}
|
||||||
resp = p.GetProviderSchema(t.Context())
|
resp = p.GetProviderSchema(t.Context())
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
|
||||||
|
|
||||||
plugin "github.com/hashicorp/go-plugin"
|
plugin "github.com/hashicorp/go-plugin"
|
||||||
"github.com/opentofu/opentofu/internal/plugin6/validation"
|
"github.com/opentofu/opentofu/internal/plugin6/validation"
|
||||||
@@ -18,7 +17,6 @@ import (
|
|||||||
"github.com/zclconf/go-cty/cty/msgpack"
|
"github.com/zclconf/go-cty/cty/msgpack"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/addrs"
|
|
||||||
"github.com/opentofu/opentofu/internal/logging"
|
"github.com/opentofu/opentofu/internal/logging"
|
||||||
"github.com/opentofu/opentofu/internal/plugin6/convert"
|
"github.com/opentofu/opentofu/internal/plugin6/convert"
|
||||||
"github.com/opentofu/opentofu/internal/providers"
|
"github.com/opentofu/opentofu/internal/providers"
|
||||||
@@ -27,6 +25,16 @@ import (
|
|||||||
|
|
||||||
var logger = logging.HCLogger()
|
var logger = logging.HCLogger()
|
||||||
|
|
||||||
|
// Some providers may generate quite large schemas, and the internal default
|
||||||
|
// grpc response size limit is 4MB. 64MB should cover most any use case, and
|
||||||
|
// if we get providers nearing that we may want to consider a finer-grained
|
||||||
|
// API to fetch individual resource schemas.
|
||||||
|
// Note: this option is marked as EXPERIMENTAL in the grpc API. We keep
|
||||||
|
// this for compatibility, but recent providers all set the max message
|
||||||
|
// size much higher on the server side, which is the supported method for
|
||||||
|
// determining payload size.
|
||||||
|
const maxRecvSize = 64 << 20
|
||||||
|
|
||||||
// GRPCProviderPlugin implements plugin.GRPCPlugin for the go-plugin package.
|
// GRPCProviderPlugin implements plugin.GRPCPlugin for the go-plugin package.
|
||||||
type GRPCProviderPlugin struct {
|
type GRPCProviderPlugin struct {
|
||||||
plugin.Plugin
|
plugin.Plugin
|
||||||
@@ -75,11 +83,6 @@ type GRPCProvider struct {
|
|||||||
// used in an end to end test of a provider.
|
// used in an end to end test of a provider.
|
||||||
TestServer *grpc.Server
|
TestServer *grpc.Server
|
||||||
|
|
||||||
// Addr uniquely identifies the type of provider.
|
|
||||||
// Normally executed providers will have this set during initialization,
|
|
||||||
// but it may not always be available for alternative execute modes.
|
|
||||||
Addr addrs.Provider
|
|
||||||
|
|
||||||
// Proto client use to make the grpc service calls.
|
// Proto client use to make the grpc service calls.
|
||||||
client proto6.ProviderClient
|
client proto6.ProviderClient
|
||||||
|
|
||||||
@@ -94,53 +97,45 @@ type GRPCProvider struct {
|
|||||||
// to use as the parent context for gRPC API calls.
|
// to use as the parent context for gRPC API calls.
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
|
||||||
mu sync.Mutex
|
// SchemaCache stores the schema for this provider. This is used to properly
|
||||||
// schema stores the schema for this provider. This is used to properly
|
// serialize the requests for schemas. This is shared between instances
|
||||||
// serialize the requests for schemas.
|
// of the provider.
|
||||||
schema providers.GetProviderSchemaResponse
|
SchemaCache func(func() providers.GetProviderSchemaResponse) providers.GetProviderSchemaResponse
|
||||||
|
hasFetchedSchema bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ providers.Interface = new(GRPCProvider)
|
var _ providers.Interface = new(GRPCProvider)
|
||||||
|
|
||||||
func (p *GRPCProvider) GetProviderSchema(ctx context.Context) (resp providers.GetProviderSchemaResponse) {
|
func (p *GRPCProvider) GetProviderSchema(ctx context.Context) (resp providers.GetProviderSchemaResponse) {
|
||||||
logger.Trace("GRPCProvider.v6: GetProviderSchema")
|
logger.Trace("GRPCProvider.v6: GetProviderSchema")
|
||||||
p.mu.Lock()
|
|
||||||
defer p.mu.Unlock()
|
|
||||||
|
|
||||||
// First, we check the global cache.
|
// For testing only
|
||||||
// The cache could contain this schema if an instance of this provider has previously been started.
|
if p.SchemaCache == nil {
|
||||||
if !p.Addr.IsZero() {
|
return p.getProviderSchema(ctx)
|
||||||
// Even if the schema is cached, GetProviderSchemaOptional could be false. This would indicate that once instantiated,
|
|
||||||
// this provider requires the get schema call to be made at least once, as it handles part of the provider's setup.
|
|
||||||
// At this point, we don't know if this is the first call to a provider instance or not, so we don't use the result in that case.
|
|
||||||
if schemaCached, ok := providers.SchemaCache.Get(p.Addr); ok && schemaCached.ServerCapabilities.GetProviderSchemaOptional {
|
|
||||||
logger.Trace("GRPCProvider: GetProviderSchema: serving from global schema cache", "address", p.Addr)
|
|
||||||
return schemaCached
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the local cache is non-zero, we know this instance has called
|
schema := p.SchemaCache(func() providers.GetProviderSchemaResponse {
|
||||||
// GetProviderSchema at least once, so has satisfied the possible requirement of `GetProviderSchemaOptional=false`.
|
return p.getProviderSchema(ctx)
|
||||||
// This means that we can return early now using the locally cached schema, without making this call again.
|
})
|
||||||
if p.schema.Provider.Block != nil {
|
|
||||||
return p.schema
|
if !p.hasFetchedSchema && !schema.ServerCapabilities.GetProviderSchemaOptional {
|
||||||
|
// Force call
|
||||||
|
p.client.GetProviderSchema(ctx, new(proto6.GetProviderSchema_Request), grpc.MaxRecvMsgSizeCallOption{MaxRecvMsgSize: maxRecvSize})
|
||||||
|
p.hasFetchedSchema = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *GRPCProvider) getProviderSchema(ctx context.Context) (resp providers.GetProviderSchemaResponse) {
|
||||||
resp.ResourceTypes = make(map[string]providers.Schema)
|
resp.ResourceTypes = make(map[string]providers.Schema)
|
||||||
resp.DataSources = make(map[string]providers.Schema)
|
resp.DataSources = make(map[string]providers.Schema)
|
||||||
resp.EphemeralResources = make(map[string]providers.Schema)
|
resp.EphemeralResources = make(map[string]providers.Schema)
|
||||||
resp.Functions = make(map[string]providers.FunctionSpec)
|
resp.Functions = make(map[string]providers.FunctionSpec)
|
||||||
|
|
||||||
// Some providers may generate quite large schemas, and the internal default
|
|
||||||
// grpc response size limit is 4MB. 64MB should cover most any use case, and
|
|
||||||
// if we get providers nearing that we may want to consider a finer-grained
|
|
||||||
// API to fetch individual resource schemas.
|
|
||||||
// Note: this option is marked as EXPERIMENTAL in the grpc API. We keep
|
|
||||||
// this for compatibility, but recent providers all set the max message
|
|
||||||
// size much higher on the server side, which is the supported method for
|
|
||||||
// determining payload size.
|
|
||||||
const maxRecvSize = 64 << 20
|
|
||||||
protoResp, err := p.client.GetProviderSchema(ctx, new(proto6.GetProviderSchema_Request), grpc.MaxRecvMsgSizeCallOption{MaxRecvMsgSize: maxRecvSize})
|
protoResp, err := p.client.GetProviderSchema(ctx, new(proto6.GetProviderSchema_Request), grpc.MaxRecvMsgSizeCallOption{MaxRecvMsgSize: maxRecvSize})
|
||||||
|
p.hasFetchedSchema = true
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err))
|
resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err))
|
||||||
return resp
|
return resp
|
||||||
@@ -188,23 +183,6 @@ func (p *GRPCProvider) GetProviderSchema(ctx context.Context) (resp providers.Ge
|
|||||||
resp.ServerCapabilities.GetProviderSchemaOptional = protoResp.ServerCapabilities.GetProviderSchemaOptional
|
resp.ServerCapabilities.GetProviderSchemaOptional = protoResp.ServerCapabilities.GetProviderSchemaOptional
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the global provider cache so that future calls to this provider can use the cached value.
|
|
||||||
// Crucially, this doesn't look at GetProviderSchemaOptional, because the layers above could use this cache
|
|
||||||
// *without* creating an instance of this provider. And if there is no instance,
|
|
||||||
// then we don't need to set up anything (cause there is nothing to set up), so we need no call
|
|
||||||
// to the providers GetSchema rpc.
|
|
||||||
if !p.Addr.IsZero() {
|
|
||||||
providers.SchemaCache.Set(p.Addr, resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always store this here in the client for providers that are not able to use GetProviderSchemaOptional.
|
|
||||||
// Crucially, this indicates that we've made at least one call to GetProviderSchema to this instance of the provider,
|
|
||||||
// which means in the future we'll be able to return using this cache
|
|
||||||
// (because the possible setup contained in the GetProviderSchema call has happened).
|
|
||||||
// If GetProviderSchemaOptional is true then this cache won't actually ever be used, because the calls to this method
|
|
||||||
// will be satisfied by the global provider cache.
|
|
||||||
p.schema = resp
|
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -23,7 +24,6 @@ import (
|
|||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/addrs"
|
|
||||||
"github.com/opentofu/opentofu/internal/legacy/hcl2shim"
|
"github.com/opentofu/opentofu/internal/legacy/hcl2shim"
|
||||||
mockproto "github.com/opentofu/opentofu/internal/plugin6/mock_proto"
|
mockproto "github.com/opentofu/opentofu/internal/plugin6/mock_proto"
|
||||||
"github.com/opentofu/opentofu/internal/providers"
|
"github.com/opentofu/opentofu/internal/providers"
|
||||||
@@ -31,6 +31,23 @@ import (
|
|||||||
proto "github.com/opentofu/opentofu/internal/tfplugin6"
|
proto "github.com/opentofu/opentofu/internal/tfplugin6"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO this should probably live somewhere common
|
||||||
|
func providerSchemaCache() func(func() providers.ProviderSchema) providers.ProviderSchema {
|
||||||
|
var mu sync.Mutex
|
||||||
|
var schema providers.ProviderSchema
|
||||||
|
|
||||||
|
return func(getSchema func() providers.ProviderSchema) providers.ProviderSchema {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
if schema.Provider.Block != nil {
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
schema = getSchema()
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var _ providers.Interface = (*GRPCProvider)(nil)
|
var _ providers.Interface = (*GRPCProvider)(nil)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -243,13 +260,8 @@ func TestGRPCProvider_GetSchema_ResponseErrorDiagnostic(t *testing.T) {
|
|||||||
func TestGRPCProvider_GetSchema_GlobalCacheEnabled(t *testing.T) {
|
func TestGRPCProvider_GetSchema_GlobalCacheEnabled(t *testing.T) {
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
client := mockproto.NewMockProviderClient(ctrl)
|
client := mockproto.NewMockProviderClient(ctrl)
|
||||||
// The SchemaCache is global and is saved between test runs
|
|
||||||
providers.SchemaCache = providers.NewMockSchemaCache()
|
|
||||||
|
|
||||||
providerAddr := addrs.Provider{
|
cache := providerSchemaCache()
|
||||||
Namespace: "namespace",
|
|
||||||
Type: "type",
|
|
||||||
}
|
|
||||||
|
|
||||||
mockedProviderResponse := &proto.Schema{Version: 2, Block: &proto.Schema_Block{}}
|
mockedProviderResponse := &proto.Schema{Version: 2, Block: &proto.Schema_Block{}}
|
||||||
|
|
||||||
@@ -265,8 +277,8 @@ func TestGRPCProvider_GetSchema_GlobalCacheEnabled(t *testing.T) {
|
|||||||
// Run GetProviderTwice, expect GetSchema to be called once
|
// Run GetProviderTwice, expect GetSchema to be called once
|
||||||
// Re-initialize the provider before each run to avoid usage of the local cache
|
// Re-initialize the provider before each run to avoid usage of the local cache
|
||||||
p := &GRPCProvider{
|
p := &GRPCProvider{
|
||||||
client: client,
|
client: client,
|
||||||
Addr: providerAddr,
|
SchemaCache: cache,
|
||||||
}
|
}
|
||||||
resp := p.GetProviderSchema(t.Context())
|
resp := p.GetProviderSchema(t.Context())
|
||||||
|
|
||||||
@@ -276,8 +288,8 @@ func TestGRPCProvider_GetSchema_GlobalCacheEnabled(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
p = &GRPCProvider{
|
p = &GRPCProvider{
|
||||||
client: client,
|
client: client,
|
||||||
Addr: providerAddr,
|
SchemaCache: cache,
|
||||||
}
|
}
|
||||||
resp = p.GetProviderSchema(t.Context())
|
resp = p.GetProviderSchema(t.Context())
|
||||||
|
|
||||||
@@ -290,13 +302,6 @@ func TestGRPCProvider_GetSchema_GlobalCacheEnabled(t *testing.T) {
|
|||||||
func TestGRPCProvider_GetSchema_GlobalCacheDisabled(t *testing.T) {
|
func TestGRPCProvider_GetSchema_GlobalCacheDisabled(t *testing.T) {
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
client := mockproto.NewMockProviderClient(ctrl)
|
client := mockproto.NewMockProviderClient(ctrl)
|
||||||
// The SchemaCache is global and is saved between test runs
|
|
||||||
providers.SchemaCache = providers.NewMockSchemaCache()
|
|
||||||
|
|
||||||
providerAddr := addrs.Provider{
|
|
||||||
Namespace: "namespace",
|
|
||||||
Type: "type",
|
|
||||||
}
|
|
||||||
|
|
||||||
mockedProviderResponse := &proto.Schema{Version: 2, Block: &proto.Schema_Block{}}
|
mockedProviderResponse := &proto.Schema{Version: 2, Block: &proto.Schema_Block{}}
|
||||||
|
|
||||||
@@ -312,8 +317,8 @@ func TestGRPCProvider_GetSchema_GlobalCacheDisabled(t *testing.T) {
|
|||||||
// Run GetProviderTwice, expect GetSchema to be called once
|
// Run GetProviderTwice, expect GetSchema to be called once
|
||||||
// Re-initialize the provider before each run to avoid usage of the local cache
|
// Re-initialize the provider before each run to avoid usage of the local cache
|
||||||
p := &GRPCProvider{
|
p := &GRPCProvider{
|
||||||
client: client,
|
client: client,
|
||||||
Addr: providerAddr,
|
SchemaCache: providerSchemaCache(),
|
||||||
}
|
}
|
||||||
resp := p.GetProviderSchema(t.Context())
|
resp := p.GetProviderSchema(t.Context())
|
||||||
|
|
||||||
@@ -323,8 +328,8 @@ func TestGRPCProvider_GetSchema_GlobalCacheDisabled(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
p = &GRPCProvider{
|
p = &GRPCProvider{
|
||||||
client: client,
|
client: client,
|
||||||
Addr: providerAddr,
|
SchemaCache: providerSchemaCache(),
|
||||||
}
|
}
|
||||||
resp = p.GetProviderSchema(t.Context())
|
resp = p.GetProviderSchema(t.Context())
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
package providers
|
|
||||||
|
|
||||||
import "github.com/opentofu/opentofu/internal/addrs"
|
|
||||||
|
|
||||||
func NewMockSchemaCache() *schemaCache {
|
|
||||||
return &schemaCache{
|
|
||||||
m: make(map[addrs.Provider]ProviderSchema),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +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 providers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/addrs"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SchemaCache is a global cache of Schemas.
|
|
||||||
// This will be accessed by both core and the provider clients to ensure that
|
|
||||||
// large schemas are stored in a single location.
|
|
||||||
var SchemaCache = &schemaCache{
|
|
||||||
m: make(map[addrs.Provider]ProviderSchema),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Global cache for provider schemas
|
|
||||||
// Cache the entire response to ensure we capture any new fields, like
|
|
||||||
// ServerCapabilities. This also serves to capture errors so that multiple
|
|
||||||
// concurrent calls resulting in an error can be handled in the same manner.
|
|
||||||
type schemaCache struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
m map[addrs.Provider]ProviderSchema
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *schemaCache) Set(p addrs.Provider, s ProviderSchema) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
c.m[p] = s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *schemaCache) Get(p addrs.Provider) (ProviderSchema, bool) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
s, ok := c.m[p]
|
|
||||||
return s, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *schemaCache) Remove(p addrs.Provider) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
delete(c.m, p)
|
|
||||||
}
|
|
||||||
@@ -6,6 +6,8 @@
|
|||||||
package providers
|
package providers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/addrs"
|
"github.com/opentofu/opentofu/internal/addrs"
|
||||||
"github.com/opentofu/opentofu/internal/configs/configschema"
|
"github.com/opentofu/opentofu/internal/configs/configschema"
|
||||||
)
|
)
|
||||||
@@ -39,3 +41,48 @@ func (ss ProviderSchema) SchemaForResourceType(mode addrs.ResourceMode, typeName
|
|||||||
func (ss ProviderSchema) SchemaForResourceAddr(addr addrs.Resource) (schema *configschema.Block, version uint64) {
|
func (ss ProviderSchema) SchemaForResourceAddr(addr addrs.Resource) (schema *configschema.Block, version uint64) {
|
||||||
return ss.SchemaForResourceType(addr.Mode, addr.Type)
|
return ss.SchemaForResourceType(addr.Mode, addr.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (resp ProviderSchema) Validate(addr addrs.Provider) error {
|
||||||
|
if resp.Diagnostics.HasErrors() {
|
||||||
|
return 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 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 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 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 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 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 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 fmt.Errorf("provider %s has invalid negative schema version for ephemeral resource type %q, which is a bug in the provider", addr, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import (
|
|||||||
"github.com/opentofu/opentofu/internal/logging"
|
"github.com/opentofu/opentofu/internal/logging"
|
||||||
"github.com/opentofu/opentofu/internal/providers"
|
"github.com/opentofu/opentofu/internal/providers"
|
||||||
"github.com/opentofu/opentofu/internal/provisioners"
|
"github.com/opentofu/opentofu/internal/provisioners"
|
||||||
"github.com/opentofu/opentofu/internal/states"
|
|
||||||
"github.com/opentofu/opentofu/internal/tfdiags"
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -135,7 +134,11 @@ func NewContext(opts *ContextOpts) (*Context, tfdiags.Diagnostics) {
|
|||||||
par = 10
|
par = 10
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins := newContextPlugins(opts.Providers, opts.Provisioners)
|
plugins, pluginDiags := newContextPlugins(opts.Providers, opts.Provisioners)
|
||||||
|
diags = diags.Append(pluginDiags)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return nil, diags
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("[TRACE] tofu.NewContext: complete")
|
log.Printf("[TRACE] tofu.NewContext: complete")
|
||||||
|
|
||||||
@@ -154,19 +157,8 @@ func NewContext(opts *ContextOpts) (*Context, tfdiags.Diagnostics) {
|
|||||||
}, diags
|
}, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) Schemas(ctx context.Context, config *configs.Config, state *states.State) (*Schemas, tfdiags.Diagnostics) {
|
func (c *Context) Schemas() *Schemas {
|
||||||
var diags tfdiags.Diagnostics
|
return c.plugins.schemas
|
||||||
|
|
||||||
ret, err := loadSchemas(ctx, config, state, c.plugins)
|
|
||||||
if err != nil {
|
|
||||||
diags = diags.Append(tfdiags.Sourceless(
|
|
||||||
tfdiags.Error,
|
|
||||||
"Failed to load plugin schemas",
|
|
||||||
fmt.Sprintf("Error while loading schemas for plugin components: %s.", err),
|
|
||||||
))
|
|
||||||
return nil, diags
|
|
||||||
}
|
|
||||||
return ret, diags
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContextGraphOpts struct {
|
type ContextGraphOpts struct {
|
||||||
|
|||||||
@@ -471,12 +471,6 @@ variable "obfmod" {
|
|||||||
// Defaulted stub provider with non-custom function
|
// Defaulted stub provider with non-custom function
|
||||||
func TestContext2Functions_providerFunctionsStub(t *testing.T) {
|
func TestContext2Functions_providerFunctionsStub(t *testing.T) {
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
addr := addrs.ImpliedProviderForUnqualifiedType("aws")
|
|
||||||
|
|
||||||
// Explicitly non-parallel
|
|
||||||
t.Setenv("foo", "bar")
|
|
||||||
defer providers.SchemaCache.Remove(addr)
|
|
||||||
|
|
||||||
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
|
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
|
||||||
Functions: map[string]providers.FunctionSpec{
|
Functions: map[string]providers.FunctionSpec{
|
||||||
"arn_parse": providers.FunctionSpec{
|
"arn_parse": providers.FunctionSpec{
|
||||||
@@ -492,9 +486,6 @@ func TestContext2Functions_providerFunctionsStub(t *testing.T) {
|
|||||||
Result: cty.True,
|
Result: cty.True,
|
||||||
}
|
}
|
||||||
|
|
||||||
// SchemaCache is initialzed earlier on in the command package
|
|
||||||
providers.SchemaCache.Set(addr, *p.GetProviderSchemaResponse)
|
|
||||||
|
|
||||||
m := testModuleInline(t, map[string]string{
|
m := testModuleInline(t, map[string]string{
|
||||||
"main.tf": `
|
"main.tf": `
|
||||||
module "mod" {
|
module "mod" {
|
||||||
@@ -571,12 +562,6 @@ variable "obfmod" {
|
|||||||
// Defaulted stub provider with custom function (no allowed)
|
// Defaulted stub provider with custom function (no allowed)
|
||||||
func TestContext2Functions_providerFunctionsStubCustom(t *testing.T) {
|
func TestContext2Functions_providerFunctionsStubCustom(t *testing.T) {
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
addr := addrs.ImpliedProviderForUnqualifiedType("aws")
|
|
||||||
|
|
||||||
// Explicitly non-parallel
|
|
||||||
t.Setenv("foo", "bar")
|
|
||||||
defer providers.SchemaCache.Remove(addr)
|
|
||||||
|
|
||||||
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
|
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
|
||||||
Functions: map[string]providers.FunctionSpec{
|
Functions: map[string]providers.FunctionSpec{
|
||||||
"arn_parse": providers.FunctionSpec{
|
"arn_parse": providers.FunctionSpec{
|
||||||
@@ -592,9 +577,6 @@ func TestContext2Functions_providerFunctionsStubCustom(t *testing.T) {
|
|||||||
Result: cty.True,
|
Result: cty.True,
|
||||||
}
|
}
|
||||||
|
|
||||||
// SchemaCache is initialzed earlier on in the command package
|
|
||||||
providers.SchemaCache.Set(addr, *p.GetProviderSchemaResponse)
|
|
||||||
|
|
||||||
m := testModuleInline(t, map[string]string{
|
m := testModuleInline(t, map[string]string{
|
||||||
"main.tf": `
|
"main.tf": `
|
||||||
module "mod" {
|
module "mod" {
|
||||||
@@ -655,12 +637,6 @@ variable "obfmod" {
|
|||||||
// Defaulted stub provider
|
// Defaulted stub provider
|
||||||
func TestContext2Functions_providerFunctionsForEachCount(t *testing.T) {
|
func TestContext2Functions_providerFunctionsForEachCount(t *testing.T) {
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
addr := addrs.ImpliedProviderForUnqualifiedType("aws")
|
|
||||||
|
|
||||||
// Explicitly non-parallel
|
|
||||||
t.Setenv("foo", "bar")
|
|
||||||
defer providers.SchemaCache.Remove(addr)
|
|
||||||
|
|
||||||
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
|
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
|
||||||
Functions: map[string]providers.FunctionSpec{
|
Functions: map[string]providers.FunctionSpec{
|
||||||
"arn_parse": providers.FunctionSpec{
|
"arn_parse": providers.FunctionSpec{
|
||||||
@@ -676,9 +652,6 @@ func TestContext2Functions_providerFunctionsForEachCount(t *testing.T) {
|
|||||||
Result: cty.True,
|
Result: cty.True,
|
||||||
}
|
}
|
||||||
|
|
||||||
// SchemaCache is initialzed earlier on in the command package
|
|
||||||
providers.SchemaCache.Set(addr, *p.GetProviderSchemaResponse)
|
|
||||||
|
|
||||||
m := testModuleInline(t, map[string]string{
|
m := testModuleInline(t, map[string]string{
|
||||||
"main.tf": `
|
"main.tf": `
|
||||||
provider "aws" {
|
provider "aws" {
|
||||||
|
|||||||
@@ -56,11 +56,7 @@ func (c *Context) Input(ctx context.Context, config *configs.Config, mode InputM
|
|||||||
)
|
)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
schemas, moreDiags := c.Schemas(ctx, config, nil)
|
schemas := c.Schemas()
|
||||||
diags = diags.Append(moreDiags)
|
|
||||||
if moreDiags.HasErrors() {
|
|
||||||
return diags
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.uiInput == nil {
|
if c.uiInput == nil {
|
||||||
log.Printf("[TRACE] Context.Input: uiInput is nil, so skipping")
|
log.Printf("[TRACE] Context.Input: uiInput is nil, so skipping")
|
||||||
|
|||||||
@@ -280,9 +280,7 @@ The -target and -exclude options are not for routine use, and are provided only
|
|||||||
}
|
}
|
||||||
|
|
||||||
if plan != nil {
|
if plan != nil {
|
||||||
relevantAttrs, rDiags := c.relevantResourceAttrsForPlan(ctx, config, plan)
|
plan.RelevantAttributes = c.relevantResourceAttrsForPlan(ctx, config, plan)
|
||||||
diags = diags.Append(rDiags)
|
|
||||||
plan.RelevantAttributes = relevantAttrs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
@@ -487,10 +485,7 @@ func (c *Context) destroyPlan(ctx context.Context, config *configs.Config, prevR
|
|||||||
destroyPlan.PrevRunState = prevRunState
|
destroyPlan.PrevRunState = prevRunState
|
||||||
}
|
}
|
||||||
|
|
||||||
relevantAttrs, rDiags := c.relevantResourceAttrsForPlan(ctx, config, destroyPlan)
|
destroyPlan.RelevantAttributes = c.relevantResourceAttrsForPlan(ctx, config, destroyPlan)
|
||||||
diags = diags.Append(rDiags)
|
|
||||||
|
|
||||||
destroyPlan.RelevantAttributes = relevantAttrs
|
|
||||||
return destroyPlan, diags
|
return destroyPlan, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -931,11 +926,7 @@ func (c *Context) driftedResources(ctx context.Context, config *configs.Config,
|
|||||||
return nil, diags
|
return nil, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
schemas, schemaDiags := c.Schemas(ctx, config, newState)
|
schemas := c.Schemas()
|
||||||
diags = diags.Append(schemaDiags)
|
|
||||||
if diags.HasErrors() {
|
|
||||||
return nil, diags
|
|
||||||
}
|
|
||||||
|
|
||||||
var drs []*plans.ResourceInstanceChangeSrc
|
var drs []*plans.ResourceInstanceChangeSrc
|
||||||
|
|
||||||
@@ -1108,21 +1099,14 @@ func blockedMovesWarningDiag(results refactoring.MoveResults) tfdiags.Diagnostic
|
|||||||
// referenceAnalyzer returns a globalref.Analyzer object to help with
|
// referenceAnalyzer returns a globalref.Analyzer object to help with
|
||||||
// global analysis of references within the configuration that's attached
|
// global analysis of references within the configuration that's attached
|
||||||
// to the receiving context.
|
// to the receiving context.
|
||||||
func (c *Context) referenceAnalyzer(ctx context.Context, config *configs.Config, state *states.State) (*globalref.Analyzer, tfdiags.Diagnostics) {
|
func (c *Context) referenceAnalyzer(ctx context.Context, config *configs.Config) *globalref.Analyzer {
|
||||||
schemas, diags := c.Schemas(ctx, config, state)
|
return globalref.NewAnalyzer(config, c.Schemas().Providers)
|
||||||
if diags.HasErrors() {
|
|
||||||
return nil, diags
|
|
||||||
}
|
|
||||||
return globalref.NewAnalyzer(config, schemas.Providers), diags
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// relevantResourceAttrsForPlan implements the heuristic we use to populate the
|
// relevantResourceAttrsForPlan implements the heuristic we use to populate the
|
||||||
// RelevantResources field of returned plans.
|
// RelevantResources field of returned plans.
|
||||||
func (c *Context) relevantResourceAttrsForPlan(ctx context.Context, config *configs.Config, plan *plans.Plan) ([]globalref.ResourceAttr, tfdiags.Diagnostics) {
|
func (c *Context) relevantResourceAttrsForPlan(ctx context.Context, config *configs.Config, plan *plans.Plan) []globalref.ResourceAttr {
|
||||||
azr, diags := c.referenceAnalyzer(ctx, config, plan.PriorState)
|
azr := c.referenceAnalyzer(ctx, config)
|
||||||
if diags.HasErrors() {
|
|
||||||
return nil, diags
|
|
||||||
}
|
|
||||||
|
|
||||||
var refs []globalref.Reference
|
var refs []globalref.Reference
|
||||||
for _, change := range plan.Changes.Resources {
|
for _, change := range plan.Changes.Resources {
|
||||||
@@ -1151,7 +1135,7 @@ func (c *Context) relevantResourceAttrsForPlan(ctx context.Context, config *conf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return contributors, diags
|
return contributors
|
||||||
}
|
}
|
||||||
|
|
||||||
// warnOnUsedDeprecatedVars is checking for variables whose values are given by the user and if any of that is
|
// warnOnUsedDeprecatedVars is checking for variables whose values are given by the user and if any of that is
|
||||||
|
|||||||
@@ -6760,11 +6760,6 @@ func TestContext2Plan_importIdInvalidNull(t *testing.T) {
|
|||||||
func TestContext2Plan_importIdInvalidUnknown(t *testing.T) {
|
func TestContext2Plan_importIdInvalidUnknown(t *testing.T) {
|
||||||
p := testProvider("test")
|
p := testProvider("test")
|
||||||
m := testModule(t, "import-id-invalid-unknown")
|
m := testModule(t, "import-id-invalid-unknown")
|
||||||
ctx := testContext2(t, &ContextOpts{
|
|
||||||
Providers: map[addrs.Provider]providers.Factory{
|
|
||||||
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
||||||
ResourceTypes: map[string]*configschema.Block{
|
ResourceTypes: map[string]*configschema.Block{
|
||||||
"test_resource": {
|
"test_resource": {
|
||||||
@@ -6794,6 +6789,11 @@ func TestContext2Plan_importIdInvalidUnknown(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
ctx := testContext2(t, &ContextOpts{
|
||||||
|
Providers: map[addrs.Provider]providers.Factory{
|
||||||
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
_, diags := ctx.Plan(context.Background(), m, states.NewState(), DefaultPlanOpts)
|
_, diags := ctx.Plan(context.Background(), m, states.NewState(), DefaultPlanOpts)
|
||||||
if !diags.HasErrors() {
|
if !diags.HasErrors() {
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ package tofu
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/addrs"
|
"github.com/opentofu/opentofu/internal/addrs"
|
||||||
"github.com/opentofu/opentofu/internal/configs/configschema"
|
"github.com/opentofu/opentofu/internal/configs/configschema"
|
||||||
"github.com/opentofu/opentofu/internal/providers"
|
"github.com/opentofu/opentofu/internal/providers"
|
||||||
"github.com/opentofu/opentofu/internal/provisioners"
|
"github.com/opentofu/opentofu/internal/provisioners"
|
||||||
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||||
)
|
)
|
||||||
|
|
||||||
// contextPlugins represents a library of available plugins (providers and
|
// contextPlugins represents a library of available plugins (providers and
|
||||||
@@ -23,13 +23,24 @@ import (
|
|||||||
type contextPlugins struct {
|
type contextPlugins struct {
|
||||||
providerFactories map[addrs.Provider]providers.Factory
|
providerFactories map[addrs.Provider]providers.Factory
|
||||||
provisionerFactories map[string]provisioners.Factory
|
provisionerFactories map[string]provisioners.Factory
|
||||||
|
schemas *Schemas
|
||||||
}
|
}
|
||||||
|
|
||||||
func newContextPlugins(providerFactories map[addrs.Provider]providers.Factory, provisionerFactories map[string]provisioners.Factory) *contextPlugins {
|
func newContextPlugins(providerFactories map[addrs.Provider]providers.Factory, provisionerFactories map[string]provisioners.Factory) (*contextPlugins, tfdiags.Diagnostics) {
|
||||||
|
schemas, err := loadSchemas(context.TODO(), providerFactories, provisionerFactories)
|
||||||
|
if err != nil {
|
||||||
|
return nil, tfdiags.Diagnostics{}.Append(tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
"Failed to load plugin schemas",
|
||||||
|
fmt.Sprintf("Error while loading schemas for plugin components: %s.", err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
return &contextPlugins{
|
return &contextPlugins{
|
||||||
providerFactories: providerFactories,
|
providerFactories: providerFactories,
|
||||||
provisionerFactories: provisionerFactories,
|
provisionerFactories: provisionerFactories,
|
||||||
}
|
schemas: schemas,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *contextPlugins) HasProvider(addr addrs.Provider) bool {
|
func (cp *contextPlugins) HasProvider(addr addrs.Provider) bool {
|
||||||
@@ -67,81 +78,20 @@ func (cp *contextPlugins) NewProvisionerInstance(typ string) (provisioners.Inter
|
|||||||
// ProviderSchema memoizes results by unique provider address, so it's fine
|
// ProviderSchema memoizes results by unique provider address, so it's fine
|
||||||
// to repeatedly call this method with the same address if various different
|
// to repeatedly call this method with the same address if various different
|
||||||
// parts of OpenTofu all need the same schema information.
|
// parts of OpenTofu all need the same schema information.
|
||||||
func (cp *contextPlugins) ProviderSchema(ctx context.Context, addr addrs.Provider) (providers.ProviderSchema, error) {
|
func (cp *contextPlugins) ProviderSchema(addr addrs.Provider) (providers.ProviderSchema, error) {
|
||||||
// Check the global schema cache first.
|
schema, ok := cp.schemas.Providers[addr]
|
||||||
// This cache is only written by the provider client, and transparently
|
if !ok {
|
||||||
// used by GetProviderSchema, but we check it here because at this point we
|
return schema, fmt.Errorf("unavailable provider %q", addr.String())
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
return schema, 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
|
// ProviderConfigSchema is a helper wrapper around ProviderSchema which first
|
||||||
// reads the full schema of the given provider and then extracts just the
|
// 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's configuration schema, which defines what's expected in a
|
||||||
// "provider" block in the configuration when configuring this provider.
|
// "provider" block in the configuration when configuring this provider.
|
||||||
func (cp *contextPlugins) ProviderConfigSchema(ctx context.Context, providerAddr addrs.Provider) (*configschema.Block, error) {
|
func (cp *contextPlugins) ProviderConfigSchema(providerAddr addrs.Provider) (*configschema.Block, error) {
|
||||||
providerSchema, err := cp.ProviderSchema(ctx, providerAddr)
|
providerSchema, err := cp.ProviderSchema(providerAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -160,8 +110,8 @@ func (cp *contextPlugins) ProviderConfigSchema(ctx context.Context, providerAddr
|
|||||||
// Managed resource types have versioned schemas, so the second return value
|
// Managed resource types have versioned schemas, so the second return value
|
||||||
// is the current schema version number for the requested resource. The version
|
// is the current schema version number for the requested resource. The version
|
||||||
// is irrelevant for other resource modes.
|
// 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) {
|
func (cp *contextPlugins) ResourceTypeSchema(providerAddr addrs.Provider, resourceMode addrs.ResourceMode, resourceType string) (*configschema.Block, uint64, error) {
|
||||||
providerSchema, err := cp.ProviderSchema(ctx, providerAddr)
|
providerSchema, err := cp.ProviderSchema(providerAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
@@ -177,17 +127,9 @@ func (cp *contextPlugins) ResourceTypeSchema(ctx context.Context, providerAddr a
|
|||||||
// to repeatedly call this method with the same name if various different
|
// to repeatedly call this method with the same name if various different
|
||||||
// parts of OpenTofu all need the same schema information.
|
// parts of OpenTofu all need the same schema information.
|
||||||
func (cp *contextPlugins) ProvisionerSchema(typ string) (*configschema.Block, error) {
|
func (cp *contextPlugins) ProvisionerSchema(typ string) (*configschema.Block, error) {
|
||||||
log.Printf("[TRACE] tofu.contextPlugins: Initializing provisioner %q to read its schema", typ)
|
schema, ok := cp.schemas.Provisioners[typ]
|
||||||
provisioner, err := cp.NewProvisionerInstance(typ)
|
if !ok {
|
||||||
if err != nil {
|
return schema, fmt.Errorf("unavailable provisioner %q", typ)
|
||||||
return nil, fmt.Errorf("failed to instantiate provisioner %q to obtain schema: %w", typ, err)
|
|
||||||
}
|
}
|
||||||
defer provisioner.Close()
|
return schema, nil
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,17 +31,16 @@ func simpleMockPluginLibrary() *contextPlugins {
|
|||||||
// factory into real code under test.
|
// factory into real code under test.
|
||||||
provider := simpleMockProvider()
|
provider := simpleMockProvider()
|
||||||
provisioner := simpleMockProvisioner()
|
provisioner := simpleMockProvisioner()
|
||||||
ret := &contextPlugins{
|
ret, diags := newContextPlugins(map[addrs.Provider]providers.Factory{
|
||||||
providerFactories: map[addrs.Provider]providers.Factory{
|
addrs.NewDefaultProvider("test"): func() (providers.Interface, error) {
|
||||||
addrs.NewDefaultProvider("test"): func() (providers.Interface, error) {
|
return provider, nil
|
||||||
return provider, nil
|
}}, map[string]provisioners.Factory{
|
||||||
},
|
"test": func() (provisioners.Interface, error) {
|
||||||
},
|
return provisioner, nil
|
||||||
provisionerFactories: map[string]provisioners.Factory{
|
}},
|
||||||
"test": func() (provisioners.Interface, error) {
|
)
|
||||||
return provisioner, nil
|
if diags.HasErrors() {
|
||||||
},
|
panic(diags.Err())
|
||||||
},
|
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ func (c *BuiltinEvalContext) Provider(_ context.Context, addr addrs.AbsProviderC
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *BuiltinEvalContext) ProviderSchema(ctx context.Context, addr addrs.AbsProviderConfig) (providers.ProviderSchema, error) {
|
func (c *BuiltinEvalContext) ProviderSchema(ctx context.Context, addr addrs.AbsProviderConfig) (providers.ProviderSchema, error) {
|
||||||
return c.Plugins.ProviderSchema(ctx, addr.Provider)
|
return c.Plugins.ProviderSchema(addr.Provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *BuiltinEvalContext) CloseProvider(ctx context.Context, addr addrs.AbsProviderConfig) error {
|
func (c *BuiltinEvalContext) CloseProvider(ctx context.Context, addr addrs.AbsProviderConfig) error {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/addrs"
|
"github.com/opentofu/opentofu/internal/addrs"
|
||||||
"github.com/opentofu/opentofu/internal/providers"
|
"github.com/opentofu/opentofu/internal/providers"
|
||||||
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -64,9 +65,13 @@ func TestBuildingEvalContextInitProvider(t *testing.T) {
|
|||||||
ctx = ctx.WithPath(addrs.RootModuleInstance).(*BuiltinEvalContext)
|
ctx = ctx.WithPath(addrs.RootModuleInstance).(*BuiltinEvalContext)
|
||||||
ctx.ProviderLock = &lock
|
ctx.ProviderLock = &lock
|
||||||
ctx.ProviderCache = make(map[string]map[addrs.InstanceKey]providers.Interface)
|
ctx.ProviderCache = make(map[string]map[addrs.InstanceKey]providers.Interface)
|
||||||
ctx.Plugins = newContextPlugins(map[addrs.Provider]providers.Factory{
|
var diags tfdiags.Diagnostics
|
||||||
|
ctx.Plugins, diags = newContextPlugins(map[addrs.Provider]providers.Factory{
|
||||||
addrs.NewDefaultProvider("test"): providers.FactoryFixed(testP),
|
addrs.NewDefaultProvider("test"): providers.FactoryFixed(testP),
|
||||||
}, nil)
|
}, nil)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
t.Fatal(diags.Err())
|
||||||
|
}
|
||||||
|
|
||||||
providerAddrDefault := addrs.AbsProviderConfig{
|
providerAddrDefault := addrs.AbsProviderConfig{
|
||||||
Module: addrs.RootModule,
|
Module: addrs.RootModule,
|
||||||
|
|||||||
@@ -1015,7 +1015,7 @@ 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 {
|
func (d *evaluationStateData) getResourceSchema(ctx context.Context, addr addrs.Resource, providerAddr addrs.Provider) *configschema.Block {
|
||||||
// TODO: Plumb a useful context.Context through to here.
|
// TODO: Plumb a useful context.Context through to here.
|
||||||
schema, _, err := d.Evaluator.Plugins.ResourceTypeSchema(ctx, providerAddr, addr.Mode, addr.Type)
|
schema, _, err := d.Evaluator.Plugins.ResourceTypeSchema(providerAddr, addr.Mode, addr.Type)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// We have plenty of other codepaths that will detect and report
|
// We have plenty of other codepaths that will detect and report
|
||||||
// schema lookup errors before we'd reach this point, so we'll just
|
// schema lookup errors before we'd reach this point, so we'll just
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ func (d *evaluationStateData) staticValidateResourceReference(ctx context.Contex
|
|||||||
|
|
||||||
// TODO: Plugin a suitable context.Context through to here.
|
// TODO: Plugin a suitable context.Context through to here.
|
||||||
providerFqn := modCfg.Module.ProviderForLocalConfig(cfg.ProviderConfigAddr())
|
providerFqn := modCfg.Module.ProviderForLocalConfig(cfg.ProviderConfigAddr())
|
||||||
schema, _, err := d.Evaluator.Plugins.ResourceTypeSchema(ctx, providerFqn, addr.Mode, addr.Type)
|
schema, _, err := d.Evaluator.Plugins.ResourceTypeSchema(providerFqn, addr.Mode, addr.Type)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Prior validation should've taken care of a schema lookup error,
|
// 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
|
// so we should never get here but we'll handle it here anyway for
|
||||||
|
|||||||
@@ -780,9 +780,12 @@ func TestApplyGraphBuilder_withChecks(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins := newContextPlugins(map[addrs.Provider]providers.Factory{
|
plugins, diags := newContextPlugins(map[addrs.Provider]providers.Factory{
|
||||||
addrs.NewDefaultProvider("aws"): providers.FactoryFixed(awsProvider),
|
addrs.NewDefaultProvider("aws"): providers.FactoryFixed(awsProvider),
|
||||||
}, nil)
|
}, nil)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
t.Fatal(diags.Err())
|
||||||
|
}
|
||||||
|
|
||||||
b := &ApplyGraphBuilder{
|
b := &ApplyGraphBuilder{
|
||||||
Config: testModule(t, "apply-with-checks"),
|
Config: testModule(t, "apply-with-checks"),
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/opentofu/opentofu/internal/configs"
|
"github.com/opentofu/opentofu/internal/configs"
|
||||||
"github.com/opentofu/opentofu/internal/states"
|
"github.com/opentofu/opentofu/internal/states"
|
||||||
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/addrs"
|
"github.com/opentofu/opentofu/internal/addrs"
|
||||||
@@ -35,10 +36,13 @@ func TestPlanGraphBuilder(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
openstackProvider := mockProviderWithResourceTypeSchema("openstack_floating_ip", simpleTestSchema())
|
openstackProvider := mockProviderWithResourceTypeSchema("openstack_floating_ip", simpleTestSchema())
|
||||||
plugins := newContextPlugins(map[addrs.Provider]providers.Factory{
|
plugins, diags := newContextPlugins(map[addrs.Provider]providers.Factory{
|
||||||
addrs.NewDefaultProvider("aws"): providers.FactoryFixed(awsProvider),
|
addrs.NewDefaultProvider("aws"): providers.FactoryFixed(awsProvider),
|
||||||
addrs.NewDefaultProvider("openstack"): providers.FactoryFixed(openstackProvider),
|
addrs.NewDefaultProvider("openstack"): providers.FactoryFixed(openstackProvider),
|
||||||
}, nil)
|
}, nil)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
t.Fatal(diags.Err())
|
||||||
|
}
|
||||||
|
|
||||||
b := &PlanGraphBuilder{
|
b := &PlanGraphBuilder{
|
||||||
Config: testModule(t, "graph-builder-plan-basic"),
|
Config: testModule(t, "graph-builder-plan-basic"),
|
||||||
@@ -79,9 +83,12 @@ func TestPlanGraphBuilder_dynamicBlock(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
plugins := newContextPlugins(map[addrs.Provider]providers.Factory{
|
plugins, diags := newContextPlugins(map[addrs.Provider]providers.Factory{
|
||||||
addrs.NewDefaultProvider("test"): providers.FactoryFixed(provider),
|
addrs.NewDefaultProvider("test"): providers.FactoryFixed(provider),
|
||||||
}, nil)
|
}, nil)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
t.Fatal(diags.Err())
|
||||||
|
}
|
||||||
|
|
||||||
b := &PlanGraphBuilder{
|
b := &PlanGraphBuilder{
|
||||||
Config: testModule(t, "graph-builder-plan-dynblock"),
|
Config: testModule(t, "graph-builder-plan-dynblock"),
|
||||||
@@ -135,9 +142,12 @@ func TestPlanGraphBuilder_attrAsBlocks(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
plugins := newContextPlugins(map[addrs.Provider]providers.Factory{
|
plugins, diags := newContextPlugins(map[addrs.Provider]providers.Factory{
|
||||||
addrs.NewDefaultProvider("test"): providers.FactoryFixed(provider),
|
addrs.NewDefaultProvider("test"): providers.FactoryFixed(provider),
|
||||||
}, nil)
|
}, nil)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
t.Fatal(diags.Err())
|
||||||
|
}
|
||||||
|
|
||||||
b := &PlanGraphBuilder{
|
b := &PlanGraphBuilder{
|
||||||
Config: testModule(t, "graph-builder-plan-attr-as-blocks"),
|
Config: testModule(t, "graph-builder-plan-attr-as-blocks"),
|
||||||
@@ -221,9 +231,12 @@ func TestPlanGraphBuilder_excludeModule(t *testing.T) {
|
|||||||
func TestPlanGraphBuilder_forEach(t *testing.T) {
|
func TestPlanGraphBuilder_forEach(t *testing.T) {
|
||||||
awsProvider := mockProviderWithResourceTypeSchema("aws_instance", simpleTestSchema())
|
awsProvider := mockProviderWithResourceTypeSchema("aws_instance", simpleTestSchema())
|
||||||
|
|
||||||
plugins := newContextPlugins(map[addrs.Provider]providers.Factory{
|
plugins, diags := newContextPlugins(map[addrs.Provider]providers.Factory{
|
||||||
addrs.NewDefaultProvider("aws"): providers.FactoryFixed(awsProvider),
|
addrs.NewDefaultProvider("aws"): providers.FactoryFixed(awsProvider),
|
||||||
}, nil)
|
}, nil)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
t.Fatal(diags.Err())
|
||||||
|
}
|
||||||
|
|
||||||
b := &PlanGraphBuilder{
|
b := &PlanGraphBuilder{
|
||||||
Config: testModule(t, "plan-for-each"),
|
Config: testModule(t, "plan-for-each"),
|
||||||
@@ -256,9 +269,6 @@ func TestPlanGraphBuilder_ephemeralResourceDestroy(t *testing.T) {
|
|||||||
b := &PlanGraphBuilder{
|
b := &PlanGraphBuilder{
|
||||||
Config: &configs.Config{Module: &configs.Module{}},
|
Config: &configs.Config{Module: &configs.Module{}},
|
||||||
Operation: walkPlanDestroy,
|
Operation: walkPlanDestroy,
|
||||||
Plugins: newContextPlugins(map[addrs.Provider]providers.Factory{
|
|
||||||
addrs.NewDefaultProvider("aws"): providers.FactoryFixed(awsProvider),
|
|
||||||
}, nil),
|
|
||||||
State: &states.State{
|
State: &states.State{
|
||||||
Modules: map[string]*states.Module{
|
Modules: map[string]*states.Module{
|
||||||
"": {
|
"": {
|
||||||
@@ -280,6 +290,14 @@ func TestPlanGraphBuilder_ephemeralResourceDestroy(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var diags tfdiags.Diagnostics
|
||||||
|
b.Plugins, diags = newContextPlugins(map[addrs.Provider]providers.Factory{
|
||||||
|
addrs.NewDefaultProvider("aws"): providers.FactoryFixed(awsProvider),
|
||||||
|
}, nil)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
t.Fatal(diags.Err())
|
||||||
|
}
|
||||||
|
|
||||||
g, err := b.Build(t.Context(), addrs.RootModuleInstance)
|
g, err := b.Build(t.Context(), addrs.RootModuleInstance)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
@@ -304,7 +322,7 @@ func TestPlanGraphBuilder_ephemeralResourceDestroy(t *testing.T) {
|
|||||||
evalCtx := &MockEvalContext{
|
evalCtx := &MockEvalContext{
|
||||||
ProviderProvider: testProvider("aws"),
|
ProviderProvider: testProvider("aws"),
|
||||||
}
|
}
|
||||||
diags := found.Execute(t.Context(), evalCtx, walkPlanDestroy)
|
diags = found.Execute(t.Context(), evalCtx, walkPlanDestroy)
|
||||||
got := diags.Err().Error()
|
got := diags.Err().Error()
|
||||||
want := `An ephemeral resource planned for destroy: A destroy operation has been planned for the ephemeral resource "ephemeral.aws_secretmanager_secret.test". This is an OpenTofu error. Please report this.`
|
want := `An ephemeral resource planned for destroy: A destroy operation has been planned for the ephemeral resource "ephemeral.aws_secretmanager_secret.test". This is an OpenTofu error. Please report this.`
|
||||||
if got != want {
|
if got != want {
|
||||||
|
|||||||
@@ -12,10 +12,9 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/addrs"
|
"github.com/opentofu/opentofu/internal/addrs"
|
||||||
"github.com/opentofu/opentofu/internal/configs"
|
|
||||||
"github.com/opentofu/opentofu/internal/configs/configschema"
|
"github.com/opentofu/opentofu/internal/configs/configschema"
|
||||||
"github.com/opentofu/opentofu/internal/providers"
|
"github.com/opentofu/opentofu/internal/providers"
|
||||||
"github.com/opentofu/opentofu/internal/states"
|
"github.com/opentofu/opentofu/internal/provisioners"
|
||||||
"github.com/opentofu/opentofu/internal/tfdiags"
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -71,13 +70,13 @@ func (ss *Schemas) ProvisionerConfig(name string) *configschema.Block {
|
|||||||
// either misbehavior on the part of one of the providers or of the provider
|
// 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
|
// protocol itself. When returned with errors, the returned schemas object is
|
||||||
// still valid but may be incomplete.
|
// 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, providerFactories map[addrs.Provider]providers.Factory, provisionerFactories map[string]provisioners.Factory) (*Schemas, error) {
|
||||||
var diags tfdiags.Diagnostics
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
provisioners, provisionerDiags := loadProvisionerSchemas(ctx, config, plugins)
|
provisioners, provisionerDiags := loadProvisionerSchemas(ctx, provisionerFactories)
|
||||||
diags = diags.Append(provisionerDiags)
|
diags = diags.Append(provisionerDiags)
|
||||||
|
|
||||||
providers, providerDiags := loadProviderSchemas(ctx, config, state, plugins)
|
providers, providerDiags := loadProviderSchemas(ctx, providerFactories)
|
||||||
diags = diags.Append(providerDiags)
|
diags = diags.Append(providerDiags)
|
||||||
|
|
||||||
return &Schemas{
|
return &Schemas{
|
||||||
@@ -86,80 +85,75 @@ func loadSchemas(ctx context.Context, config *configs.Config, state *states.Stat
|
|||||||
}, diags.Err()
|
}, 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, providerFactories map[addrs.Provider]providers.Factory) (map[addrs.Provider]providers.ProviderSchema, tfdiags.Diagnostics) {
|
||||||
var diags tfdiags.Diagnostics
|
var lock sync.Mutex
|
||||||
|
|
||||||
schemas := map[addrs.Provider]providers.ProviderSchema{}
|
schemas := map[addrs.Provider]providers.ProviderSchema{}
|
||||||
|
var diags tfdiags.Diagnostics
|
||||||
if config != nil {
|
|
||||||
for _, fqn := range config.ProviderTypes() {
|
|
||||||
schemas[fqn] = providers.ProviderSchema{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if state != nil {
|
|
||||||
needed := providers.AddressedTypesAbs(state.ProviderAddrs())
|
|
||||||
for _, fqn := range needed {
|
|
||||||
schemas[fqn] = providers.ProviderSchema{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
var lock sync.Mutex
|
for fqn, factory := range providerFactories {
|
||||||
lock.Lock() // Prevent anything from started until we have finished schema map reads
|
|
||||||
for fqn := range schemas {
|
|
||||||
wg.Go(func() {
|
wg.Go(func() {
|
||||||
log.Printf("[TRACE] LoadSchemas: retrieving schema for provider type %q", fqn.String())
|
log.Printf("[TRACE] loadProviderSchemas: retrieving schema for provider type %q", fqn.String())
|
||||||
schema, err := plugins.ProviderSchema(ctx, fqn)
|
|
||||||
|
// Heavy lifting
|
||||||
|
schema, err := func() (providers.ProviderSchema, error) {
|
||||||
|
log.Printf("[TRACE] loadProviderSchemas: Initializing provider %q to read its schema", fqn)
|
||||||
|
provider, err := factory()
|
||||||
|
if err != nil {
|
||||||
|
return providers.ProviderSchema{}, fmt.Errorf("failed to instantiate provider %q to obtain schema: %w", fqn, err)
|
||||||
|
}
|
||||||
|
defer provider.Close(ctx)
|
||||||
|
|
||||||
|
resp := providers.ProviderSchema(provider.GetProviderSchema(ctx))
|
||||||
|
if resp.Diagnostics.HasErrors() {
|
||||||
|
return resp, fmt.Errorf("failed to retrieve schema from provider %q: %w", fqn, resp.Diagnostics.Err())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := resp.Validate(fqn); err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}()
|
||||||
|
|
||||||
// Ensure that we don't race on diags or schemas now that the hard work is done
|
// Ensure that we don't race on diags or schemas now that the hard work is done
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
defer lock.Unlock()
|
defer lock.Unlock()
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
diags = diags.Append(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
schemas[fqn] = schema
|
schemas[fqn] = schema
|
||||||
|
diags = diags.Append(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow execution to start now that reading of schemas map has completed
|
|
||||||
lock.Unlock()
|
|
||||||
|
|
||||||
// Wait for all of the scheduled routines to complete
|
// Wait for all of the scheduled routines to complete
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
return schemas, diags
|
return schemas, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadProvisionerSchemas(ctx context.Context, config *configs.Config, plugins *contextPlugins) (map[string]*configschema.Block, tfdiags.Diagnostics) {
|
func loadProvisionerSchemas(ctx context.Context, provisioners map[string]provisioners.Factory) (map[string]*configschema.Block, tfdiags.Diagnostics) {
|
||||||
var diags tfdiags.Diagnostics
|
var diags tfdiags.Diagnostics
|
||||||
schemas := map[string]*configschema.Block{}
|
schemas := map[string]*configschema.Block{}
|
||||||
|
|
||||||
// Determine the full list of provisioners recursively
|
|
||||||
var addProvisionersToSchema func(config *configs.Config)
|
|
||||||
addProvisionersToSchema = func(config *configs.Config) {
|
|
||||||
if config == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, rc := range config.Module.ManagedResources {
|
|
||||||
for _, pc := range rc.Managed.Provisioners {
|
|
||||||
schemas[pc.Type] = &configschema.Block{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must also visit our child modules, recursively.
|
|
||||||
for _, cc := range config.Children {
|
|
||||||
addProvisionersToSchema(cc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addProvisionersToSchema(config)
|
|
||||||
|
|
||||||
// Populate the schema entries
|
// Populate the schema entries
|
||||||
for name := range schemas {
|
for name, factory := range provisioners {
|
||||||
log.Printf("[TRACE] LoadSchemas: retrieving schema for provisioner %q", name)
|
log.Printf("[TRACE] loadProvisionerSchemas: retrieving schema for provisioner %q", name)
|
||||||
schema, err := plugins.ProvisionerSchema(name)
|
|
||||||
|
schema, err := func() (*configschema.Block, error) {
|
||||||
|
log.Printf("[TRACE] loadProvisionerSchemas: Initializing provisioner %q to read its schema", name)
|
||||||
|
provisioner, err := factory()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to instantiate provisioner %q to obtain schema: %w", name, err)
|
||||||
|
}
|
||||||
|
defer provisioner.Close()
|
||||||
|
|
||||||
|
resp := provisioner.GetSchema()
|
||||||
|
if resp.Diagnostics.HasErrors() {
|
||||||
|
return nil, fmt.Errorf("failed to retrieve schema from provisioner %q: %w", name, resp.Diagnostics.Err())
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Provisioner, nil
|
||||||
|
}()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// We'll put a stub in the map so we won't re-attempt this on
|
// We'll put a stub in the map so we won't re-attempt this on
|
||||||
// future calls, which would then repeat the same error message
|
// future calls, which would then repeat the same error message
|
||||||
|
|||||||
@@ -312,5 +312,9 @@ func schemaOnlyProvidersForTesting(schemas map[addrs.Provider]providers.Provider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return newContextPlugins(factories, nil)
|
cp, diags := newContextPlugins(factories, nil)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
t.Fatal(diags.Err())
|
||||||
|
}
|
||||||
|
return cp
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ func (t *AttachSchemaTransformer) Transform(ctx context.Context, g *Graph) error
|
|||||||
providerFqn := tv.Provider()
|
providerFqn := tv.Provider()
|
||||||
|
|
||||||
// TODO: Plumb a useful context.Context through to here.
|
// TODO: Plumb a useful context.Context through to here.
|
||||||
schema, version, err := t.Plugins.ResourceTypeSchema(ctx, providerFqn, mode, typeName)
|
schema, version, err := t.Plugins.ResourceTypeSchema(providerFqn, mode, typeName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to read schema for %s in %s: %w", addr, providerFqn, err)
|
return fmt.Errorf("failed to read schema for %s in %s: %w", addr, providerFqn, err)
|
||||||
}
|
}
|
||||||
@@ -84,7 +84,7 @@ func (t *AttachSchemaTransformer) Transform(ctx context.Context, g *Graph) error
|
|||||||
if tv, ok := v.(GraphNodeAttachProviderConfigSchema); ok {
|
if tv, ok := v.(GraphNodeAttachProviderConfigSchema); ok {
|
||||||
providerAddr := tv.ProviderAddr()
|
providerAddr := tv.ProviderAddr()
|
||||||
// TODO: Plumb a useful context.Context through to here.
|
// TODO: Plumb a useful context.Context through to here.
|
||||||
schema, err := t.Plugins.ProviderConfigSchema(ctx, providerAddr.Provider)
|
schema, err := t.Plugins.ProviderConfigSchema(providerAddr.Provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to read provider configuration schema for %s: %w", providerAddr.Provider, err)
|
return fmt.Errorf("failed to read provider configuration schema for %s: %w", providerAddr.Provider, err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user