You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
terraform/internal/providers/functions.go

154 lines
4.7 KiB

// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package providers
import (
"fmt"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/lang"
)
type FunctionDecl struct {
Parameters []FunctionParam
VariadicParameter *FunctionParam
ReturnType cty.Type
Description string
DescriptionKind configschema.StringKind
Summary string
DeprecationMessage string
}
type FunctionParam struct {
Name string // Only for documentation and UI, because arguments are positional
Type cty.Type
AllowNullValue bool
AllowUnknownValues bool
Description string
DescriptionKind configschema.StringKind
}
// BuildFunction takes a factory function which will return an unconfigured
// instance of the provider this declaration belongs to and returns a
// cty function that is ready to be called against that provider.
//
// The given name must be the name under which the provider originally
// registered this declaration, or the returned function will try to use an
// invalid name, leading to errors or undefined behavior.
//
// If the given factory returns an instance of any provider other than the
// one the declaration belongs to, or returns a _configured_ instance of
// the provider rather than an unconfigured one, the behavior of the returned
// function is undefined.
//
// Although not functionally required, callers should ideally pass a factory
// function that either retrieves already-running plugins or memoizes the
// plugins it returns so that many calls to functions in the same provider
// will not incur a repeated startup cost.
//
// The resTable argument is a shared instance of *FunctionResults, used to
// check the result values from each function call.
func (d FunctionDecl) BuildFunction(providerAddr addrs.Provider, name string, resTable *lang.FunctionResults, factory func() (Interface, error)) function.Function {
var params []function.Parameter
var varParam *function.Parameter
if len(d.Parameters) > 0 {
params = make([]function.Parameter, len(d.Parameters))
for i, paramDecl := range d.Parameters {
params[i] = paramDecl.ctyParameter()
}
}
if d.VariadicParameter != nil {
cp := d.VariadicParameter.ctyParameter()
varParam = &cp
}
return function.New(&function.Spec{
Type: function.StaticReturnType(d.ReturnType),
Params: params,
VarParam: varParam,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
for i, arg := range args {
var param function.Parameter
if i < len(params) {
param = params[i]
} else {
param = *varParam
}
// We promise provider developers that we won't pass them even
// _nested_ unknown values unless they opt in to dealing with
// them.
if !param.AllowUnknown {
if !arg.IsWhollyKnown() {
return cty.UnknownVal(retType), nil
}
}
// We also ensure that null values are never passed where they
// are not expected.
if !param.AllowNull {
if arg.IsNull() {
return cty.UnknownVal(retType), fmt.Errorf("argument %q cannot be null", param.Name)
}
}
}
provider, err := factory()
if err != nil {
return cty.UnknownVal(retType), fmt.Errorf("failed to launch provider plugin: %s", err)
}
resp := provider.CallFunction(CallFunctionRequest{
FunctionName: name,
Arguments: args,
})
if resp.Err != nil {
return cty.UnknownVal(retType), resp.Err
}
if resp.Result == cty.NilVal {
return cty.UnknownVal(retType), fmt.Errorf("provider returned no result and no errors")
}
if resTable != nil {
err = resTable.CheckPriorProvider(providerAddr, name, args, resp.Result)
if err != nil {
return cty.UnknownVal(retType), err
}
}
return resp.Result, nil
},
})
}
func (p *FunctionParam) ctyParameter() function.Parameter {
return function.Parameter{
Name: p.Name,
Type: p.Type,
AllowNull: p.AllowNullValue,
// While the function may not allow DynamicVal, a `null` literal is
// also dynamically typed. If the parameter is dynamically typed, then
// we must allow this for `null` to pass through.
AllowDynamicType: p.Type == cty.DynamicPseudoType,
// NOTE: Setting this is not a sufficient implementation of
// FunctionParam.AllowUnknownValues, because cty's function
// system only blocks passing in a top-level unknown, but
// our provider-contributed functions API promises to only
// pass wholly-known values unless AllowUnknownValues is true.
// The function implementation itself must also check this.
AllowUnknown: p.AllowUnknownValues,
}
}