mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Fix provider functions in child modules (#2082)
Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
parent
c9541d81b6
commit
e3a6bcab96
@ -41,3 +41,9 @@ func (c *schemaCache) Get(p addrs.Provider) (ProviderSchema, bool) {
|
||||
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)
|
||||
}
|
||||
|
@ -54,7 +54,9 @@ func (c *Context) Apply(plan *plans.Plan, config *configs.Config) (*states.State
|
||||
}
|
||||
}
|
||||
|
||||
graph, operation, diags := c.applyGraph(plan, config, true)
|
||||
providerFunctionTracker := make(ProviderFunctionMapping)
|
||||
|
||||
graph, operation, diags := c.applyGraph(plan, config, true, providerFunctionTracker)
|
||||
if diags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
@ -71,7 +73,8 @@ func (c *Context) Apply(plan *plans.Plan, config *configs.Config) (*states.State
|
||||
PlanTimeCheckResults: plan.Checks,
|
||||
|
||||
// We also want to propagate the timestamp from the plan file.
|
||||
PlanTimeTimestamp: plan.Timestamp,
|
||||
PlanTimeTimestamp: plan.Timestamp,
|
||||
ProviderFunctionTracker: providerFunctionTracker,
|
||||
})
|
||||
diags = diags.Append(walker.NonFatalDiagnostics)
|
||||
diags = diags.Append(walkDiags)
|
||||
@ -119,7 +122,8 @@ Note that the -target option is not suitable for routine use, and is provided on
|
||||
return newState, diags
|
||||
}
|
||||
|
||||
func (c *Context) applyGraph(plan *plans.Plan, config *configs.Config, validate bool) (*Graph, walkOperation, tfdiags.Diagnostics) {
|
||||
//nolint:revive,unparam // TODO remove validate bool as it's not used
|
||||
func (c *Context) applyGraph(plan *plans.Plan, config *configs.Config, validate bool, providerFunctionTracker ProviderFunctionMapping) (*Graph, walkOperation, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
variables := InputValues{}
|
||||
@ -171,15 +175,16 @@ func (c *Context) applyGraph(plan *plans.Plan, config *configs.Config, validate
|
||||
}
|
||||
|
||||
graph, moreDiags := (&ApplyGraphBuilder{
|
||||
Config: config,
|
||||
Changes: plan.Changes,
|
||||
State: plan.PriorState,
|
||||
RootVariableValues: variables,
|
||||
Plugins: c.plugins,
|
||||
Targets: plan.TargetAddrs,
|
||||
ForceReplace: plan.ForceReplaceAddrs,
|
||||
Operation: operation,
|
||||
ExternalReferences: plan.ExternalReferences,
|
||||
Config: config,
|
||||
Changes: plan.Changes,
|
||||
State: plan.PriorState,
|
||||
RootVariableValues: variables,
|
||||
Plugins: c.plugins,
|
||||
Targets: plan.TargetAddrs,
|
||||
ForceReplace: plan.ForceReplaceAddrs,
|
||||
Operation: operation,
|
||||
ExternalReferences: plan.ExternalReferences,
|
||||
ProviderFunctionTracker: providerFunctionTracker,
|
||||
}).Build(addrs.RootModuleInstance)
|
||||
diags = diags.Append(moreDiags)
|
||||
if moreDiags.HasErrors() {
|
||||
@ -204,7 +209,7 @@ func (c *Context) ApplyGraphForUI(plan *plans.Plan, config *configs.Config) (*Gr
|
||||
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
graph, _, moreDiags := c.applyGraph(plan, config, false)
|
||||
graph, _, moreDiags := c.applyGraph(plan, config, false, make(ProviderFunctionMapping))
|
||||
diags = diags.Append(moreDiags)
|
||||
return graph, diags
|
||||
}
|
||||
|
@ -64,11 +64,14 @@ func (c *Context) Eval(config *configs.Config, state *states.State, moduleAddr a
|
||||
|
||||
log.Printf("[DEBUG] Building and walking 'eval' graph")
|
||||
|
||||
providerFunctionTracker := make(ProviderFunctionMapping)
|
||||
|
||||
graph, moreDiags := (&EvalGraphBuilder{
|
||||
Config: config,
|
||||
State: state,
|
||||
RootVariableValues: variables,
|
||||
Plugins: c.plugins,
|
||||
Config: config,
|
||||
State: state,
|
||||
RootVariableValues: variables,
|
||||
Plugins: c.plugins,
|
||||
ProviderFunctionTracker: providerFunctionTracker,
|
||||
}).Build(addrs.RootModuleInstance)
|
||||
diags = diags.Append(moreDiags)
|
||||
if moreDiags.HasErrors() {
|
||||
@ -76,8 +79,9 @@ func (c *Context) Eval(config *configs.Config, state *states.State, moduleAddr a
|
||||
}
|
||||
|
||||
walkOpts := &graphWalkOpts{
|
||||
InputState: state,
|
||||
Config: config,
|
||||
InputState: state,
|
||||
Config: config,
|
||||
ProviderFunctionTracker: providerFunctionTracker,
|
||||
}
|
||||
|
||||
walker, moreDiags = c.walk(graph, walkEval, walkOpts)
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/opentofu/opentofu/internal/addrs"
|
||||
"github.com/opentofu/opentofu/internal/configs"
|
||||
"github.com/opentofu/opentofu/internal/providers"
|
||||
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
@ -20,62 +19,9 @@ import (
|
||||
|
||||
// This builds a provider function using an EvalContext and some additional information
|
||||
// This is split out of BuiltinEvalContext for testing
|
||||
func evalContextProviderFunction(providers func(addrs.AbsProviderConfig) providers.Interface, mc *configs.Config, op walkOperation, pf addrs.ProviderFunction, rng tfdiags.SourceRange) (*function.Function, tfdiags.Diagnostics) {
|
||||
func evalContextProviderFunction(provider providers.Interface, op walkOperation, pf addrs.ProviderFunction, rng tfdiags.SourceRange) (*function.Function, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
pr, ok := mc.Module.ProviderRequirements.RequiredProviders[pf.ProviderName]
|
||||
if !ok {
|
||||
return nil, diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unknown function provider",
|
||||
Detail: fmt.Sprintf("Provider %q does not exist within the required_providers of this module", pf.ProviderName),
|
||||
Subject: rng.ToHCL().Ptr(),
|
||||
})
|
||||
}
|
||||
|
||||
// Very similar to transform_provider.go
|
||||
absPc := addrs.AbsProviderConfig{
|
||||
Provider: pr.Type,
|
||||
Module: mc.Path,
|
||||
Alias: pf.ProviderAlias,
|
||||
}
|
||||
|
||||
provider := providers(absPc)
|
||||
|
||||
if provider == nil {
|
||||
// Configured provider (NodeApplyableProvider) not required via transform_provider.go. Instead we should use the unconfigured instance (NodeEvalableProvider) in the root.
|
||||
|
||||
// Make sure the alias is valid
|
||||
validAlias := pf.ProviderAlias == ""
|
||||
if !validAlias {
|
||||
for _, alias := range pr.Aliases {
|
||||
if alias.Alias == pf.ProviderAlias {
|
||||
validAlias = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !validAlias {
|
||||
return nil, diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unknown function provider",
|
||||
Detail: fmt.Sprintf("No provider instance %q with alias %q", pf.ProviderName, pf.ProviderAlias),
|
||||
Subject: rng.ToHCL().Ptr(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
provider = providers(addrs.AbsProviderConfig{Provider: pr.Type})
|
||||
if provider == nil {
|
||||
// This should not be possible
|
||||
return nil, diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "BUG: Uninitialized function provider",
|
||||
Detail: fmt.Sprintf("Provider %q has not yet been initialized", absPc.String()),
|
||||
Subject: rng.ToHCL().Ptr(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// First try to look up the function from provider schema
|
||||
schema := provider.GetProviderSchema()
|
||||
if schema.Diagnostics.HasErrors() {
|
||||
@ -117,7 +63,7 @@ func evalContextProviderFunction(providers func(addrs.AbsProviderConfig) provide
|
||||
return nil, diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Function not found in provider",
|
||||
Detail: fmt.Sprintf("Function %q was not registered by provider %q", pf.Function, absPc.String()),
|
||||
Detail: fmt.Sprintf("Function %q was not registered by provider", pf),
|
||||
Subject: rng.ToHCL().Ptr(),
|
||||
})
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
"github.com/opentofu/opentofu/internal/addrs"
|
||||
"github.com/opentofu/opentofu/internal/configs"
|
||||
"github.com/opentofu/opentofu/internal/configs/configschema"
|
||||
"github.com/opentofu/opentofu/internal/lang/marks"
|
||||
"github.com/opentofu/opentofu/internal/providers"
|
||||
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||
@ -117,51 +117,15 @@ func TestFunctions(t *testing.T) {
|
||||
return resp
|
||||
}
|
||||
|
||||
addr := addrs.NewDefaultProvider("mock")
|
||||
rng := tfdiags.SourceRange{}
|
||||
providerFunc := func(fn string) addrs.ProviderFunction {
|
||||
pf, _ := addrs.ParseFunction(fn).AsProviderFunction()
|
||||
return pf
|
||||
}
|
||||
|
||||
mockCtx := new(MockEvalContext)
|
||||
cfg := &configs.Config{
|
||||
Module: &configs.Module{
|
||||
ProviderRequirements: &configs.RequiredProviders{
|
||||
RequiredProviders: map[string]*configs.RequiredProvider{
|
||||
"mockname": &configs.RequiredProvider{
|
||||
Name: "mock",
|
||||
Type: addr,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Provider missing
|
||||
_, diags := evalContextProviderFunction(mockCtx.Provider, cfg, walkValidate, providerFunc("provider::invalid::unknown"), rng)
|
||||
if !diags.HasErrors() {
|
||||
t.Fatal("expected unknown function provider")
|
||||
}
|
||||
if diags.Err().Error() != `Unknown function provider: Provider "invalid" does not exist within the required_providers of this module` {
|
||||
t.Fatal(diags.Err())
|
||||
}
|
||||
|
||||
// Provider not initialized
|
||||
_, diags = evalContextProviderFunction(mockCtx.Provider, cfg, walkValidate, providerFunc("provider::mockname::missing"), rng)
|
||||
if !diags.HasErrors() {
|
||||
t.Fatal("expected unknown function provider")
|
||||
}
|
||||
if diags.Err().Error() != `BUG: Uninitialized function provider: Provider "provider[\"registry.opentofu.org/hashicorp/mock\"]" has not yet been initialized` {
|
||||
t.Fatal(diags.Err())
|
||||
}
|
||||
|
||||
// "initialize" provider
|
||||
mockCtx.ProviderProvider = mockProvider
|
||||
|
||||
// Function missing (validate)
|
||||
mockProvider.GetFunctionsCalled = false
|
||||
_, diags = evalContextProviderFunction(mockCtx.Provider, cfg, walkValidate, providerFunc("provider::mockname::missing"), rng)
|
||||
_, diags := evalContextProviderFunction(mockProvider, walkValidate, providerFunc("provider::mockname::missing"), rng)
|
||||
if diags.HasErrors() {
|
||||
t.Fatal(diags.Err())
|
||||
}
|
||||
@ -171,11 +135,11 @@ func TestFunctions(t *testing.T) {
|
||||
|
||||
// Function missing (Non-validate)
|
||||
mockProvider.GetFunctionsCalled = false
|
||||
_, diags = evalContextProviderFunction(mockCtx.Provider, cfg, walkPlan, providerFunc("provider::mockname::missing"), rng)
|
||||
_, diags = evalContextProviderFunction(mockProvider, walkPlan, providerFunc("provider::mockname::missing"), rng)
|
||||
if !diags.HasErrors() {
|
||||
t.Fatal("expected unknown function")
|
||||
}
|
||||
if diags.Err().Error() != `Function not found in provider: Function "missing" was not registered by provider "provider[\"registry.opentofu.org/hashicorp/mock\"]"` {
|
||||
if diags.Err().Error() != `Function not found in provider: Function "provider::mockname::missing" was not registered by provider` {
|
||||
t.Fatal(diags.Err())
|
||||
}
|
||||
if !mockProvider.GetFunctionsCalled {
|
||||
@ -193,7 +157,7 @@ func TestFunctions(t *testing.T) {
|
||||
// Load functions into ctx
|
||||
for _, fn := range []string{"echo", "concat", "coalesce", "unknown_param", "error_param"} {
|
||||
pf := providerFunc("provider::mockname::" + fn)
|
||||
impl, diags := evalContextProviderFunction(mockCtx.Provider, cfg, walkPlan, pf, rng)
|
||||
impl, diags := evalContextProviderFunction(mockProvider, walkPlan, pf, rng)
|
||||
if diags.HasErrors() {
|
||||
t.Fatal(diags.Err())
|
||||
}
|
||||
@ -316,3 +280,373 @@ func TestFunctions(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Standard scenario using root provider explicitly passed
|
||||
func TestContext2Functions_providerFunctions(t *testing.T) {
|
||||
p := testProvider("aws")
|
||||
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
|
||||
Provider: providers.Schema{
|
||||
Block: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"region": &configschema.Attribute{
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Functions: map[string]providers.FunctionSpec{
|
||||
"arn_parse": providers.FunctionSpec{
|
||||
Parameters: []providers.FunctionParameterSpec{{
|
||||
Name: "arn",
|
||||
Type: cty.String,
|
||||
}},
|
||||
Return: cty.Bool,
|
||||
},
|
||||
},
|
||||
}
|
||||
p.CallFunctionResponse = &providers.CallFunctionResponse{
|
||||
Result: cty.True,
|
||||
}
|
||||
m := testModuleInline(t, map[string]string{
|
||||
"main.tf": `
|
||||
terraform {
|
||||
required_providers {
|
||||
aws = ">=5.70.0"
|
||||
}
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
region="us-east-1"
|
||||
}
|
||||
|
||||
module "mod" {
|
||||
source = "./mod"
|
||||
providers = {
|
||||
aws = aws
|
||||
}
|
||||
}
|
||||
`,
|
||||
"mod/mod.tf": `
|
||||
terraform {
|
||||
required_providers {
|
||||
aws = ">=5.70.0"
|
||||
}
|
||||
}
|
||||
|
||||
variable "obfmod" {
|
||||
type = object({
|
||||
arns = optional(list(string))
|
||||
})
|
||||
description = "Configuration for xxx."
|
||||
|
||||
validation {
|
||||
condition = alltrue([
|
||||
for arn in var.obfmod.arns: can(provider::aws::arn_parse(arn))
|
||||
])
|
||||
error_message = "All arns MUST BE a valid AWS ARN format."
|
||||
}
|
||||
|
||||
default = {
|
||||
arns = [
|
||||
"arn:partition:service:region:account-id:resource-id",
|
||||
]
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
diags := ctx.Validate(m)
|
||||
if diags.HasErrors() {
|
||||
t.Fatal(diags.Err())
|
||||
}
|
||||
if !p.CallFunctionCalled {
|
||||
t.Fatalf("Expected function call")
|
||||
}
|
||||
}
|
||||
|
||||
// Explicitly passed provider with custom function
|
||||
func TestContext2Functions_providerFunctionsCustom(t *testing.T) {
|
||||
p := testProvider("aws")
|
||||
p.GetFunctionsResponse = &providers.GetFunctionsResponse{
|
||||
Functions: map[string]providers.FunctionSpec{
|
||||
"arn_parse_custom": providers.FunctionSpec{
|
||||
Parameters: []providers.FunctionParameterSpec{{
|
||||
Name: "arn",
|
||||
Type: cty.String,
|
||||
}},
|
||||
Return: cty.Bool,
|
||||
},
|
||||
},
|
||||
}
|
||||
p.CallFunctionResponse = &providers.CallFunctionResponse{
|
||||
Result: cty.True,
|
||||
}
|
||||
m := testModuleInline(t, map[string]string{
|
||||
"main.tf": `
|
||||
terraform {
|
||||
required_providers {
|
||||
aws = ">=5.70.0"
|
||||
}
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
region="us-east-1"
|
||||
alias = "primary"
|
||||
}
|
||||
|
||||
module "mod" {
|
||||
source = "./mod"
|
||||
providers = {
|
||||
aws = aws.primary
|
||||
}
|
||||
}
|
||||
`,
|
||||
"mod/mod.tf": `
|
||||
terraform {
|
||||
required_providers {
|
||||
aws = ">=5.70.0"
|
||||
}
|
||||
}
|
||||
|
||||
variable "obfmod" {
|
||||
type = object({
|
||||
arns = optional(list(string))
|
||||
})
|
||||
description = "Configuration for xxx."
|
||||
|
||||
validation {
|
||||
condition = alltrue([
|
||||
for arn in var.obfmod.arns: can(provider::aws::arn_parse_custom(arn))
|
||||
])
|
||||
error_message = "All arns MUST BE a valid AWS ARN format."
|
||||
}
|
||||
|
||||
default = {
|
||||
arns = [
|
||||
"arn:partition:service:region:account-id:resource-id",
|
||||
]
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
diags := ctx.Validate(m)
|
||||
if diags.HasErrors() {
|
||||
t.Fatal(diags.Err())
|
||||
}
|
||||
if p.GetFunctionsCalled {
|
||||
t.Fatalf("Unexpected function call")
|
||||
}
|
||||
if p.CallFunctionCalled {
|
||||
t.Fatalf("Unexpected function call")
|
||||
}
|
||||
|
||||
p.GetFunctionsCalled = false
|
||||
p.CallFunctionCalled = false
|
||||
_, diags = ctx.Plan(m, nil, nil)
|
||||
if diags.HasErrors() {
|
||||
t.Fatal(diags.Err())
|
||||
}
|
||||
if !p.GetFunctionsCalled {
|
||||
t.Fatalf("Expected function call")
|
||||
}
|
||||
if !p.CallFunctionCalled {
|
||||
t.Fatalf("Expected function call")
|
||||
}
|
||||
}
|
||||
|
||||
// Defaulted stub provider with non-custom function
|
||||
func TestContext2Functions_providerFunctionsStub(t *testing.T) {
|
||||
p := testProvider("aws")
|
||||
addr := addrs.ImpliedProviderForUnqualifiedType("aws")
|
||||
|
||||
// Explicitly non-parallel
|
||||
t.Setenv("foo", "bar")
|
||||
defer providers.SchemaCache.Remove(addr)
|
||||
|
||||
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
|
||||
Functions: map[string]providers.FunctionSpec{
|
||||
"arn_parse": providers.FunctionSpec{
|
||||
Parameters: []providers.FunctionParameterSpec{{
|
||||
Name: "arn",
|
||||
Type: cty.String,
|
||||
}},
|
||||
Return: cty.Bool,
|
||||
},
|
||||
},
|
||||
}
|
||||
p.CallFunctionResponse = &providers.CallFunctionResponse{
|
||||
Result: cty.True,
|
||||
}
|
||||
|
||||
// SchemaCache is initialzed earlier on in the command package
|
||||
providers.SchemaCache.Set(addr, *p.GetProviderSchemaResponse)
|
||||
|
||||
m := testModuleInline(t, map[string]string{
|
||||
"main.tf": `
|
||||
module "mod" {
|
||||
source = "./mod"
|
||||
}
|
||||
`,
|
||||
"mod/mod.tf": `
|
||||
terraform {
|
||||
required_providers {
|
||||
aws = ">=5.70.0"
|
||||
}
|
||||
}
|
||||
|
||||
variable "obfmod" {
|
||||
type = object({
|
||||
arns = optional(list(string))
|
||||
})
|
||||
description = "Configuration for xxx."
|
||||
|
||||
validation {
|
||||
condition = alltrue([
|
||||
for arn in var.obfmod.arns: can(provider::aws::arn_parse(arn))
|
||||
])
|
||||
error_message = "All arns MUST BE a valid AWS ARN format."
|
||||
}
|
||||
|
||||
default = {
|
||||
arns = [
|
||||
"arn:partition:service:region:account-id:resource-id",
|
||||
]
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
diags := ctx.Validate(m)
|
||||
if diags.HasErrors() {
|
||||
t.Fatal(diags.Err())
|
||||
}
|
||||
if !p.GetProviderSchemaCalled {
|
||||
t.Fatalf("Unexpected function call")
|
||||
}
|
||||
if p.GetFunctionsCalled {
|
||||
t.Fatalf("Unexpected function call")
|
||||
}
|
||||
if !p.CallFunctionCalled {
|
||||
t.Fatalf("Unexpected function call")
|
||||
}
|
||||
|
||||
p.GetProviderSchemaCalled = false
|
||||
p.GetFunctionsCalled = false
|
||||
p.CallFunctionCalled = false
|
||||
_, diags = ctx.Plan(m, nil, nil)
|
||||
if diags.HasErrors() {
|
||||
t.Fatal(diags.Err())
|
||||
}
|
||||
if !p.GetProviderSchemaCalled {
|
||||
t.Fatalf("Unexpected function call")
|
||||
}
|
||||
if p.GetFunctionsCalled {
|
||||
t.Fatalf("Expected function call")
|
||||
}
|
||||
if !p.CallFunctionCalled {
|
||||
t.Fatalf("Expected function call")
|
||||
}
|
||||
}
|
||||
|
||||
// Defaulted stub provider with custom function (no allowed)
|
||||
func TestContext2Functions_providerFunctionsStubCustom(t *testing.T) {
|
||||
p := testProvider("aws")
|
||||
addr := addrs.ImpliedProviderForUnqualifiedType("aws")
|
||||
|
||||
// Explicitly non-parallel
|
||||
t.Setenv("foo", "bar")
|
||||
defer providers.SchemaCache.Remove(addr)
|
||||
|
||||
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
|
||||
Functions: map[string]providers.FunctionSpec{
|
||||
"arn_parse": providers.FunctionSpec{
|
||||
Parameters: []providers.FunctionParameterSpec{{
|
||||
Name: "arn",
|
||||
Type: cty.String,
|
||||
}},
|
||||
Return: cty.Bool,
|
||||
},
|
||||
},
|
||||
}
|
||||
p.CallFunctionResponse = &providers.CallFunctionResponse{
|
||||
Result: cty.True,
|
||||
}
|
||||
|
||||
// SchemaCache is initialzed earlier on in the command package
|
||||
providers.SchemaCache.Set(addr, *p.GetProviderSchemaResponse)
|
||||
|
||||
m := testModuleInline(t, map[string]string{
|
||||
"main.tf": `
|
||||
module "mod" {
|
||||
source = "./mod"
|
||||
}
|
||||
`,
|
||||
"mod/mod.tf": `
|
||||
terraform {
|
||||
required_providers {
|
||||
aws = ">=5.70.0"
|
||||
}
|
||||
}
|
||||
|
||||
variable "obfmod" {
|
||||
type = object({
|
||||
arns = optional(list(string))
|
||||
})
|
||||
description = "Configuration for xxx."
|
||||
|
||||
validation {
|
||||
condition = alltrue([
|
||||
for arn in var.obfmod.arns: can(provider::aws::arn_parse_custom(arn))
|
||||
])
|
||||
error_message = "All arns MUST BE a valid AWS ARN format."
|
||||
}
|
||||
|
||||
default = {
|
||||
arns = [
|
||||
"arn:partition:service:region:account-id:resource-id",
|
||||
]
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
diags := ctx.Validate(m)
|
||||
if !diags.HasErrors() {
|
||||
t.Fatal("Expected error!")
|
||||
}
|
||||
expected := `Unknown provider function: Provider "module.mod.provider[\"registry.opentofu.org/hashicorp/aws\"]" does not have a function "arn_parse_custom" or has not been configured`
|
||||
if expected != diags.Err().Error() {
|
||||
t.Fatalf("Expected error %q, got %q", expected, diags.Err().Error())
|
||||
}
|
||||
if p.GetFunctionsCalled {
|
||||
t.Fatalf("Unexpected function call")
|
||||
}
|
||||
if p.CallFunctionCalled {
|
||||
t.Fatalf("Unexpected function call")
|
||||
}
|
||||
}
|
||||
|
@ -251,14 +251,17 @@ func (c *Context) Import(config *configs.Config, prevRunState *states.State, opt
|
||||
|
||||
variables := opts.SetVariables
|
||||
|
||||
providerFunctionTracker := make(ProviderFunctionMapping)
|
||||
|
||||
// Initialize our graph builder
|
||||
builder := &PlanGraphBuilder{
|
||||
ImportTargets: opts.Targets,
|
||||
Config: config,
|
||||
State: state,
|
||||
RootVariableValues: variables,
|
||||
Plugins: c.plugins,
|
||||
Operation: walkImport,
|
||||
ImportTargets: opts.Targets,
|
||||
Config: config,
|
||||
State: state,
|
||||
RootVariableValues: variables,
|
||||
Plugins: c.plugins,
|
||||
Operation: walkImport,
|
||||
ProviderFunctionTracker: providerFunctionTracker,
|
||||
}
|
||||
|
||||
// Build the graph
|
||||
@ -270,8 +273,9 @@ func (c *Context) Import(config *configs.Config, prevRunState *states.State, opt
|
||||
|
||||
// Walk it
|
||||
walker, walkDiags := c.walk(graph, walkImport, &graphWalkOpts{
|
||||
Config: config,
|
||||
InputState: state,
|
||||
Config: config,
|
||||
InputState: state,
|
||||
ProviderFunctionTracker: providerFunctionTracker,
|
||||
})
|
||||
diags = diags.Append(walkDiags)
|
||||
if walkDiags.HasErrors() {
|
||||
|
@ -277,7 +277,7 @@ func (c *Context) checkApplyGraph(plan *plans.Plan, config *configs.Config) tfdi
|
||||
return nil
|
||||
}
|
||||
log.Println("[DEBUG] building apply graph to check for errors")
|
||||
_, _, diags := c.applyGraph(plan, config, true)
|
||||
_, _, diags := c.applyGraph(plan, config, true, make(ProviderFunctionMapping))
|
||||
return diags
|
||||
}
|
||||
|
||||
@ -673,8 +673,9 @@ func (c *Context) planWalk(config *configs.Config, prevRunState *states.State, o
|
||||
// strange problems that may lead to confusing error messages.
|
||||
return nil, diags
|
||||
}
|
||||
providerFunctionTracker := make(ProviderFunctionMapping)
|
||||
|
||||
graph, walkOp, moreDiags := c.planGraph(config, prevRunState, opts)
|
||||
graph, walkOp, moreDiags := c.planGraph(config, prevRunState, opts, providerFunctionTracker)
|
||||
diags = diags.Append(moreDiags)
|
||||
if diags.HasErrors() {
|
||||
return nil, diags
|
||||
@ -686,11 +687,12 @@ func (c *Context) planWalk(config *configs.Config, prevRunState *states.State, o
|
||||
// we can now walk.
|
||||
changes := plans.NewChanges()
|
||||
walker, walkDiags := c.walk(graph, walkOp, &graphWalkOpts{
|
||||
Config: config,
|
||||
InputState: prevRunState,
|
||||
Changes: changes,
|
||||
MoveResults: moveResults,
|
||||
PlanTimeTimestamp: timestamp,
|
||||
Config: config,
|
||||
InputState: prevRunState,
|
||||
Changes: changes,
|
||||
MoveResults: moveResults,
|
||||
PlanTimeTimestamp: timestamp,
|
||||
ProviderFunctionTracker: providerFunctionTracker,
|
||||
})
|
||||
diags = diags.Append(walker.NonFatalDiagnostics)
|
||||
diags = diags.Append(walkDiags)
|
||||
@ -754,47 +756,50 @@ func (c *Context) planWalk(config *configs.Config, prevRunState *states.State, o
|
||||
return plan, diags
|
||||
}
|
||||
|
||||
func (c *Context) planGraph(config *configs.Config, prevRunState *states.State, opts *PlanOpts) (*Graph, walkOperation, tfdiags.Diagnostics) {
|
||||
func (c *Context) planGraph(config *configs.Config, prevRunState *states.State, opts *PlanOpts, providerFunctionTracker ProviderFunctionMapping) (*Graph, walkOperation, tfdiags.Diagnostics) {
|
||||
switch mode := opts.Mode; mode {
|
||||
case plans.NormalMode:
|
||||
graph, diags := (&PlanGraphBuilder{
|
||||
Config: config,
|
||||
State: prevRunState,
|
||||
RootVariableValues: opts.SetVariables,
|
||||
Plugins: c.plugins,
|
||||
Targets: opts.Targets,
|
||||
ForceReplace: opts.ForceReplace,
|
||||
skipRefresh: opts.SkipRefresh,
|
||||
preDestroyRefresh: opts.PreDestroyRefresh,
|
||||
Operation: walkPlan,
|
||||
ExternalReferences: opts.ExternalReferences,
|
||||
ImportTargets: opts.ImportTargets,
|
||||
GenerateConfigPath: opts.GenerateConfigPath,
|
||||
EndpointsToRemove: opts.EndpointsToRemove,
|
||||
Config: config,
|
||||
State: prevRunState,
|
||||
RootVariableValues: opts.SetVariables,
|
||||
Plugins: c.plugins,
|
||||
Targets: opts.Targets,
|
||||
ForceReplace: opts.ForceReplace,
|
||||
skipRefresh: opts.SkipRefresh,
|
||||
preDestroyRefresh: opts.PreDestroyRefresh,
|
||||
Operation: walkPlan,
|
||||
ExternalReferences: opts.ExternalReferences,
|
||||
ImportTargets: opts.ImportTargets,
|
||||
GenerateConfigPath: opts.GenerateConfigPath,
|
||||
EndpointsToRemove: opts.EndpointsToRemove,
|
||||
ProviderFunctionTracker: providerFunctionTracker,
|
||||
}).Build(addrs.RootModuleInstance)
|
||||
return graph, walkPlan, diags
|
||||
case plans.RefreshOnlyMode:
|
||||
graph, diags := (&PlanGraphBuilder{
|
||||
Config: config,
|
||||
State: prevRunState,
|
||||
RootVariableValues: opts.SetVariables,
|
||||
Plugins: c.plugins,
|
||||
Targets: opts.Targets,
|
||||
skipRefresh: opts.SkipRefresh,
|
||||
skipPlanChanges: true, // this activates "refresh only" mode.
|
||||
Operation: walkPlan,
|
||||
ExternalReferences: opts.ExternalReferences,
|
||||
Config: config,
|
||||
State: prevRunState,
|
||||
RootVariableValues: opts.SetVariables,
|
||||
Plugins: c.plugins,
|
||||
Targets: opts.Targets,
|
||||
skipRefresh: opts.SkipRefresh,
|
||||
skipPlanChanges: true, // this activates "refresh only" mode.
|
||||
Operation: walkPlan,
|
||||
ExternalReferences: opts.ExternalReferences,
|
||||
ProviderFunctionTracker: providerFunctionTracker,
|
||||
}).Build(addrs.RootModuleInstance)
|
||||
return graph, walkPlan, diags
|
||||
case plans.DestroyMode:
|
||||
graph, diags := (&PlanGraphBuilder{
|
||||
Config: config,
|
||||
State: prevRunState,
|
||||
RootVariableValues: opts.SetVariables,
|
||||
Plugins: c.plugins,
|
||||
Targets: opts.Targets,
|
||||
skipRefresh: opts.SkipRefresh,
|
||||
Operation: walkPlanDestroy,
|
||||
Config: config,
|
||||
State: prevRunState,
|
||||
RootVariableValues: opts.SetVariables,
|
||||
Plugins: c.plugins,
|
||||
Targets: opts.Targets,
|
||||
skipRefresh: opts.SkipRefresh,
|
||||
Operation: walkPlanDestroy,
|
||||
ProviderFunctionTracker: providerFunctionTracker,
|
||||
}).Build(addrs.RootModuleInstance)
|
||||
return graph, walkPlanDestroy, diags
|
||||
default:
|
||||
@ -962,7 +967,7 @@ func (c *Context) PlanGraphForUI(config *configs.Config, prevRunState *states.St
|
||||
|
||||
opts := &PlanOpts{Mode: mode}
|
||||
|
||||
graph, _, moreDiags := c.planGraph(config, prevRunState, opts)
|
||||
graph, _, moreDiags := c.planGraph(config, prevRunState, opts, make(ProviderFunctionMapping))
|
||||
diags = diags.Append(moreDiags)
|
||||
return graph, diags
|
||||
}
|
||||
|
@ -60,12 +60,15 @@ func (c *Context) Validate(config *configs.Config) tfdiags.Diagnostics {
|
||||
}
|
||||
}
|
||||
|
||||
providerFunctionTracker := make(ProviderFunctionMapping)
|
||||
|
||||
graph, moreDiags := (&PlanGraphBuilder{
|
||||
Config: config,
|
||||
Plugins: c.plugins,
|
||||
State: states.NewState(),
|
||||
RootVariableValues: varValues,
|
||||
Operation: walkValidate,
|
||||
Config: config,
|
||||
Plugins: c.plugins,
|
||||
State: states.NewState(),
|
||||
RootVariableValues: varValues,
|
||||
Operation: walkValidate,
|
||||
ProviderFunctionTracker: providerFunctionTracker,
|
||||
}).Build(addrs.RootModuleInstance)
|
||||
diags = diags.Append(moreDiags)
|
||||
if moreDiags.HasErrors() {
|
||||
@ -73,7 +76,8 @@ func (c *Context) Validate(config *configs.Config) tfdiags.Diagnostics {
|
||||
}
|
||||
|
||||
walker, walkDiags := c.walk(graph, walkValidate, &graphWalkOpts{
|
||||
Config: config,
|
||||
Config: config,
|
||||
ProviderFunctionTracker: providerFunctionTracker,
|
||||
})
|
||||
diags = diags.Append(walker.NonFatalDiagnostics)
|
||||
diags = diags.Append(walkDiags)
|
||||
|
@ -2487,84 +2487,3 @@ locals {
|
||||
t.Fatalf("expected deprecated warning, got: %q\n", warn)
|
||||
}
|
||||
}
|
||||
|
||||
func TextContext2Validate_providerFunctions(t *testing.T) {
|
||||
p := testProvider("aws")
|
||||
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
|
||||
Functions: map[string]providers.FunctionSpec{
|
||||
"arn_parse": providers.FunctionSpec{
|
||||
Parameters: []providers.FunctionParameterSpec{{
|
||||
Name: "arn",
|
||||
Type: cty.String,
|
||||
}},
|
||||
Return: cty.Bool,
|
||||
},
|
||||
},
|
||||
}
|
||||
p.CallFunctionResponse = &providers.CallFunctionResponse{
|
||||
Result: cty.True,
|
||||
}
|
||||
m := testModuleInline(t, map[string]string{
|
||||
"main.tf": `
|
||||
terraform {
|
||||
required_providers {
|
||||
aws = ">=5.70.0"
|
||||
}
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
region="us-east-1"
|
||||
}
|
||||
|
||||
module "mod" {
|
||||
source = "./mod"
|
||||
providers = {
|
||||
aws = aws
|
||||
}
|
||||
}
|
||||
`,
|
||||
"mod/mod.tf": `
|
||||
terraform {
|
||||
required_providers {
|
||||
aws = ">=5.70.0"
|
||||
}
|
||||
}
|
||||
|
||||
variable "obfmod" {
|
||||
type = object({
|
||||
arns = optional(list(string))
|
||||
})
|
||||
description = "Configuration for xxx."
|
||||
|
||||
validation {
|
||||
condition = alltrue([
|
||||
for arn in var.obfmod.arns: can(provider::aws::arn_parse(arn))
|
||||
])
|
||||
error_message = "All arns MUST BE a valid AWS ARN format."
|
||||
}
|
||||
|
||||
default = {
|
||||
arns = [
|
||||
"arn:partition:service:region:account-id:resource-id",
|
||||
]
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
diags := ctx.Validate(m)
|
||||
warn := diags.ErrWithWarnings().Error()
|
||||
if !strings.Contains(warn, `The attribute "foo" is deprecated`) {
|
||||
t.Fatalf("expected deprecated warning, got: %q\n", warn)
|
||||
}
|
||||
|
||||
if !p.CallFunctionCalled {
|
||||
t.Fatalf("Expected function call")
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +44,8 @@ type graphWalkOpts struct {
|
||||
PlanTimeTimestamp time.Time
|
||||
|
||||
MoveResults refactoring.MoveResults
|
||||
|
||||
ProviderFunctionTracker ProviderFunctionMapping
|
||||
}
|
||||
|
||||
func (c *Context) walk(graph *Graph, operation walkOperation, opts *graphWalkOpts) (*ContextGraphWalker, tfdiags.Diagnostics) {
|
||||
@ -140,19 +142,20 @@ func (c *Context) graphWalker(operation walkOperation, opts *graphWalkOpts) *Con
|
||||
}
|
||||
|
||||
return &ContextGraphWalker{
|
||||
Context: c,
|
||||
State: state,
|
||||
Config: opts.Config,
|
||||
RefreshState: refreshState,
|
||||
PrevRunState: prevRunState,
|
||||
Changes: changes.SyncWrapper(),
|
||||
Checks: checkState,
|
||||
InstanceExpander: instances.NewExpander(),
|
||||
MoveResults: opts.MoveResults,
|
||||
ImportResolver: NewImportResolver(),
|
||||
Operation: operation,
|
||||
StopContext: c.runContext,
|
||||
PlanTimestamp: opts.PlanTimeTimestamp,
|
||||
Encryption: c.encryption,
|
||||
Context: c,
|
||||
State: state,
|
||||
Config: opts.Config,
|
||||
RefreshState: refreshState,
|
||||
PrevRunState: prevRunState,
|
||||
Changes: changes.SyncWrapper(),
|
||||
Checks: checkState,
|
||||
InstanceExpander: instances.NewExpander(),
|
||||
MoveResults: opts.MoveResults,
|
||||
ImportResolver: NewImportResolver(),
|
||||
Operation: operation,
|
||||
StopContext: c.runContext,
|
||||
PlanTimestamp: opts.PlanTimeTimestamp,
|
||||
Encryption: c.encryption,
|
||||
ProviderFunctionTracker: opts.ProviderFunctionTracker,
|
||||
}
|
||||
}
|
||||
|
@ -73,15 +73,16 @@ type BuiltinEvalContext struct {
|
||||
ProvisionerLock *sync.Mutex
|
||||
ProvisionerCache map[string]provisioners.Interface
|
||||
|
||||
ChangesValue *plans.ChangesSync
|
||||
StateValue *states.SyncState
|
||||
ChecksValue *checks.State
|
||||
RefreshStateValue *states.SyncState
|
||||
PrevRunStateValue *states.SyncState
|
||||
InstanceExpanderValue *instances.Expander
|
||||
MoveResultsValue refactoring.MoveResults
|
||||
ImportResolverValue *ImportResolver
|
||||
Encryption encryption.Encryption
|
||||
ChangesValue *plans.ChangesSync
|
||||
StateValue *states.SyncState
|
||||
ChecksValue *checks.State
|
||||
RefreshStateValue *states.SyncState
|
||||
PrevRunStateValue *states.SyncState
|
||||
InstanceExpanderValue *instances.Expander
|
||||
MoveResultsValue refactoring.MoveResults
|
||||
ImportResolverValue *ImportResolver
|
||||
Encryption encryption.Encryption
|
||||
ProviderFunctionTracker ProviderFunctionMapping
|
||||
}
|
||||
|
||||
// BuiltinEvalContext implements EvalContext
|
||||
@ -432,7 +433,30 @@ 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) {
|
||||
return evalContextProviderFunction(ctx.Provider, mc, ctx.Evaluator.Operation, pf, rng)
|
||||
absPc, ok := ctx.ProviderFunctionTracker.Lookup(ctx.PathValue.Module(), pf)
|
||||
if !ok {
|
||||
// This should not be possible if references are tracked correctly
|
||||
return nil, tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "BUG: Uninitialized function provider",
|
||||
Detail: fmt.Sprintf("Provider function %q has not been tracked properly", pf),
|
||||
Subject: rng.ToHCL().Ptr(),
|
||||
})
|
||||
}
|
||||
|
||||
provider := ctx.Provider(absPc)
|
||||
|
||||
if provider == nil {
|
||||
// This should not be possible if references are tracked correctly
|
||||
return nil, tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "BUG: Uninitialized function provider",
|
||||
Detail: fmt.Sprintf("Provider %q has not yet been initialized", absPc.String()),
|
||||
Subject: rng.ToHCL().Ptr(),
|
||||
})
|
||||
}
|
||||
|
||||
return evalContextProviderFunction(provider, ctx.Evaluator.Operation, pf, rng)
|
||||
})
|
||||
scope.SetActiveExperiments(mc.Module.ActiveExperiments)
|
||||
|
||||
|
@ -245,7 +245,7 @@ func evalVariableValidations(addr addrs.AbsInputVariableInstance, config *config
|
||||
continue
|
||||
}
|
||||
|
||||
hclCtx, ctxDiags := ctx.EvaluationScope(nil, nil, EvalDataForNoInstanceKey).EvalContext(append(condFuncs, errFuncs...))
|
||||
hclCtx, ctxDiags := ctx.WithPath(addr.Module).EvaluationScope(nil, nil, EvalDataForNoInstanceKey).EvalContext(append(condFuncs, errFuncs...))
|
||||
diags = diags.Append(ctxDiags)
|
||||
if diags.HasErrors() {
|
||||
continue
|
||||
|
@ -59,6 +59,8 @@ type ApplyGraphBuilder struct {
|
||||
// nodes that should not be pruned even if they are not referenced within
|
||||
// the actual graph.
|
||||
ExternalReferences []*addrs.Reference
|
||||
|
||||
ProviderFunctionTracker ProviderFunctionMapping
|
||||
}
|
||||
|
||||
// See GraphBuilder
|
||||
@ -147,7 +149,7 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer {
|
||||
&AttachSchemaTransformer{Plugins: b.Plugins, Config: b.Config},
|
||||
|
||||
// After schema transformer, we can add function references
|
||||
&ProviderFunctionTransformer{Config: b.Config},
|
||||
&ProviderFunctionTransformer{Config: b.Config, ProviderFunctionTracker: b.ProviderFunctionTracker},
|
||||
|
||||
// Remove unused providers and proxies
|
||||
&PruneProviderTransformer{},
|
||||
|
@ -43,6 +43,8 @@ type EvalGraphBuilder struct {
|
||||
// Plugins is a library of plug-in components (providers and
|
||||
// provisioners) available for use.
|
||||
Plugins *contextPlugins
|
||||
|
||||
ProviderFunctionTracker ProviderFunctionMapping
|
||||
}
|
||||
|
||||
// See GraphBuilder
|
||||
@ -90,7 +92,7 @@ func (b *EvalGraphBuilder) Steps() []GraphTransformer {
|
||||
&AttachSchemaTransformer{Plugins: b.Plugins, Config: b.Config},
|
||||
|
||||
// After schema transformer, we can add function references
|
||||
&ProviderFunctionTransformer{Config: b.Config},
|
||||
&ProviderFunctionTransformer{Config: b.Config, ProviderFunctionTracker: b.ProviderFunctionTracker},
|
||||
|
||||
// Remove unused providers and proxies
|
||||
&PruneProviderTransformer{},
|
||||
|
@ -92,6 +92,8 @@ type PlanGraphBuilder struct {
|
||||
//
|
||||
// If empty, then config will not be generated.
|
||||
GenerateConfigPath string
|
||||
|
||||
ProviderFunctionTracker ProviderFunctionMapping
|
||||
}
|
||||
|
||||
// See GraphBuilder
|
||||
@ -200,7 +202,7 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
|
||||
&AttachSchemaTransformer{Plugins: b.Plugins, Config: b.Config},
|
||||
|
||||
// After schema transformer, we can add function references
|
||||
&ProviderFunctionTransformer{Config: b.Config},
|
||||
&ProviderFunctionTransformer{Config: b.Config, ProviderFunctionTracker: b.ProviderFunctionTracker},
|
||||
|
||||
// Remove unused providers and proxies
|
||||
&PruneProviderTransformer{},
|
||||
|
@ -31,21 +31,22 @@ type ContextGraphWalker struct {
|
||||
NullGraphWalker
|
||||
|
||||
// Configurable values
|
||||
Context *Context
|
||||
State *states.SyncState // Used for safe concurrent access to state
|
||||
RefreshState *states.SyncState // Used for safe concurrent access to state
|
||||
PrevRunState *states.SyncState // Used for safe concurrent access to state
|
||||
Changes *plans.ChangesSync // Used for safe concurrent writes to changes
|
||||
Checks *checks.State // Used for safe concurrent writes of checkable objects and their check results
|
||||
InstanceExpander *instances.Expander // Tracks our gradual expansion of module and resource instances
|
||||
ImportResolver *ImportResolver // Tracks import targets as they are being resolved
|
||||
MoveResults refactoring.MoveResults // Read-only record of earlier processing of move statements
|
||||
Operation walkOperation
|
||||
StopContext context.Context
|
||||
RootVariableValues InputValues
|
||||
Config *configs.Config
|
||||
PlanTimestamp time.Time
|
||||
Encryption encryption.Encryption
|
||||
Context *Context
|
||||
State *states.SyncState // Used for safe concurrent access to state
|
||||
RefreshState *states.SyncState // Used for safe concurrent access to state
|
||||
PrevRunState *states.SyncState // Used for safe concurrent access to state
|
||||
Changes *plans.ChangesSync // Used for safe concurrent writes to changes
|
||||
Checks *checks.State // Used for safe concurrent writes of checkable objects and their check results
|
||||
InstanceExpander *instances.Expander // Tracks our gradual expansion of module and resource instances
|
||||
ImportResolver *ImportResolver // Tracks import targets as they are being resolved
|
||||
MoveResults refactoring.MoveResults // Read-only record of earlier processing of move statements
|
||||
Operation walkOperation
|
||||
StopContext context.Context
|
||||
RootVariableValues InputValues
|
||||
Config *configs.Config
|
||||
PlanTimestamp time.Time
|
||||
Encryption encryption.Encryption
|
||||
ProviderFunctionTracker ProviderFunctionMapping
|
||||
|
||||
// This is an output. Do not set this, nor read it while a graph walk
|
||||
// is in progress.
|
||||
@ -99,27 +100,28 @@ func (w *ContextGraphWalker) EvalContext() EvalContext {
|
||||
}
|
||||
|
||||
ctx := &BuiltinEvalContext{
|
||||
StopContext: w.StopContext,
|
||||
Hooks: w.Context.hooks,
|
||||
InputValue: w.Context.uiInput,
|
||||
InstanceExpanderValue: w.InstanceExpander,
|
||||
Plugins: w.Context.plugins,
|
||||
MoveResultsValue: w.MoveResults,
|
||||
ImportResolverValue: w.ImportResolver,
|
||||
ProviderCache: w.providerCache,
|
||||
ProviderInputConfig: w.Context.providerInputConfig,
|
||||
ProviderLock: &w.providerLock,
|
||||
ProvisionerCache: w.provisionerCache,
|
||||
ProvisionerLock: &w.provisionerLock,
|
||||
ChangesValue: w.Changes,
|
||||
ChecksValue: w.Checks,
|
||||
StateValue: w.State,
|
||||
RefreshStateValue: w.RefreshState,
|
||||
PrevRunStateValue: w.PrevRunState,
|
||||
Evaluator: evaluator,
|
||||
VariableValues: w.variableValues,
|
||||
VariableValuesLock: &w.variableValuesLock,
|
||||
Encryption: w.Encryption,
|
||||
StopContext: w.StopContext,
|
||||
Hooks: w.Context.hooks,
|
||||
InputValue: w.Context.uiInput,
|
||||
InstanceExpanderValue: w.InstanceExpander,
|
||||
Plugins: w.Context.plugins,
|
||||
MoveResultsValue: w.MoveResults,
|
||||
ImportResolverValue: w.ImportResolver,
|
||||
ProviderCache: w.providerCache,
|
||||
ProviderInputConfig: w.Context.providerInputConfig,
|
||||
ProviderLock: &w.providerLock,
|
||||
ProvisionerCache: w.provisionerCache,
|
||||
ProvisionerLock: &w.provisionerLock,
|
||||
ChangesValue: w.Changes,
|
||||
ChecksValue: w.Checks,
|
||||
StateValue: w.State,
|
||||
RefreshStateValue: w.RefreshState,
|
||||
PrevRunStateValue: w.PrevRunState,
|
||||
Evaluator: evaluator,
|
||||
VariableValues: w.variableValues,
|
||||
VariableValuesLock: &w.variableValuesLock,
|
||||
Encryption: w.Encryption,
|
||||
ProviderFunctionTracker: w.ProviderFunctionTracker,
|
||||
}
|
||||
|
||||
return ctx
|
||||
|
@ -111,29 +111,29 @@ func (ctx *TestContext) evaluate(state *states.SyncState, changes *plans.Changes
|
||||
}
|
||||
}()
|
||||
|
||||
providerSupplier := func(addr addrs.AbsProviderConfig) providers.Interface {
|
||||
providerSupplier := func(addr addrs.Provider) providers.Interface {
|
||||
providerInstanceLock.Lock()
|
||||
defer providerInstanceLock.Unlock()
|
||||
|
||||
if inst, ok := providerInstances[addr.Provider]; ok {
|
||||
if inst, ok := providerInstances[addr]; ok {
|
||||
return inst
|
||||
}
|
||||
|
||||
factory, ok := ctx.plugins.providerFactories[addr.Provider]
|
||||
factory, ok := ctx.plugins.providerFactories[addr]
|
||||
if !ok {
|
||||
log.Printf("[WARN] Unable to find provider %s in test context", addr)
|
||||
providerInstances[addr.Provider] = nil
|
||||
providerInstances[addr] = nil
|
||||
return nil
|
||||
}
|
||||
log.Printf("[INFO] Starting test provider %s", addr)
|
||||
inst, err := factory()
|
||||
if err != nil {
|
||||
log.Printf("[WARN] Unable to start provider %s in test context", addr)
|
||||
providerInstances[addr.Provider] = nil
|
||||
providerInstances[addr] = nil
|
||||
return nil
|
||||
} else {
|
||||
log.Printf("[INFO] Shutting down test provider %s", addr)
|
||||
providerInstances[addr.Provider] = inst
|
||||
providerInstances[addr] = inst
|
||||
return inst
|
||||
}
|
||||
}
|
||||
@ -144,7 +144,21 @@ func (ctx *TestContext) evaluate(state *states.SyncState, changes *plans.Changes
|
||||
PureOnly: operation != walkApply,
|
||||
PlanTimestamp: ctx.Plan.Timestamp,
|
||||
ProviderFunctions: func(pf addrs.ProviderFunction, rng tfdiags.SourceRange) (*function.Function, tfdiags.Diagnostics) {
|
||||
return evalContextProviderFunction(providerSupplier, ctx.Config, walkPlan, pf, rng)
|
||||
// This is a simpler flow than what is allowed during normal exection.
|
||||
// We only support non-configured functions here.
|
||||
pr, ok := ctx.Config.Module.ProviderRequirements.RequiredProviders[pf.ProviderName]
|
||||
if !ok {
|
||||
return nil, tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unknown function provider",
|
||||
Detail: fmt.Sprintf("Provider %q does not exist within the required_providers of this module", pf.ProviderName),
|
||||
Subject: rng.ToHCL().Ptr(),
|
||||
})
|
||||
}
|
||||
|
||||
provider := providerSupplier(pr.Type)
|
||||
|
||||
return evalContextProviderFunction(provider, walkPlan, pf, rng)
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"github.com/opentofu/opentofu/internal/addrs"
|
||||
"github.com/opentofu/opentofu/internal/configs"
|
||||
"github.com/opentofu/opentofu/internal/dag"
|
||||
"github.com/opentofu/opentofu/internal/providers"
|
||||
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||
)
|
||||
|
||||
@ -212,10 +213,36 @@ func (t *ProviderTransformer) Transform(g *Graph) error {
|
||||
return diags.Err()
|
||||
}
|
||||
|
||||
// ProviderFunctionReference is all the information needed to identify
|
||||
// the provider required in a given module path. Alternatively, this
|
||||
// could be seen as a Module path + addrs.LocalProviderConfig.
|
||||
type ProviderFunctionReference struct {
|
||||
ModulePath string
|
||||
ProviderName string
|
||||
ProviderAlias string
|
||||
}
|
||||
|
||||
// ProviderFunctionMapping maps a provider used by functions at a given location in the graph to the actual AbsProviderConfig
|
||||
// that's required. This is due to the provider inheritence logic and proxy logic in the below
|
||||
// transformer needing to be known in other parts of the application.
|
||||
// Ideally, this would not be needed and be built like the ProviderTransformer. Unfortunately, it's
|
||||
// a significant refactor to get to that point which adds a lot of complexity.
|
||||
type ProviderFunctionMapping map[ProviderFunctionReference]addrs.AbsProviderConfig
|
||||
|
||||
func (m ProviderFunctionMapping) Lookup(module addrs.Module, pf addrs.ProviderFunction) (addrs.AbsProviderConfig, bool) {
|
||||
addr, ok := m[ProviderFunctionReference{
|
||||
ModulePath: module.String(),
|
||||
ProviderName: pf.ProviderName,
|
||||
ProviderAlias: pf.ProviderAlias,
|
||||
}]
|
||||
return addr, ok
|
||||
}
|
||||
|
||||
// ProviderFunctionTransformer is a GraphTransformer that maps nodes which reference functions to providers
|
||||
// within the graph. This will error if there are any provider functions that don't map to known providers.
|
||||
type ProviderFunctionTransformer struct {
|
||||
Config *configs.Config
|
||||
Config *configs.Config
|
||||
ProviderFunctionTracker ProviderFunctionMapping
|
||||
}
|
||||
|
||||
func (t *ProviderFunctionTransformer) Transform(g *Graph) error {
|
||||
@ -227,26 +254,20 @@ func (t *ProviderFunctionTransformer) Transform(g *Graph) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Locate all providers in the graph
|
||||
providers := providerVertexMap(g)
|
||||
|
||||
type providerReference struct {
|
||||
path string
|
||||
name string
|
||||
alias string
|
||||
}
|
||||
// Locate all providerVerts in the graph
|
||||
providerVerts := providerVertexMap(g)
|
||||
// LuT of provider reference -> provider vertex
|
||||
providerReferences := make(map[providerReference]dag.Vertex)
|
||||
providerReferences := make(map[ProviderFunctionReference]dag.Vertex)
|
||||
|
||||
for _, v := range g.Vertices() {
|
||||
// Provider function references
|
||||
if nr, ok := v.(GraphNodeReferencer); ok && t.Config != nil {
|
||||
for _, ref := range nr.References() {
|
||||
if pf, ok := ref.Subject.(addrs.ProviderFunction); ok {
|
||||
key := providerReference{
|
||||
path: nr.ModulePath().String(),
|
||||
name: pf.ProviderName,
|
||||
alias: pf.ProviderAlias,
|
||||
key := ProviderFunctionReference{
|
||||
ModulePath: nr.ModulePath().String(),
|
||||
ProviderName: pf.ProviderName,
|
||||
ProviderAlias: pf.ProviderAlias,
|
||||
}
|
||||
|
||||
// We already know about this provider and can link directly
|
||||
@ -291,12 +312,35 @@ func (t *ProviderFunctionTransformer) Transform(g *Graph) error {
|
||||
log.Printf("[TRACE] ProviderFunctionTransformer: %s in %s is provided by %s", pf, dag.VertexName(v), absPc)
|
||||
|
||||
// Lookup provider via full address
|
||||
provider := providers[absPc.String()]
|
||||
provider := providerVerts[absPc.String()]
|
||||
|
||||
if provider != nil {
|
||||
// Providers with configuration will already exist within the graph and can be directly referenced
|
||||
log.Printf("[TRACE] ProviderFunctionTransformer: exact match for %s serving %s", absPc, dag.VertexName(v))
|
||||
} else {
|
||||
// At this point, all provider schemas should be loaded. We
|
||||
// can now check to see if configuration is optional for this function.
|
||||
providerSchema, ok := providers.SchemaCache.Get(absPc.Provider)
|
||||
if !ok {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unknown provider for function",
|
||||
Detail: fmt.Sprintf("Provider %q does not have it's schema initialized", absPc.Provider),
|
||||
Subject: ref.SourceRange.ToHCL().Ptr(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
_, functionOk := providerSchema.Functions[pf.Function]
|
||||
if !functionOk {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unknown provider function",
|
||||
Detail: fmt.Sprintf("Provider %q does not have a function %q or has not been configured", absPc, pf.Function),
|
||||
Subject: ref.SourceRange.ToHCL().Ptr(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// If this provider doesn't need to be configured then we can just
|
||||
// stub it out with an init-only provider node, which will just
|
||||
// start up the provider and fetch its schema.
|
||||
@ -304,22 +348,24 @@ func (t *ProviderFunctionTransformer) Transform(g *Graph) error {
|
||||
Module: addrs.RootModule,
|
||||
Provider: absPc.Provider,
|
||||
}
|
||||
if provider, ok = providers[stubAddr.String()]; !ok {
|
||||
stub := &NodeEvalableProvider{
|
||||
// Try to look up an existing stub
|
||||
provider, ok = providerVerts[stubAddr.String()]
|
||||
// If it does not exist, create it
|
||||
if !ok {
|
||||
log.Printf("[TRACE] ProviderFunctionTransformer: creating init-only node for %s", stubAddr)
|
||||
|
||||
provider = &NodeEvalableProvider{
|
||||
&NodeAbstractProvider{
|
||||
Addr: stubAddr,
|
||||
},
|
||||
}
|
||||
providers[stubAddr.String()] = stub
|
||||
log.Printf("[TRACE] ProviderFunctionTransformer: creating init-only node for %s", stubAddr)
|
||||
provider = stub
|
||||
providerVerts[stubAddr.String()] = provider
|
||||
g.Add(provider)
|
||||
}
|
||||
}
|
||||
|
||||
// see if this is a proxy provider pointing to another concrete config
|
||||
if p, ok := provider.(*graphNodeProxyProvider); ok {
|
||||
g.Remove(p)
|
||||
provider = p.Target()
|
||||
}
|
||||
|
||||
@ -328,6 +374,7 @@ func (t *ProviderFunctionTransformer) Transform(g *Graph) error {
|
||||
|
||||
// Save for future lookups
|
||||
providerReferences[key] = provider
|
||||
t.ProviderFunctionTracker[key] = provider.ProviderAddr()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user