// Copyright (c) The OpenTofu Authors // SPDX-License-Identifier: MPL-2.0 // Copyright (c) 2023 HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package tofu import ( "context" "errors" "fmt" "github.com/hashicorp/hcl/v2" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/function" "github.com/opentofu/opentofu/internal/addrs" "github.com/opentofu/opentofu/internal/providers" "github.com/opentofu/opentofu/internal/tfdiags" ) // This builds a provider function using an EvalContext and some additional information // This is split out of BuiltinEvalContext for testing func evalContextProviderFunction(ctx context.Context, provider providers.Interface, op walkOperation, pf addrs.ProviderFunction, rng tfdiags.SourceRange) (*function.Function, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics // First try to look up the function from provider schema schema := provider.GetProviderSchema(ctx) 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(ctx) 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", pf), Subject: rng.ToHCL().Ptr(), }) } } fn := providerFunction(ctx, pf.Function, spec, provider) return &fn, nil } // 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. func providerFunction(ctx context.Context, name string, spec providers.FunctionSpec, provider providers.Interface) function.Function { 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(ctx, 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, } }