mirror of
https://github.com/hashicorp/terraform.git
synced 2026-03-21 18:10:30 -04:00
153 lines
4.7 KiB
Go
153 lines
4.7 KiB
Go
// 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,
|
|
}
|
|
}
|