Allow unconfigured provider functions in test context (#1603)

Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
Christian Mesh 2024-05-07 09:31:17 -04:00 committed by GitHub
parent 08469452b6
commit 015b79b139
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 52 additions and 9 deletions

View File

@ -12,6 +12,7 @@ BUG FIXES:
* Fix inmem backend crash due to missing struct field ([#1619](https://github.com/opentofu/opentofu/pull/1619)) * Fix inmem backend crash due to missing struct field ([#1619](https://github.com/opentofu/opentofu/pull/1619))
* Added a check in the `tofu test` to validate that the names of test run blocks do not contain spaces. ([#1489](https://github.com/opentofu/opentofu/pull/1489)) * Added a check in the `tofu test` to validate that the names of test run blocks do not contain spaces. ([#1489](https://github.com/opentofu/opentofu/pull/1489))
* `tofu test` now supports accessing module outputs when the module has no resources. ([#1409](https://github.com/opentofu/opentofu/pull/1409)) * `tofu test` now supports accessing module outputs when the module has no resources. ([#1409](https://github.com/opentofu/opentofu/pull/1409))
* Fixed support for provider functions in tests ([#1603](https://github.com/opentofu/opentofu/pull/1603))
## Previous Releases ## Previous Releases

View File

@ -15,7 +15,7 @@ import (
// This builds a provider function using an EvalContext and some additional information // This builds a provider function using an EvalContext and some additional information
// This is split out of BuiltinEvalContext for testing // This is split out of BuiltinEvalContext for testing
func evalContextProviderFunction(ctx EvalContext, mc *configs.Config, op walkOperation, pf addrs.ProviderFunction, rng tfdiags.SourceRange) (*function.Function, tfdiags.Diagnostics) { func evalContextProviderFunction(providers func(addrs.AbsProviderConfig) providers.Interface, mc *configs.Config, op walkOperation, pf addrs.ProviderFunction, rng tfdiags.SourceRange) (*function.Function, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics var diags tfdiags.Diagnostics
pr, ok := mc.Module.ProviderRequirements.RequiredProviders[pf.ProviderName] pr, ok := mc.Module.ProviderRequirements.RequiredProviders[pf.ProviderName]
@ -35,7 +35,7 @@ func evalContextProviderFunction(ctx EvalContext, mc *configs.Config, op walkOpe
Alias: pf.ProviderAlias, Alias: pf.ProviderAlias,
} }
provider := ctx.Provider(absPc) provider := providers(absPc)
if provider == nil { if provider == nil {
// Configured provider (NodeApplyableProvider) not required via transform_provider.go. Instead we should use the unconfigured instance (NodeEvalableProvider) in the root. // Configured provider (NodeApplyableProvider) not required via transform_provider.go. Instead we should use the unconfigured instance (NodeEvalableProvider) in the root.
@ -59,7 +59,7 @@ func evalContextProviderFunction(ctx EvalContext, mc *configs.Config, op walkOpe
} }
} }
provider = ctx.Provider(addrs.AbsProviderConfig{Provider: pr.Type}) provider = providers(addrs.AbsProviderConfig{Provider: pr.Type})
if provider == nil { if provider == nil {
// This should not be possible // This should not be possible
return nil, diags.Append(&hcl.Diagnostic{ return nil, diags.Append(&hcl.Diagnostic{

View File

@ -134,7 +134,7 @@ func TestFunctions(t *testing.T) {
} }
// Provider missing // Provider missing
_, diags := evalContextProviderFunction(mockCtx, cfg, walkValidate, providerFunc("provider::invalid::unknown"), rng) _, diags := evalContextProviderFunction(mockCtx.Provider, cfg, walkValidate, providerFunc("provider::invalid::unknown"), rng)
if !diags.HasErrors() { if !diags.HasErrors() {
t.Fatal("expected unknown function provider") t.Fatal("expected unknown function provider")
} }
@ -143,7 +143,7 @@ func TestFunctions(t *testing.T) {
} }
// Provider not initialized // Provider not initialized
_, diags = evalContextProviderFunction(mockCtx, cfg, walkValidate, providerFunc("provider::mockname::missing"), rng) _, diags = evalContextProviderFunction(mockCtx.Provider, cfg, walkValidate, providerFunc("provider::mockname::missing"), rng)
if !diags.HasErrors() { if !diags.HasErrors() {
t.Fatal("expected unknown function provider") t.Fatal("expected unknown function provider")
} }
@ -156,7 +156,7 @@ func TestFunctions(t *testing.T) {
// Function missing (validate) // Function missing (validate)
mockProvider.GetFunctionsCalled = false mockProvider.GetFunctionsCalled = false
_, diags = evalContextProviderFunction(mockCtx, cfg, walkValidate, providerFunc("provider::mockname::missing"), rng) _, diags = evalContextProviderFunction(mockCtx.Provider, cfg, walkValidate, providerFunc("provider::mockname::missing"), rng)
if diags.HasErrors() { if diags.HasErrors() {
t.Fatal(diags.Err()) t.Fatal(diags.Err())
} }
@ -166,7 +166,7 @@ func TestFunctions(t *testing.T) {
// Function missing (Non-validate) // Function missing (Non-validate)
mockProvider.GetFunctionsCalled = false mockProvider.GetFunctionsCalled = false
_, diags = evalContextProviderFunction(mockCtx, cfg, walkPlan, providerFunc("provider::mockname::missing"), rng) _, diags = evalContextProviderFunction(mockCtx.Provider, cfg, walkPlan, providerFunc("provider::mockname::missing"), rng)
if !diags.HasErrors() { if !diags.HasErrors() {
t.Fatal("expected unknown function") t.Fatal("expected unknown function")
} }
@ -188,7 +188,7 @@ func TestFunctions(t *testing.T) {
// Load functions into ctx // Load functions into ctx
for _, fn := range []string{"echo", "concat", "coalesce", "unknown_param", "error_param"} { for _, fn := range []string{"echo", "concat", "coalesce", "unknown_param", "error_param"} {
pf := providerFunc("provider::mockname::" + fn) pf := providerFunc("provider::mockname::" + fn)
impl, diags := evalContextProviderFunction(mockCtx, cfg, walkPlan, pf, rng) impl, diags := evalContextProviderFunction(mockCtx.Provider, cfg, walkPlan, pf, rng)
if diags.HasErrors() { if diags.HasErrors() {
t.Fatal(diags.Err()) t.Fatal(diags.Err())
} }

View File

@ -526,7 +526,7 @@ func (ctx *BuiltinEvalContext) EvaluationScope(self addrs.Referenceable, source
} }
scope := ctx.Evaluator.Scope(data, self, source, func(pf addrs.ProviderFunction, rng tfdiags.SourceRange) (*function.Function, tfdiags.Diagnostics) { scope := ctx.Evaluator.Scope(data, self, source, func(pf addrs.ProviderFunction, rng tfdiags.SourceRange) (*function.Function, tfdiags.Diagnostics) {
return evalContextProviderFunction(ctx, mc, ctx.Evaluator.Operation, pf, rng) return evalContextProviderFunction(ctx.Provider, mc, ctx.Evaluator.Operation, pf, rng)
}) })
scope.SetActiveExperiments(mc.Module.ActiveExperiments) scope.SetActiveExperiments(mc.Module.ActiveExperiments)

View File

@ -7,17 +7,20 @@ package tofu
import ( import (
"fmt" "fmt"
"log"
"sync" "sync"
"github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert" "github.com/zclconf/go-cty/cty/convert"
"github.com/zclconf/go-cty/cty/function"
"github.com/opentofu/opentofu/internal/addrs" "github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/configs" "github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/lang" "github.com/opentofu/opentofu/internal/lang"
"github.com/opentofu/opentofu/internal/moduletest" "github.com/opentofu/opentofu/internal/moduletest"
"github.com/opentofu/opentofu/internal/plans" "github.com/opentofu/opentofu/internal/plans"
"github.com/opentofu/opentofu/internal/providers"
"github.com/opentofu/opentofu/internal/states" "github.com/opentofu/opentofu/internal/states"
"github.com/opentofu/opentofu/internal/tfdiags" "github.com/opentofu/opentofu/internal/tfdiags"
) )
@ -99,11 +102,50 @@ func (ctx *TestContext) evaluate(state *states.SyncState, changes *plans.Changes
Operation: operation, Operation: operation,
} }
var providerInstanceLock sync.Mutex
providerInstances := make(map[addrs.Provider]providers.Interface)
defer func() {
for addr, inst := range providerInstances {
log.Printf("[INFO] Shutting down test provider %s", addr)
inst.Close()
}
}()
providerSupplier := func(addr addrs.AbsProviderConfig) providers.Interface {
providerInstanceLock.Lock()
defer providerInstanceLock.Unlock()
if inst, ok := providerInstances[addr.Provider]; ok {
return inst
}
factory, ok := ctx.plugins.providerFactories[addr.Provider]
if !ok {
log.Printf("[WARN] Unable to find provider %s in test context", addr)
providerInstances[addr.Provider] = nil
return nil
}
log.Printf("[INFO] Starting test provider %s", addr)
inst, err := factory()
if err != nil {
log.Printf("[WARN] Unable to start provider %s in test context", addr)
providerInstances[addr.Provider] = nil
return nil
} else {
log.Printf("[INFO] Shutting down test provider %s", addr)
providerInstances[addr.Provider] = inst
return inst
}
}
scope := &lang.Scope{ scope := &lang.Scope{
Data: data, Data: data,
BaseDir: ".", BaseDir: ".",
PureOnly: operation != walkApply, PureOnly: operation != walkApply,
PlanTimestamp: ctx.Plan.Timestamp, PlanTimestamp: ctx.Plan.Timestamp,
ProviderFunctions: func(pf addrs.ProviderFunction, rng tfdiags.SourceRange) (*function.Function, tfdiags.Diagnostics) {
return evalContextProviderFunction(providerSupplier, ctx.Config, walkPlan, pf, rng)
},
} }
// We're going to assume the run has passed, and then if anything fails this // We're going to assume the run has passed, and then if anything fails this