github.com/opentofu/opentofu@v1.7.1/internal/tofu/context_functions.go (about)

     1  package tofu
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/hashicorp/hcl/v2"
     8  	"github.com/opentofu/opentofu/internal/addrs"
     9  	"github.com/opentofu/opentofu/internal/configs"
    10  	"github.com/opentofu/opentofu/internal/providers"
    11  	"github.com/opentofu/opentofu/internal/tfdiags"
    12  	"github.com/zclconf/go-cty/cty"
    13  	"github.com/zclconf/go-cty/cty/function"
    14  )
    15  
    16  // This builds a provider function using an EvalContext and some additional information
    17  // This is split out of BuiltinEvalContext for testing
    18  func evalContextProviderFunction(providers func(addrs.AbsProviderConfig) providers.Interface, mc *configs.Config, op walkOperation, pf addrs.ProviderFunction, rng tfdiags.SourceRange) (*function.Function, tfdiags.Diagnostics) {
    19  	var diags tfdiags.Diagnostics
    20  
    21  	pr, ok := mc.Module.ProviderRequirements.RequiredProviders[pf.ProviderName]
    22  	if !ok {
    23  		return nil, diags.Append(&hcl.Diagnostic{
    24  			Severity: hcl.DiagError,
    25  			Summary:  "Unknown function provider",
    26  			Detail:   fmt.Sprintf("Provider %q does not exist within the required_providers of this module", pf.ProviderName),
    27  			Subject:  rng.ToHCL().Ptr(),
    28  		})
    29  	}
    30  
    31  	// Very similar to transform_provider.go
    32  	absPc := addrs.AbsProviderConfig{
    33  		Provider: pr.Type,
    34  		Module:   mc.Path,
    35  		Alias:    pf.ProviderAlias,
    36  	}
    37  
    38  	provider := providers(absPc)
    39  
    40  	if provider == nil {
    41  		// Configured provider (NodeApplyableProvider) not required via transform_provider.go.  Instead we should use the unconfigured instance (NodeEvalableProvider) in the root.
    42  
    43  		// Make sure the alias is valid
    44  		validAlias := pf.ProviderAlias == ""
    45  		if !validAlias {
    46  			for _, alias := range pr.Aliases {
    47  				if alias.Alias == pf.ProviderAlias {
    48  					validAlias = true
    49  					break
    50  				}
    51  			}
    52  			if !validAlias {
    53  				return nil, diags.Append(&hcl.Diagnostic{
    54  					Severity: hcl.DiagError,
    55  					Summary:  "Unknown function provider",
    56  					Detail:   fmt.Sprintf("No provider instance %q with alias %q", pf.ProviderName, pf.ProviderAlias),
    57  					Subject:  rng.ToHCL().Ptr(),
    58  				})
    59  			}
    60  		}
    61  
    62  		provider = providers(addrs.AbsProviderConfig{Provider: pr.Type})
    63  		if provider == nil {
    64  			// This should not be possible
    65  			return nil, diags.Append(&hcl.Diagnostic{
    66  				Severity: hcl.DiagError,
    67  				Summary:  "BUG: Uninitialized function provider",
    68  				Detail:   fmt.Sprintf("Provider %q has not yet been initialized", absPc.String()),
    69  				Subject:  rng.ToHCL().Ptr(),
    70  			})
    71  		}
    72  	}
    73  
    74  	// First try to look up the function from provider schema
    75  	schema := provider.GetProviderSchema()
    76  	if schema.Diagnostics.HasErrors() {
    77  		return nil, schema.Diagnostics
    78  	}
    79  	spec, ok := schema.Functions[pf.Function]
    80  	if !ok {
    81  		// During the validate operation, providers are not configured and therefore won't provide
    82  		// a comprehensive GetFunctions list
    83  		// Validate is built around unknown values already, we can stub in a placeholder
    84  		if op == walkValidate {
    85  			// Configured provider functions are not available during validate
    86  			fn := function.New(&function.Spec{
    87  				Description: "Validate Placeholder",
    88  				VarParam: &function.Parameter{
    89  					Type:             cty.DynamicPseudoType,
    90  					AllowNull:        true,
    91  					AllowUnknown:     true,
    92  					AllowDynamicType: true,
    93  					AllowMarked:      false,
    94  				},
    95  				Type: function.StaticReturnType(cty.DynamicPseudoType),
    96  				Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
    97  					return cty.UnknownVal(cty.DynamicPseudoType), nil
    98  				},
    99  			})
   100  			return &fn, nil
   101  		}
   102  
   103  		// The provider may be configured and present additional functions via GetFunctions
   104  		specs := provider.GetFunctions()
   105  		if specs.Diagnostics.HasErrors() {
   106  			return nil, specs.Diagnostics
   107  		}
   108  
   109  		// If the function isn't in the custom GetFunctions list, it must be undefined
   110  		spec, ok = specs.Functions[pf.Function]
   111  		if !ok {
   112  			return nil, diags.Append(&hcl.Diagnostic{
   113  				Severity: hcl.DiagError,
   114  				Summary:  "Function not found in provider",
   115  				Detail:   fmt.Sprintf("Function %q was not registered by provider %q", pf.Function, absPc.String()),
   116  				Subject:  rng.ToHCL().Ptr(),
   117  			})
   118  		}
   119  	}
   120  
   121  	fn := providerFunction(pf.Function, spec, provider)
   122  
   123  	return &fn, nil
   124  
   125  }
   126  
   127  // Turn a provider function spec into a cty callable function
   128  // This will use the instance factory to get a provider to support the
   129  // function call.
   130  func providerFunction(name string, spec providers.FunctionSpec, provider providers.Interface) function.Function {
   131  	params := make([]function.Parameter, len(spec.Parameters))
   132  	for i, param := range spec.Parameters {
   133  		params[i] = providerFunctionParameter(param)
   134  	}
   135  
   136  	var varParam *function.Parameter
   137  	if spec.VariadicParameter != nil {
   138  		value := providerFunctionParameter(*spec.VariadicParameter)
   139  		varParam = &value
   140  	}
   141  
   142  	impl := func(args []cty.Value, retType cty.Type) (cty.Value, error) {
   143  		resp := provider.CallFunction(providers.CallFunctionRequest{
   144  			Name:      name,
   145  			Arguments: args,
   146  		})
   147  
   148  		if argError, ok := resp.Error.(*providers.CallFunctionArgumentError); ok {
   149  			// Convert ArgumentError to cty error
   150  			return resp.Result, function.NewArgError(argError.FunctionArgument, errors.New(argError.Text))
   151  		}
   152  
   153  		return resp.Result, resp.Error
   154  	}
   155  
   156  	return function.New(&function.Spec{
   157  		Description: spec.Summary,
   158  		Params:      params,
   159  		VarParam:    varParam,
   160  		Type:        function.StaticReturnType(spec.Return),
   161  		Impl:        impl,
   162  	})
   163  
   164  }
   165  
   166  // Simple mapping of function parameter spec to function parameter
   167  func providerFunctionParameter(spec providers.FunctionParameterSpec) function.Parameter {
   168  	return function.Parameter{
   169  		Name:         spec.Name,
   170  		Description:  spec.Description,
   171  		Type:         spec.Type,
   172  		AllowNull:    spec.AllowNullValue,
   173  		AllowUnknown: spec.AllowUnknownValues,
   174  		// I don't believe this is allowable for provider functions
   175  		AllowDynamicType: false,
   176  		// force cty to strip marks ahead of time and re-add them to the resulting object
   177  		// GRPC: failed: value has marks, so it cannot be serialized.
   178  		AllowMarked: false,
   179  	}
   180  }