Files
opentf/internal/tofu/context_functions.go
Martin Atkins 1380154250 lang: Data methods now take context.Context
This caused a bunch of mechanical changes to callers, of course. Expression
evaluation is a very cross-cutting concern, so updating everything all at
once would be a lot and so this stops at a mostly-arbitrary point wiring
a bunch of callers to pass in contexts without changing anything that has
lots of callers.

We'll continue pulling on this thread in later commits.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-06-18 07:26:17 -07:00

134 lines
4.3 KiB
Go

// 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,
}
}