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]
|
s, ok := c.m[p]
|
||||||
return s, ok
|
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() {
|
if diags.HasErrors() {
|
||||||
return nil, diags
|
return nil, diags
|
||||||
}
|
}
|
||||||
@ -72,6 +74,7 @@ func (c *Context) Apply(plan *plans.Plan, config *configs.Config) (*states.State
|
|||||||
|
|
||||||
// We also want to propagate the timestamp from the plan file.
|
// 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(walker.NonFatalDiagnostics)
|
||||||
diags = diags.Append(walkDiags)
|
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
|
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
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
variables := InputValues{}
|
variables := InputValues{}
|
||||||
@ -180,6 +184,7 @@ func (c *Context) applyGraph(plan *plans.Plan, config *configs.Config, validate
|
|||||||
ForceReplace: plan.ForceReplaceAddrs,
|
ForceReplace: plan.ForceReplaceAddrs,
|
||||||
Operation: operation,
|
Operation: operation,
|
||||||
ExternalReferences: plan.ExternalReferences,
|
ExternalReferences: plan.ExternalReferences,
|
||||||
|
ProviderFunctionTracker: providerFunctionTracker,
|
||||||
}).Build(addrs.RootModuleInstance)
|
}).Build(addrs.RootModuleInstance)
|
||||||
diags = diags.Append(moreDiags)
|
diags = diags.Append(moreDiags)
|
||||||
if moreDiags.HasErrors() {
|
if moreDiags.HasErrors() {
|
||||||
@ -204,7 +209,7 @@ func (c *Context) ApplyGraphForUI(plan *plans.Plan, config *configs.Config) (*Gr
|
|||||||
|
|
||||||
var diags tfdiags.Diagnostics
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
graph, _, moreDiags := c.applyGraph(plan, config, false)
|
graph, _, moreDiags := c.applyGraph(plan, config, false, make(ProviderFunctionMapping))
|
||||||
diags = diags.Append(moreDiags)
|
diags = diags.Append(moreDiags)
|
||||||
return graph, diags
|
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")
|
log.Printf("[DEBUG] Building and walking 'eval' graph")
|
||||||
|
|
||||||
|
providerFunctionTracker := make(ProviderFunctionMapping)
|
||||||
|
|
||||||
graph, moreDiags := (&EvalGraphBuilder{
|
graph, moreDiags := (&EvalGraphBuilder{
|
||||||
Config: config,
|
Config: config,
|
||||||
State: state,
|
State: state,
|
||||||
RootVariableValues: variables,
|
RootVariableValues: variables,
|
||||||
Plugins: c.plugins,
|
Plugins: c.plugins,
|
||||||
|
ProviderFunctionTracker: providerFunctionTracker,
|
||||||
}).Build(addrs.RootModuleInstance)
|
}).Build(addrs.RootModuleInstance)
|
||||||
diags = diags.Append(moreDiags)
|
diags = diags.Append(moreDiags)
|
||||||
if moreDiags.HasErrors() {
|
if moreDiags.HasErrors() {
|
||||||
@ -78,6 +81,7 @@ func (c *Context) Eval(config *configs.Config, state *states.State, moduleAddr a
|
|||||||
walkOpts := &graphWalkOpts{
|
walkOpts := &graphWalkOpts{
|
||||||
InputState: state,
|
InputState: state,
|
||||||
Config: config,
|
Config: config,
|
||||||
|
ProviderFunctionTracker: providerFunctionTracker,
|
||||||
}
|
}
|
||||||
|
|
||||||
walker, moreDiags = c.walk(graph, walkEval, walkOpts)
|
walker, moreDiags = c.walk(graph, walkEval, walkOpts)
|
||||||
|
@ -11,7 +11,6 @@ import (
|
|||||||
|
|
||||||
"github.com/hashicorp/hcl/v2"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/opentofu/opentofu/internal/addrs"
|
"github.com/opentofu/opentofu/internal/addrs"
|
||||||
"github.com/opentofu/opentofu/internal/configs"
|
|
||||||
"github.com/opentofu/opentofu/internal/providers"
|
"github.com/opentofu/opentofu/internal/providers"
|
||||||
"github.com/opentofu/opentofu/internal/tfdiags"
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
@ -20,62 +19,9 @@ 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(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
|
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
|
// First try to look up the function from provider schema
|
||||||
schema := provider.GetProviderSchema()
|
schema := provider.GetProviderSchema()
|
||||||
if schema.Diagnostics.HasErrors() {
|
if schema.Diagnostics.HasErrors() {
|
||||||
@ -117,7 +63,7 @@ func evalContextProviderFunction(providers func(addrs.AbsProviderConfig) provide
|
|||||||
return nil, diags.Append(&hcl.Diagnostic{
|
return nil, diags.Append(&hcl.Diagnostic{
|
||||||
Severity: hcl.DiagError,
|
Severity: hcl.DiagError,
|
||||||
Summary: "Function not found in provider",
|
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(),
|
Subject: rng.ToHCL().Ptr(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/hashicorp/hcl/v2"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
"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/lang/marks"
|
"github.com/opentofu/opentofu/internal/lang/marks"
|
||||||
"github.com/opentofu/opentofu/internal/providers"
|
"github.com/opentofu/opentofu/internal/providers"
|
||||||
"github.com/opentofu/opentofu/internal/tfdiags"
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||||
@ -117,51 +117,15 @@ func TestFunctions(t *testing.T) {
|
|||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := addrs.NewDefaultProvider("mock")
|
|
||||||
rng := tfdiags.SourceRange{}
|
rng := tfdiags.SourceRange{}
|
||||||
providerFunc := func(fn string) addrs.ProviderFunction {
|
providerFunc := func(fn string) addrs.ProviderFunction {
|
||||||
pf, _ := addrs.ParseFunction(fn).AsProviderFunction()
|
pf, _ := addrs.ParseFunction(fn).AsProviderFunction()
|
||||||
return pf
|
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)
|
// Function missing (validate)
|
||||||
mockProvider.GetFunctionsCalled = false
|
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() {
|
if diags.HasErrors() {
|
||||||
t.Fatal(diags.Err())
|
t.Fatal(diags.Err())
|
||||||
}
|
}
|
||||||
@ -171,11 +135,11 @@ func TestFunctions(t *testing.T) {
|
|||||||
|
|
||||||
// Function missing (Non-validate)
|
// Function missing (Non-validate)
|
||||||
mockProvider.GetFunctionsCalled = false
|
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() {
|
if !diags.HasErrors() {
|
||||||
t.Fatal("expected unknown function")
|
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())
|
t.Fatal(diags.Err())
|
||||||
}
|
}
|
||||||
if !mockProvider.GetFunctionsCalled {
|
if !mockProvider.GetFunctionsCalled {
|
||||||
@ -193,7 +157,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.Provider, cfg, walkPlan, pf, rng)
|
impl, diags := evalContextProviderFunction(mockProvider, walkPlan, pf, rng)
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
t.Fatal(diags.Err())
|
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,6 +251,8 @@ func (c *Context) Import(config *configs.Config, prevRunState *states.State, opt
|
|||||||
|
|
||||||
variables := opts.SetVariables
|
variables := opts.SetVariables
|
||||||
|
|
||||||
|
providerFunctionTracker := make(ProviderFunctionMapping)
|
||||||
|
|
||||||
// Initialize our graph builder
|
// Initialize our graph builder
|
||||||
builder := &PlanGraphBuilder{
|
builder := &PlanGraphBuilder{
|
||||||
ImportTargets: opts.Targets,
|
ImportTargets: opts.Targets,
|
||||||
@ -259,6 +261,7 @@ func (c *Context) Import(config *configs.Config, prevRunState *states.State, opt
|
|||||||
RootVariableValues: variables,
|
RootVariableValues: variables,
|
||||||
Plugins: c.plugins,
|
Plugins: c.plugins,
|
||||||
Operation: walkImport,
|
Operation: walkImport,
|
||||||
|
ProviderFunctionTracker: providerFunctionTracker,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the graph
|
// Build the graph
|
||||||
@ -272,6 +275,7 @@ func (c *Context) Import(config *configs.Config, prevRunState *states.State, opt
|
|||||||
walker, walkDiags := c.walk(graph, walkImport, &graphWalkOpts{
|
walker, walkDiags := c.walk(graph, walkImport, &graphWalkOpts{
|
||||||
Config: config,
|
Config: config,
|
||||||
InputState: state,
|
InputState: state,
|
||||||
|
ProviderFunctionTracker: providerFunctionTracker,
|
||||||
})
|
})
|
||||||
diags = diags.Append(walkDiags)
|
diags = diags.Append(walkDiags)
|
||||||
if walkDiags.HasErrors() {
|
if walkDiags.HasErrors() {
|
||||||
|
@ -277,7 +277,7 @@ func (c *Context) checkApplyGraph(plan *plans.Plan, config *configs.Config) tfdi
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
log.Println("[DEBUG] building apply graph to check for errors")
|
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
|
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.
|
// strange problems that may lead to confusing error messages.
|
||||||
return nil, diags
|
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)
|
diags = diags.Append(moreDiags)
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
return nil, diags
|
return nil, diags
|
||||||
@ -691,6 +692,7 @@ func (c *Context) planWalk(config *configs.Config, prevRunState *states.State, o
|
|||||||
Changes: changes,
|
Changes: changes,
|
||||||
MoveResults: moveResults,
|
MoveResults: moveResults,
|
||||||
PlanTimeTimestamp: timestamp,
|
PlanTimeTimestamp: timestamp,
|
||||||
|
ProviderFunctionTracker: providerFunctionTracker,
|
||||||
})
|
})
|
||||||
diags = diags.Append(walker.NonFatalDiagnostics)
|
diags = diags.Append(walker.NonFatalDiagnostics)
|
||||||
diags = diags.Append(walkDiags)
|
diags = diags.Append(walkDiags)
|
||||||
@ -754,7 +756,7 @@ func (c *Context) planWalk(config *configs.Config, prevRunState *states.State, o
|
|||||||
return plan, diags
|
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 {
|
switch mode := opts.Mode; mode {
|
||||||
case plans.NormalMode:
|
case plans.NormalMode:
|
||||||
graph, diags := (&PlanGraphBuilder{
|
graph, diags := (&PlanGraphBuilder{
|
||||||
@ -771,6 +773,7 @@ func (c *Context) planGraph(config *configs.Config, prevRunState *states.State,
|
|||||||
ImportTargets: opts.ImportTargets,
|
ImportTargets: opts.ImportTargets,
|
||||||
GenerateConfigPath: opts.GenerateConfigPath,
|
GenerateConfigPath: opts.GenerateConfigPath,
|
||||||
EndpointsToRemove: opts.EndpointsToRemove,
|
EndpointsToRemove: opts.EndpointsToRemove,
|
||||||
|
ProviderFunctionTracker: providerFunctionTracker,
|
||||||
}).Build(addrs.RootModuleInstance)
|
}).Build(addrs.RootModuleInstance)
|
||||||
return graph, walkPlan, diags
|
return graph, walkPlan, diags
|
||||||
case plans.RefreshOnlyMode:
|
case plans.RefreshOnlyMode:
|
||||||
@ -784,6 +787,7 @@ func (c *Context) planGraph(config *configs.Config, prevRunState *states.State,
|
|||||||
skipPlanChanges: true, // this activates "refresh only" mode.
|
skipPlanChanges: true, // this activates "refresh only" mode.
|
||||||
Operation: walkPlan,
|
Operation: walkPlan,
|
||||||
ExternalReferences: opts.ExternalReferences,
|
ExternalReferences: opts.ExternalReferences,
|
||||||
|
ProviderFunctionTracker: providerFunctionTracker,
|
||||||
}).Build(addrs.RootModuleInstance)
|
}).Build(addrs.RootModuleInstance)
|
||||||
return graph, walkPlan, diags
|
return graph, walkPlan, diags
|
||||||
case plans.DestroyMode:
|
case plans.DestroyMode:
|
||||||
@ -795,6 +799,7 @@ func (c *Context) planGraph(config *configs.Config, prevRunState *states.State,
|
|||||||
Targets: opts.Targets,
|
Targets: opts.Targets,
|
||||||
skipRefresh: opts.SkipRefresh,
|
skipRefresh: opts.SkipRefresh,
|
||||||
Operation: walkPlanDestroy,
|
Operation: walkPlanDestroy,
|
||||||
|
ProviderFunctionTracker: providerFunctionTracker,
|
||||||
}).Build(addrs.RootModuleInstance)
|
}).Build(addrs.RootModuleInstance)
|
||||||
return graph, walkPlanDestroy, diags
|
return graph, walkPlanDestroy, diags
|
||||||
default:
|
default:
|
||||||
@ -962,7 +967,7 @@ func (c *Context) PlanGraphForUI(config *configs.Config, prevRunState *states.St
|
|||||||
|
|
||||||
opts := &PlanOpts{Mode: mode}
|
opts := &PlanOpts{Mode: mode}
|
||||||
|
|
||||||
graph, _, moreDiags := c.planGraph(config, prevRunState, opts)
|
graph, _, moreDiags := c.planGraph(config, prevRunState, opts, make(ProviderFunctionMapping))
|
||||||
diags = diags.Append(moreDiags)
|
diags = diags.Append(moreDiags)
|
||||||
return graph, diags
|
return graph, diags
|
||||||
}
|
}
|
||||||
|
@ -60,12 +60,15 @@ func (c *Context) Validate(config *configs.Config) tfdiags.Diagnostics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
providerFunctionTracker := make(ProviderFunctionMapping)
|
||||||
|
|
||||||
graph, moreDiags := (&PlanGraphBuilder{
|
graph, moreDiags := (&PlanGraphBuilder{
|
||||||
Config: config,
|
Config: config,
|
||||||
Plugins: c.plugins,
|
Plugins: c.plugins,
|
||||||
State: states.NewState(),
|
State: states.NewState(),
|
||||||
RootVariableValues: varValues,
|
RootVariableValues: varValues,
|
||||||
Operation: walkValidate,
|
Operation: walkValidate,
|
||||||
|
ProviderFunctionTracker: providerFunctionTracker,
|
||||||
}).Build(addrs.RootModuleInstance)
|
}).Build(addrs.RootModuleInstance)
|
||||||
diags = diags.Append(moreDiags)
|
diags = diags.Append(moreDiags)
|
||||||
if moreDiags.HasErrors() {
|
if moreDiags.HasErrors() {
|
||||||
@ -74,6 +77,7 @@ func (c *Context) Validate(config *configs.Config) tfdiags.Diagnostics {
|
|||||||
|
|
||||||
walker, walkDiags := c.walk(graph, walkValidate, &graphWalkOpts{
|
walker, walkDiags := c.walk(graph, walkValidate, &graphWalkOpts{
|
||||||
Config: config,
|
Config: config,
|
||||||
|
ProviderFunctionTracker: providerFunctionTracker,
|
||||||
})
|
})
|
||||||
diags = diags.Append(walker.NonFatalDiagnostics)
|
diags = diags.Append(walker.NonFatalDiagnostics)
|
||||||
diags = diags.Append(walkDiags)
|
diags = diags.Append(walkDiags)
|
||||||
|
@ -2487,84 +2487,3 @@ locals {
|
|||||||
t.Fatalf("expected deprecated warning, got: %q\n", warn)
|
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
|
PlanTimeTimestamp time.Time
|
||||||
|
|
||||||
MoveResults refactoring.MoveResults
|
MoveResults refactoring.MoveResults
|
||||||
|
|
||||||
|
ProviderFunctionTracker ProviderFunctionMapping
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) walk(graph *Graph, operation walkOperation, opts *graphWalkOpts) (*ContextGraphWalker, tfdiags.Diagnostics) {
|
func (c *Context) walk(graph *Graph, operation walkOperation, opts *graphWalkOpts) (*ContextGraphWalker, tfdiags.Diagnostics) {
|
||||||
@ -154,5 +156,6 @@ func (c *Context) graphWalker(operation walkOperation, opts *graphWalkOpts) *Con
|
|||||||
StopContext: c.runContext,
|
StopContext: c.runContext,
|
||||||
PlanTimestamp: opts.PlanTimeTimestamp,
|
PlanTimestamp: opts.PlanTimeTimestamp,
|
||||||
Encryption: c.encryption,
|
Encryption: c.encryption,
|
||||||
|
ProviderFunctionTracker: opts.ProviderFunctionTracker,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,6 +82,7 @@ type BuiltinEvalContext struct {
|
|||||||
MoveResultsValue refactoring.MoveResults
|
MoveResultsValue refactoring.MoveResults
|
||||||
ImportResolverValue *ImportResolver
|
ImportResolverValue *ImportResolver
|
||||||
Encryption encryption.Encryption
|
Encryption encryption.Encryption
|
||||||
|
ProviderFunctionTracker ProviderFunctionMapping
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuiltinEvalContext implements EvalContext
|
// 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) {
|
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)
|
scope.SetActiveExperiments(mc.Module.ActiveExperiments)
|
||||||
|
|
||||||
|
@ -245,7 +245,7 @@ func evalVariableValidations(addr addrs.AbsInputVariableInstance, config *config
|
|||||||
continue
|
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)
|
diags = diags.Append(ctxDiags)
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
continue
|
continue
|
||||||
|
@ -59,6 +59,8 @@ type ApplyGraphBuilder struct {
|
|||||||
// nodes that should not be pruned even if they are not referenced within
|
// nodes that should not be pruned even if they are not referenced within
|
||||||
// the actual graph.
|
// the actual graph.
|
||||||
ExternalReferences []*addrs.Reference
|
ExternalReferences []*addrs.Reference
|
||||||
|
|
||||||
|
ProviderFunctionTracker ProviderFunctionMapping
|
||||||
}
|
}
|
||||||
|
|
||||||
// See GraphBuilder
|
// See GraphBuilder
|
||||||
@ -147,7 +149,7 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer {
|
|||||||
&AttachSchemaTransformer{Plugins: b.Plugins, Config: b.Config},
|
&AttachSchemaTransformer{Plugins: b.Plugins, Config: b.Config},
|
||||||
|
|
||||||
// After schema transformer, we can add function references
|
// After schema transformer, we can add function references
|
||||||
&ProviderFunctionTransformer{Config: b.Config},
|
&ProviderFunctionTransformer{Config: b.Config, ProviderFunctionTracker: b.ProviderFunctionTracker},
|
||||||
|
|
||||||
// Remove unused providers and proxies
|
// Remove unused providers and proxies
|
||||||
&PruneProviderTransformer{},
|
&PruneProviderTransformer{},
|
||||||
|
@ -43,6 +43,8 @@ type EvalGraphBuilder struct {
|
|||||||
// Plugins is a library of plug-in components (providers and
|
// Plugins is a library of plug-in components (providers and
|
||||||
// provisioners) available for use.
|
// provisioners) available for use.
|
||||||
Plugins *contextPlugins
|
Plugins *contextPlugins
|
||||||
|
|
||||||
|
ProviderFunctionTracker ProviderFunctionMapping
|
||||||
}
|
}
|
||||||
|
|
||||||
// See GraphBuilder
|
// See GraphBuilder
|
||||||
@ -90,7 +92,7 @@ func (b *EvalGraphBuilder) Steps() []GraphTransformer {
|
|||||||
&AttachSchemaTransformer{Plugins: b.Plugins, Config: b.Config},
|
&AttachSchemaTransformer{Plugins: b.Plugins, Config: b.Config},
|
||||||
|
|
||||||
// After schema transformer, we can add function references
|
// After schema transformer, we can add function references
|
||||||
&ProviderFunctionTransformer{Config: b.Config},
|
&ProviderFunctionTransformer{Config: b.Config, ProviderFunctionTracker: b.ProviderFunctionTracker},
|
||||||
|
|
||||||
// Remove unused providers and proxies
|
// Remove unused providers and proxies
|
||||||
&PruneProviderTransformer{},
|
&PruneProviderTransformer{},
|
||||||
|
@ -92,6 +92,8 @@ type PlanGraphBuilder struct {
|
|||||||
//
|
//
|
||||||
// If empty, then config will not be generated.
|
// If empty, then config will not be generated.
|
||||||
GenerateConfigPath string
|
GenerateConfigPath string
|
||||||
|
|
||||||
|
ProviderFunctionTracker ProviderFunctionMapping
|
||||||
}
|
}
|
||||||
|
|
||||||
// See GraphBuilder
|
// See GraphBuilder
|
||||||
@ -200,7 +202,7 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
|
|||||||
&AttachSchemaTransformer{Plugins: b.Plugins, Config: b.Config},
|
&AttachSchemaTransformer{Plugins: b.Plugins, Config: b.Config},
|
||||||
|
|
||||||
// After schema transformer, we can add function references
|
// After schema transformer, we can add function references
|
||||||
&ProviderFunctionTransformer{Config: b.Config},
|
&ProviderFunctionTransformer{Config: b.Config, ProviderFunctionTracker: b.ProviderFunctionTracker},
|
||||||
|
|
||||||
// Remove unused providers and proxies
|
// Remove unused providers and proxies
|
||||||
&PruneProviderTransformer{},
|
&PruneProviderTransformer{},
|
||||||
|
@ -46,6 +46,7 @@ type ContextGraphWalker struct {
|
|||||||
Config *configs.Config
|
Config *configs.Config
|
||||||
PlanTimestamp time.Time
|
PlanTimestamp time.Time
|
||||||
Encryption encryption.Encryption
|
Encryption encryption.Encryption
|
||||||
|
ProviderFunctionTracker ProviderFunctionMapping
|
||||||
|
|
||||||
// This is an output. Do not set this, nor read it while a graph walk
|
// This is an output. Do not set this, nor read it while a graph walk
|
||||||
// is in progress.
|
// is in progress.
|
||||||
@ -120,6 +121,7 @@ func (w *ContextGraphWalker) EvalContext() EvalContext {
|
|||||||
VariableValues: w.variableValues,
|
VariableValues: w.variableValues,
|
||||||
VariableValuesLock: &w.variableValuesLock,
|
VariableValuesLock: &w.variableValuesLock,
|
||||||
Encryption: w.Encryption,
|
Encryption: w.Encryption,
|
||||||
|
ProviderFunctionTracker: w.ProviderFunctionTracker,
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx
|
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()
|
providerInstanceLock.Lock()
|
||||||
defer providerInstanceLock.Unlock()
|
defer providerInstanceLock.Unlock()
|
||||||
|
|
||||||
if inst, ok := providerInstances[addr.Provider]; ok {
|
if inst, ok := providerInstances[addr]; ok {
|
||||||
return inst
|
return inst
|
||||||
}
|
}
|
||||||
|
|
||||||
factory, ok := ctx.plugins.providerFactories[addr.Provider]
|
factory, ok := ctx.plugins.providerFactories[addr]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[WARN] Unable to find provider %s in test context", addr)
|
log.Printf("[WARN] Unable to find provider %s in test context", addr)
|
||||||
providerInstances[addr.Provider] = nil
|
providerInstances[addr] = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
log.Printf("[INFO] Starting test provider %s", addr)
|
log.Printf("[INFO] Starting test provider %s", addr)
|
||||||
inst, err := factory()
|
inst, err := factory()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[WARN] Unable to start provider %s in test context", addr)
|
log.Printf("[WARN] Unable to start provider %s in test context", addr)
|
||||||
providerInstances[addr.Provider] = nil
|
providerInstances[addr] = nil
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
log.Printf("[INFO] Shutting down test provider %s", addr)
|
log.Printf("[INFO] Shutting down test provider %s", addr)
|
||||||
providerInstances[addr.Provider] = inst
|
providerInstances[addr] = inst
|
||||||
return inst
|
return inst
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -144,7 +144,21 @@ func (ctx *TestContext) evaluate(state *states.SyncState, changes *plans.Changes
|
|||||||
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) {
|
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/addrs"
|
||||||
"github.com/opentofu/opentofu/internal/configs"
|
"github.com/opentofu/opentofu/internal/configs"
|
||||||
"github.com/opentofu/opentofu/internal/dag"
|
"github.com/opentofu/opentofu/internal/dag"
|
||||||
|
"github.com/opentofu/opentofu/internal/providers"
|
||||||
"github.com/opentofu/opentofu/internal/tfdiags"
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -212,10 +213,36 @@ func (t *ProviderTransformer) Transform(g *Graph) error {
|
|||||||
return diags.Err()
|
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
|
// 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.
|
// within the graph. This will error if there are any provider functions that don't map to known providers.
|
||||||
type ProviderFunctionTransformer struct {
|
type ProviderFunctionTransformer struct {
|
||||||
Config *configs.Config
|
Config *configs.Config
|
||||||
|
ProviderFunctionTracker ProviderFunctionMapping
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ProviderFunctionTransformer) Transform(g *Graph) error {
|
func (t *ProviderFunctionTransformer) Transform(g *Graph) error {
|
||||||
@ -227,26 +254,20 @@ func (t *ProviderFunctionTransformer) Transform(g *Graph) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locate all providers in the graph
|
// Locate all providerVerts in the graph
|
||||||
providers := providerVertexMap(g)
|
providerVerts := providerVertexMap(g)
|
||||||
|
|
||||||
type providerReference struct {
|
|
||||||
path string
|
|
||||||
name string
|
|
||||||
alias string
|
|
||||||
}
|
|
||||||
// LuT of provider reference -> provider vertex
|
// LuT of provider reference -> provider vertex
|
||||||
providerReferences := make(map[providerReference]dag.Vertex)
|
providerReferences := make(map[ProviderFunctionReference]dag.Vertex)
|
||||||
|
|
||||||
for _, v := range g.Vertices() {
|
for _, v := range g.Vertices() {
|
||||||
// Provider function references
|
// Provider function references
|
||||||
if nr, ok := v.(GraphNodeReferencer); ok && t.Config != nil {
|
if nr, ok := v.(GraphNodeReferencer); ok && t.Config != nil {
|
||||||
for _, ref := range nr.References() {
|
for _, ref := range nr.References() {
|
||||||
if pf, ok := ref.Subject.(addrs.ProviderFunction); ok {
|
if pf, ok := ref.Subject.(addrs.ProviderFunction); ok {
|
||||||
key := providerReference{
|
key := ProviderFunctionReference{
|
||||||
path: nr.ModulePath().String(),
|
ModulePath: nr.ModulePath().String(),
|
||||||
name: pf.ProviderName,
|
ProviderName: pf.ProviderName,
|
||||||
alias: pf.ProviderAlias,
|
ProviderAlias: pf.ProviderAlias,
|
||||||
}
|
}
|
||||||
|
|
||||||
// We already know about this provider and can link directly
|
// 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)
|
log.Printf("[TRACE] ProviderFunctionTransformer: %s in %s is provided by %s", pf, dag.VertexName(v), absPc)
|
||||||
|
|
||||||
// Lookup provider via full address
|
// Lookup provider via full address
|
||||||
provider := providers[absPc.String()]
|
provider := providerVerts[absPc.String()]
|
||||||
|
|
||||||
if provider != nil {
|
if provider != nil {
|
||||||
// Providers with configuration will already exist within the graph and can be directly referenced
|
// 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))
|
log.Printf("[TRACE] ProviderFunctionTransformer: exact match for %s serving %s", absPc, dag.VertexName(v))
|
||||||
} else {
|
} 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
|
// 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
|
// stub it out with an init-only provider node, which will just
|
||||||
// start up the provider and fetch its schema.
|
// start up the provider and fetch its schema.
|
||||||
@ -304,22 +348,24 @@ func (t *ProviderFunctionTransformer) Transform(g *Graph) error {
|
|||||||
Module: addrs.RootModule,
|
Module: addrs.RootModule,
|
||||||
Provider: absPc.Provider,
|
Provider: absPc.Provider,
|
||||||
}
|
}
|
||||||
if provider, ok = providers[stubAddr.String()]; !ok {
|
// Try to look up an existing stub
|
||||||
stub := &NodeEvalableProvider{
|
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{
|
&NodeAbstractProvider{
|
||||||
Addr: stubAddr,
|
Addr: stubAddr,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
providers[stubAddr.String()] = stub
|
providerVerts[stubAddr.String()] = provider
|
||||||
log.Printf("[TRACE] ProviderFunctionTransformer: creating init-only node for %s", stubAddr)
|
|
||||||
provider = stub
|
|
||||||
g.Add(provider)
|
g.Add(provider)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// see if this is a proxy provider pointing to another concrete config
|
// see if this is a proxy provider pointing to another concrete config
|
||||||
if p, ok := provider.(*graphNodeProxyProvider); ok {
|
if p, ok := provider.(*graphNodeProxyProvider); ok {
|
||||||
g.Remove(p)
|
|
||||||
provider = p.Target()
|
provider = p.Target()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,6 +374,7 @@ func (t *ProviderFunctionTransformer) Transform(g *Graph) error {
|
|||||||
|
|
||||||
// Save for future lookups
|
// Save for future lookups
|
||||||
providerReferences[key] = provider
|
providerReferences[key] = provider
|
||||||
|
t.ProviderFunctionTracker[key] = provider.ProviderAddr()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user