2024-06-03 08:49:36 -05:00
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
2024-04-10 07:04:08 -05:00
package tofu
import (
"errors"
"fmt"
2024-04-18 08:11:38 -05:00
"github.com/hashicorp/hcl/v2"
2024-04-10 07:04:08 -05:00
"github.com/opentofu/opentofu/internal/addrs"
2024-04-18 08:11:38 -05:00
"github.com/opentofu/opentofu/internal/configs"
2024-04-10 07:04:08 -05:00
"github.com/opentofu/opentofu/internal/providers"
2024-04-18 08:11:38 -05:00
"github.com/opentofu/opentofu/internal/tfdiags"
2024-04-10 07:04:08 -05:00
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
)
2024-04-18 08:11:38 -05:00
// This builds a provider function using an EvalContext and some additional information
// This is split out of BuiltinEvalContext for testing
2024-05-07 08:31:17 -05:00
func evalContextProviderFunction ( providers func ( addrs . AbsProviderConfig ) providers . Interface , mc * configs . Config , op walkOperation , pf addrs . ProviderFunction , rng tfdiags . SourceRange ) ( * function . Function , tfdiags . Diagnostics ) {
2024-04-18 08:11:38 -05:00
var diags tfdiags . Diagnostics
2024-04-10 07:04:08 -05:00
2024-04-18 08:11:38 -05:00
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 ,
}
2024-05-07 08:31:17 -05:00
provider := providers ( absPc )
2024-04-10 07:04:08 -05:00
2024-04-18 08:11:38 -05:00
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 ( ) ,
} )
}
}
2024-05-07 08:31:17 -05:00
provider = providers ( addrs . AbsProviderConfig { Provider : pr . Type } )
2024-04-10 07:04:08 -05:00
if provider == nil {
2024-04-18 08:11:38 -05:00
// 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 ( ) ,
} )
2024-04-10 07:04:08 -05:00
}
}
2024-04-18 08:11:38 -05:00
// First try to look up the function from provider schema
schema := provider . GetProviderSchema ( )
if schema . Diagnostics . HasErrors ( ) {
return nil , schema . Diagnostics
}
spec , ok := schema . Functions [ pf . Function ]
if ! ok {
// During the validate operation, providers are not configured and therefore won't provide
// a comprehensive GetFunctions list
// Validate is built around unknown values already, we can stub in a placeholder
if op == walkValidate {
// Configured provider functions are not available during validate
fn := function . New ( & function . Spec {
Description : "Validate Placeholder" ,
VarParam : & function . Parameter {
Type : cty . DynamicPseudoType ,
AllowNull : true ,
AllowUnknown : true ,
AllowDynamicType : true ,
AllowMarked : false ,
} ,
Type : function . StaticReturnType ( cty . DynamicPseudoType ) ,
Impl : func ( args [ ] cty . Value , retType cty . Type ) ( cty . Value , error ) {
return cty . UnknownVal ( cty . DynamicPseudoType ) , nil
} ,
} )
return & fn , nil
}
// The provider may be configured and present additional functions via GetFunctions
specs := provider . GetFunctions ( )
if specs . Diagnostics . HasErrors ( ) {
return nil , specs . Diagnostics
}
// If the function isn't in the custom GetFunctions list, it must be undefined
spec , ok = specs . Functions [ pf . Function ]
if ! ok {
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 ( ) ) ,
Subject : rng . ToHCL ( ) . Ptr ( ) ,
} )
2024-04-10 07:04:08 -05:00
}
}
2024-04-18 08:11:38 -05:00
fn := providerFunction ( pf . Function , spec , provider )
return & fn , nil
2024-04-10 07:04:08 -05:00
}
// Turn a provider function spec into a cty callable function
// This will use the instance factory to get a provider to support the
// function call.
2024-04-18 08:11:38 -05:00
func providerFunction ( name string , spec providers . FunctionSpec , provider providers . Interface ) function . Function {
2024-04-10 07:04:08 -05:00
params := make ( [ ] function . Parameter , len ( spec . Parameters ) )
for i , param := range spec . Parameters {
params [ i ] = providerFunctionParameter ( param )
}
var varParam * function . Parameter
if spec . VariadicParameter != nil {
value := providerFunctionParameter ( * spec . VariadicParameter )
varParam = & value
}
impl := func ( args [ ] cty . Value , retType cty . Type ) ( cty . Value , error ) {
resp := provider . CallFunction ( providers . CallFunctionRequest {
Name : name ,
Arguments : args ,
} )
if argError , ok := resp . Error . ( * providers . CallFunctionArgumentError ) ; ok {
// Convert ArgumentError to cty error
return resp . Result , function . NewArgError ( argError . FunctionArgument , errors . New ( argError . Text ) )
}
return resp . Result , resp . Error
}
return function . New ( & function . Spec {
Description : spec . Summary ,
Params : params ,
VarParam : varParam ,
Type : function . StaticReturnType ( spec . Return ) ,
Impl : impl ,
} )
}
// Simple mapping of function parameter spec to function parameter
func providerFunctionParameter ( spec providers . FunctionParameterSpec ) function . Parameter {
return function . Parameter {
Name : spec . Name ,
Description : spec . Description ,
Type : spec . Type ,
AllowNull : spec . AllowNullValue ,
AllowUnknown : spec . AllowUnknownValues ,
// I don't believe this is allowable for provider functions
AllowDynamicType : false ,
// force cty to strip marks ahead of time and re-add them to the resulting object
// GRPC: failed: value has marks, so it cannot be serialized.
AllowMarked : false ,
}
}