mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Add provider functions to provider.Interface with GRPC implementation (#1437)
Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
parent
6dcc39e107
commit
969a7e0a99
@ -160,6 +160,10 @@ func (p *Provider) ValidateResourceConfig(req providers.ValidateResourceConfigRe
|
|||||||
return validateDataStoreResourceConfig(req)
|
return validateDataStoreResourceConfig(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Provider) CallFunction(r providers.CallFunctionRequest) providers.CallFunctionResponse {
|
||||||
|
panic("unimplemented - terraform provider has no functions")
|
||||||
|
}
|
||||||
|
|
||||||
// Close is a noop for this provider, since it's run in-process.
|
// Close is a noop for this provider, since it's run in-process.
|
||||||
func (p *Provider) Close() error {
|
func (p *Provider) Close() error {
|
||||||
return nil
|
return nil
|
||||||
|
@ -362,6 +362,10 @@ func (p *MockProvider) ReadDataSource(r providers.ReadDataSourceRequest) provide
|
|||||||
return p.ReadDataSourceResponse
|
return p.ReadDataSourceResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *MockProvider) CallFunction(r providers.CallFunctionRequest) providers.CallFunctionResponse {
|
||||||
|
panic("Not Implemented")
|
||||||
|
}
|
||||||
|
|
||||||
func (p *MockProvider) Close() error {
|
func (p *MockProvider) Close() error {
|
||||||
p.CloseCalled = true
|
p.CloseCalled = true
|
||||||
return p.CloseError
|
return p.CloseError
|
||||||
|
63
internal/plugin/convert/function.go
Normal file
63
internal/plugin/convert/function.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package convert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/opentofu/opentofu/internal/providers"
|
||||||
|
"github.com/opentofu/opentofu/internal/tfplugin5"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ProtoToCtyType(in []byte) cty.Type {
|
||||||
|
var out cty.Type
|
||||||
|
if err := json.Unmarshal(in, &out); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProtoToTextFormatting(proto tfplugin5.StringKind) providers.TextFormatting {
|
||||||
|
switch proto {
|
||||||
|
case tfplugin5.StringKind_PLAIN:
|
||||||
|
return providers.TextFormattingPlain
|
||||||
|
case tfplugin5.StringKind_MARKDOWN:
|
||||||
|
return providers.TextFormattingMarkdown
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("Invalid text tfplugin5.StringKind %v", proto))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProtoToFunctionParameterSpec(proto *tfplugin5.Function_Parameter) providers.FunctionParameterSpec {
|
||||||
|
return providers.FunctionParameterSpec{
|
||||||
|
Name: proto.Name,
|
||||||
|
Type: ProtoToCtyType(proto.Type),
|
||||||
|
AllowNullValue: proto.AllowNullValue,
|
||||||
|
AllowUnknownValues: proto.AllowUnknownValues,
|
||||||
|
Description: proto.Description,
|
||||||
|
DescriptionFormat: ProtoToTextFormatting(proto.DescriptionKind),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProtoToFunctionSpec(proto *tfplugin5.Function) providers.FunctionSpec {
|
||||||
|
params := make([]providers.FunctionParameterSpec, len(proto.Parameters))
|
||||||
|
for i, param := range proto.Parameters {
|
||||||
|
params[i] = ProtoToFunctionParameterSpec(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
var varParam *providers.FunctionParameterSpec
|
||||||
|
if proto.VariadicParameter != nil {
|
||||||
|
param := ProtoToFunctionParameterSpec(proto.VariadicParameter)
|
||||||
|
varParam = ¶m
|
||||||
|
}
|
||||||
|
|
||||||
|
return providers.FunctionSpec{
|
||||||
|
Parameters: params,
|
||||||
|
VariadicParameter: varParam,
|
||||||
|
Return: ProtoToCtyType(proto.Return.Type),
|
||||||
|
Summary: proto.Summary,
|
||||||
|
Description: proto.Description,
|
||||||
|
DescriptionFormat: ProtoToTextFormatting(proto.DescriptionKind),
|
||||||
|
DeprecationMessage: proto.DeprecationMessage,
|
||||||
|
}
|
||||||
|
}
|
@ -76,6 +76,8 @@ type GRPCProvider struct {
|
|||||||
schema providers.GetProviderSchemaResponse
|
schema providers.GetProviderSchemaResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ providers.Interface = new(GRPCProvider)
|
||||||
|
|
||||||
func (p *GRPCProvider) GetProviderSchema() (resp providers.GetProviderSchemaResponse) {
|
func (p *GRPCProvider) GetProviderSchema() (resp providers.GetProviderSchemaResponse) {
|
||||||
logger.Trace("GRPCProvider: GetProviderSchema")
|
logger.Trace("GRPCProvider: GetProviderSchema")
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
@ -102,6 +104,7 @@ func (p *GRPCProvider) GetProviderSchema() (resp providers.GetProviderSchemaResp
|
|||||||
|
|
||||||
resp.ResourceTypes = make(map[string]providers.Schema)
|
resp.ResourceTypes = make(map[string]providers.Schema)
|
||||||
resp.DataSources = make(map[string]providers.Schema)
|
resp.DataSources = make(map[string]providers.Schema)
|
||||||
|
resp.Functions = make(map[string]providers.FunctionSpec)
|
||||||
|
|
||||||
// Some providers may generate quite large schemas, and the internal default
|
// Some providers may generate quite large schemas, and the internal default
|
||||||
// grpc response size limit is 4MB. 64MB should cover most any use case, and
|
// grpc response size limit is 4MB. 64MB should cover most any use case, and
|
||||||
@ -144,6 +147,10 @@ func (p *GRPCProvider) GetProviderSchema() (resp providers.GetProviderSchemaResp
|
|||||||
resp.DataSources[name] = convert.ProtoToProviderSchema(data)
|
resp.DataSources[name] = convert.ProtoToProviderSchema(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for name, fn := range protoResp.Functions {
|
||||||
|
resp.Functions[name] = convert.ProtoToFunctionSpec(fn)
|
||||||
|
}
|
||||||
|
|
||||||
if protoResp.ServerCapabilities != nil {
|
if protoResp.ServerCapabilities != nil {
|
||||||
resp.ServerCapabilities.PlanDestroy = protoResp.ServerCapabilities.PlanDestroy
|
resp.ServerCapabilities.PlanDestroy = protoResp.ServerCapabilities.PlanDestroy
|
||||||
resp.ServerCapabilities.GetProviderSchemaOptional = protoResp.ServerCapabilities.GetProviderSchemaOptional
|
resp.ServerCapabilities.GetProviderSchemaOptional = protoResp.ServerCapabilities.GetProviderSchemaOptional
|
||||||
@ -686,6 +693,96 @@ func (p *GRPCProvider) ReadDataSource(r providers.ReadDataSourceRequest) (resp p
|
|||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *GRPCProvider) CallFunction(r providers.CallFunctionRequest) (resp providers.CallFunctionResponse) {
|
||||||
|
logger.Trace("GRPCProvider: CallFunction")
|
||||||
|
|
||||||
|
schema := p.GetProviderSchema()
|
||||||
|
if schema.Diagnostics.HasErrors() {
|
||||||
|
// This should be unreachable
|
||||||
|
resp.Error = schema.Diagnostics.Err()
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
spec, ok := schema.Functions[r.Name]
|
||||||
|
if !ok {
|
||||||
|
// This should be unreachable
|
||||||
|
resp.Error = fmt.Errorf("invalid CallFunctionRequest: function %s not defined in provider schema", r.Name)
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
protoReq := &proto.CallFunction_Request{
|
||||||
|
Name: r.Name,
|
||||||
|
Arguments: make([]*proto.DynamicValue, len(r.Arguments)),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate the arguments
|
||||||
|
// As this is functionality is always sitting behind cty/function.Function, we skip some validation
|
||||||
|
// checks of from the function and param spec. We still include basic validation to prevent panics,
|
||||||
|
// just in case there are bugs in cty
|
||||||
|
if len(r.Arguments) < len(spec.Parameters) {
|
||||||
|
// This should be unreachable
|
||||||
|
resp.Error = fmt.Errorf("invalid CallFunctionRequest: function %s expected %d parameters and got %d instead", r.Name, len(spec.Parameters), len(r.Arguments))
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, arg := range r.Arguments {
|
||||||
|
var paramSpec providers.FunctionParameterSpec
|
||||||
|
if i < len(spec.Parameters) {
|
||||||
|
paramSpec = spec.Parameters[i]
|
||||||
|
} else {
|
||||||
|
// We are past the end of spec.Parameters, this is either variadic or an error
|
||||||
|
if spec.VariadicParameter != nil {
|
||||||
|
paramSpec = *spec.VariadicParameter
|
||||||
|
} else {
|
||||||
|
// This should be unreachable
|
||||||
|
resp.Error = fmt.Errorf("invalid CallFunctionRequest: too many arguments passed to non-variadic function %s", r.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if arg.IsNull() {
|
||||||
|
if paramSpec.AllowNullValue {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
resp.Error = &providers.CallFunctionArgumentError{
|
||||||
|
Text: fmt.Sprintf("parameter %s is null, which is not allowed for function %s", paramSpec.Name, r.Name),
|
||||||
|
FunctionArgument: i,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
encodedArg, err := msgpack.Marshal(arg, paramSpec.Type)
|
||||||
|
if err != nil {
|
||||||
|
resp.Error = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
protoReq.Arguments[i] = &proto.DynamicValue{
|
||||||
|
Msgpack: encodedArg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protoResp, err := p.client.CallFunction(p.ctx, protoReq)
|
||||||
|
if err != nil {
|
||||||
|
resp.Error = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if protoResp.Error != nil {
|
||||||
|
err := &providers.CallFunctionArgumentError{
|
||||||
|
Text: protoResp.Error.Text,
|
||||||
|
}
|
||||||
|
if protoResp.Error.FunctionArgument != nil {
|
||||||
|
err.FunctionArgument = int(*protoResp.Error.FunctionArgument)
|
||||||
|
}
|
||||||
|
resp.Error = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Result, resp.Error = decodeDynamicValue(protoResp.Result, spec.Return)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// closing the grpc connection is final, and tofu will call it at the end of every phase.
|
// closing the grpc connection is final, and tofu will call it at the end of every phase.
|
||||||
func (p *GRPCProvider) Close() error {
|
func (p *GRPCProvider) Close() error {
|
||||||
logger.Trace("GRPCProvider: Close")
|
logger.Trace("GRPCProvider: Close")
|
||||||
|
@ -96,6 +96,25 @@ func providerProtoSchema() *proto.GetProviderSchema_Response {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Functions: map[string]*proto.Function{
|
||||||
|
"fn": &proto.Function{
|
||||||
|
Parameters: []*proto.Function_Parameter{{
|
||||||
|
Name: "par_a",
|
||||||
|
Type: []byte(`"string"`),
|
||||||
|
AllowNullValue: false,
|
||||||
|
AllowUnknownValues: false,
|
||||||
|
}},
|
||||||
|
VariadicParameter: &proto.Function_Parameter{
|
||||||
|
Name: "par_var",
|
||||||
|
Type: []byte(`"string"`),
|
||||||
|
AllowNullValue: true,
|
||||||
|
AllowUnknownValues: false,
|
||||||
|
},
|
||||||
|
Return: &proto.Function_Return{
|
||||||
|
Type: []byte(`"string"`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -876,3 +895,29 @@ func TestGRPCProvider_ReadDataSourceJSON(t *testing.T) {
|
|||||||
t.Fatal(cmp.Diff(expected, resp.State, typeComparer, valueComparer, equateEmpty))
|
t.Fatal(cmp.Diff(expected, resp.State, typeComparer, valueComparer, equateEmpty))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGRPCProvider_CallFunction(t *testing.T) {
|
||||||
|
client := mockProviderClient(t)
|
||||||
|
p := &GRPCProvider{
|
||||||
|
client: client,
|
||||||
|
}
|
||||||
|
|
||||||
|
client.EXPECT().CallFunction(
|
||||||
|
gomock.Any(),
|
||||||
|
gomock.Any(),
|
||||||
|
).Return(&proto.CallFunction_Response{
|
||||||
|
Result: &proto.DynamicValue{Json: []byte(`"foo"`)},
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
resp := p.CallFunction(providers.CallFunctionRequest{
|
||||||
|
Name: "fn",
|
||||||
|
Arguments: []cty.Value{cty.StringVal("bar"), cty.NilVal},
|
||||||
|
})
|
||||||
|
|
||||||
|
if resp.Error != nil {
|
||||||
|
t.Fatal(resp.Error)
|
||||||
|
}
|
||||||
|
if resp.Result != cty.StringVal("foo") {
|
||||||
|
t.Fatalf("%v", resp.Result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
63
internal/plugin6/convert/function.go
Normal file
63
internal/plugin6/convert/function.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package convert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/opentofu/opentofu/internal/providers"
|
||||||
|
"github.com/opentofu/opentofu/internal/tfplugin6"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ProtoToCtyType(in []byte) cty.Type {
|
||||||
|
var out cty.Type
|
||||||
|
if err := json.Unmarshal(in, &out); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProtoToTextFormatting(proto tfplugin6.StringKind) providers.TextFormatting {
|
||||||
|
switch proto {
|
||||||
|
case tfplugin6.StringKind_PLAIN:
|
||||||
|
return providers.TextFormattingPlain
|
||||||
|
case tfplugin6.StringKind_MARKDOWN:
|
||||||
|
return providers.TextFormattingMarkdown
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("Invalid text tfplugin6.StringKind %v", proto))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProtoToFunctionParameterSpec(proto *tfplugin6.Function_Parameter) providers.FunctionParameterSpec {
|
||||||
|
return providers.FunctionParameterSpec{
|
||||||
|
Name: proto.Name,
|
||||||
|
Type: ProtoToCtyType(proto.Type),
|
||||||
|
AllowNullValue: proto.AllowNullValue,
|
||||||
|
AllowUnknownValues: proto.AllowUnknownValues,
|
||||||
|
Description: proto.Description,
|
||||||
|
DescriptionFormat: ProtoToTextFormatting(proto.DescriptionKind),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProtoToFunctionSpec(proto *tfplugin6.Function) providers.FunctionSpec {
|
||||||
|
params := make([]providers.FunctionParameterSpec, len(proto.Parameters))
|
||||||
|
for i, param := range proto.Parameters {
|
||||||
|
params[i] = ProtoToFunctionParameterSpec(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
var varParam *providers.FunctionParameterSpec
|
||||||
|
if proto.VariadicParameter != nil {
|
||||||
|
param := ProtoToFunctionParameterSpec(proto.VariadicParameter)
|
||||||
|
varParam = ¶m
|
||||||
|
}
|
||||||
|
|
||||||
|
return providers.FunctionSpec{
|
||||||
|
Parameters: params,
|
||||||
|
VariadicParameter: varParam,
|
||||||
|
Return: ProtoToCtyType(proto.Return.Type),
|
||||||
|
Summary: proto.Summary,
|
||||||
|
Description: proto.Description,
|
||||||
|
DescriptionFormat: ProtoToTextFormatting(proto.DescriptionKind),
|
||||||
|
DeprecationMessage: proto.DeprecationMessage,
|
||||||
|
}
|
||||||
|
}
|
@ -76,6 +76,8 @@ type GRPCProvider struct {
|
|||||||
schema providers.GetProviderSchemaResponse
|
schema providers.GetProviderSchemaResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ providers.Interface = new(GRPCProvider)
|
||||||
|
|
||||||
func (p *GRPCProvider) GetProviderSchema() (resp providers.GetProviderSchemaResponse) {
|
func (p *GRPCProvider) GetProviderSchema() (resp providers.GetProviderSchemaResponse) {
|
||||||
logger.Trace("GRPCProvider.v6: GetProviderSchema")
|
logger.Trace("GRPCProvider.v6: GetProviderSchema")
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
@ -102,6 +104,7 @@ func (p *GRPCProvider) GetProviderSchema() (resp providers.GetProviderSchemaResp
|
|||||||
|
|
||||||
resp.ResourceTypes = make(map[string]providers.Schema)
|
resp.ResourceTypes = make(map[string]providers.Schema)
|
||||||
resp.DataSources = make(map[string]providers.Schema)
|
resp.DataSources = make(map[string]providers.Schema)
|
||||||
|
resp.Functions = make(map[string]providers.FunctionSpec)
|
||||||
|
|
||||||
// Some providers may generate quite large schemas, and the internal default
|
// Some providers may generate quite large schemas, and the internal default
|
||||||
// grpc response size limit is 4MB. 64MB should cover most any use case, and
|
// grpc response size limit is 4MB. 64MB should cover most any use case, and
|
||||||
@ -144,6 +147,10 @@ func (p *GRPCProvider) GetProviderSchema() (resp providers.GetProviderSchemaResp
|
|||||||
resp.DataSources[name] = convert.ProtoToProviderSchema(data)
|
resp.DataSources[name] = convert.ProtoToProviderSchema(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for name, fn := range protoResp.Functions {
|
||||||
|
resp.Functions[name] = convert.ProtoToFunctionSpec(fn)
|
||||||
|
}
|
||||||
|
|
||||||
if protoResp.ServerCapabilities != nil {
|
if protoResp.ServerCapabilities != nil {
|
||||||
resp.ServerCapabilities.PlanDestroy = protoResp.ServerCapabilities.PlanDestroy
|
resp.ServerCapabilities.PlanDestroy = protoResp.ServerCapabilities.PlanDestroy
|
||||||
resp.ServerCapabilities.GetProviderSchemaOptional = protoResp.ServerCapabilities.GetProviderSchemaOptional
|
resp.ServerCapabilities.GetProviderSchemaOptional = protoResp.ServerCapabilities.GetProviderSchemaOptional
|
||||||
@ -675,6 +682,96 @@ func (p *GRPCProvider) ReadDataSource(r providers.ReadDataSourceRequest) (resp p
|
|||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *GRPCProvider) CallFunction(r providers.CallFunctionRequest) (resp providers.CallFunctionResponse) {
|
||||||
|
logger.Trace("GRPCProvider6: CallFunction")
|
||||||
|
|
||||||
|
schema := p.GetProviderSchema()
|
||||||
|
if schema.Diagnostics.HasErrors() {
|
||||||
|
// This should be unreachable
|
||||||
|
resp.Error = schema.Diagnostics.Err()
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
spec, ok := schema.Functions[r.Name]
|
||||||
|
if !ok {
|
||||||
|
// This should be unreachable
|
||||||
|
resp.Error = fmt.Errorf("invalid CallFunctionRequest: function %s not defined in provider schema", r.Name)
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
protoReq := &proto6.CallFunction_Request{
|
||||||
|
Name: r.Name,
|
||||||
|
Arguments: make([]*proto6.DynamicValue, len(r.Arguments)),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate the arguments
|
||||||
|
// As this is functionality is always sitting behind cty/function.Function, we skip some validation
|
||||||
|
// checks of from the function and param spec. We still include basic validation to prevent panics,
|
||||||
|
// just in case there are bugs in cty
|
||||||
|
if len(r.Arguments) < len(spec.Parameters) {
|
||||||
|
// This should be unreachable
|
||||||
|
resp.Error = fmt.Errorf("invalid CallFunctionRequest: function %s expected %d parameters and got %d instead", r.Name, len(spec.Parameters), len(r.Arguments))
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, arg := range r.Arguments {
|
||||||
|
var paramSpec providers.FunctionParameterSpec
|
||||||
|
if i < len(spec.Parameters) {
|
||||||
|
paramSpec = spec.Parameters[i]
|
||||||
|
} else {
|
||||||
|
// We are past the end of spec.Parameters, this is either variadic or an error
|
||||||
|
if spec.VariadicParameter != nil {
|
||||||
|
paramSpec = *spec.VariadicParameter
|
||||||
|
} else {
|
||||||
|
// This should be unreachable
|
||||||
|
resp.Error = fmt.Errorf("invalid CallFunctionRequest: too many arguments passed to non-variadic function %s", r.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if arg.IsNull() {
|
||||||
|
if paramSpec.AllowNullValue {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
resp.Error = &providers.CallFunctionArgumentError{
|
||||||
|
Text: fmt.Sprintf("parameter %s is null, which is not allowed for function %s", paramSpec.Name, r.Name),
|
||||||
|
FunctionArgument: i,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
encodedArg, err := msgpack.Marshal(arg, paramSpec.Type)
|
||||||
|
if err != nil {
|
||||||
|
resp.Error = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
protoReq.Arguments[i] = &proto6.DynamicValue{
|
||||||
|
Msgpack: encodedArg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protoResp, err := p.client.CallFunction(p.ctx, protoReq)
|
||||||
|
if err != nil {
|
||||||
|
resp.Error = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if protoResp.Error != nil {
|
||||||
|
err := &providers.CallFunctionArgumentError{
|
||||||
|
Text: protoResp.Error.Text,
|
||||||
|
}
|
||||||
|
if protoResp.Error.FunctionArgument != nil {
|
||||||
|
err.FunctionArgument = int(*protoResp.Error.FunctionArgument)
|
||||||
|
}
|
||||||
|
resp.Error = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Result, resp.Error = decodeDynamicValue(protoResp.Result, spec.Return)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// closing the grpc connection is final, and tofu will call it at the end of every phase.
|
// closing the grpc connection is final, and tofu will call it at the end of every phase.
|
||||||
func (p *GRPCProvider) Close() error {
|
func (p *GRPCProvider) Close() error {
|
||||||
logger.Trace("GRPCProvider.v6: Close")
|
logger.Trace("GRPCProvider.v6: Close")
|
||||||
|
@ -103,6 +103,25 @@ func providerProtoSchema() *proto.GetProviderSchema_Response {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Functions: map[string]*proto.Function{
|
||||||
|
"fn": &proto.Function{
|
||||||
|
Parameters: []*proto.Function_Parameter{{
|
||||||
|
Name: "par_a",
|
||||||
|
Type: []byte(`"string"`),
|
||||||
|
AllowNullValue: false,
|
||||||
|
AllowUnknownValues: false,
|
||||||
|
}},
|
||||||
|
VariadicParameter: &proto.Function_Parameter{
|
||||||
|
Name: "par_var",
|
||||||
|
Type: []byte(`"string"`),
|
||||||
|
AllowNullValue: true,
|
||||||
|
AllowUnknownValues: false,
|
||||||
|
},
|
||||||
|
Return: &proto.Function_Return{
|
||||||
|
Type: []byte(`"string"`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -883,3 +902,29 @@ func TestGRPCProvider_ReadDataSourceJSON(t *testing.T) {
|
|||||||
t.Fatal(cmp.Diff(expected, resp.State, typeComparer, valueComparer, equateEmpty))
|
t.Fatal(cmp.Diff(expected, resp.State, typeComparer, valueComparer, equateEmpty))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGRPCProvider_CallFunction(t *testing.T) {
|
||||||
|
client := mockProviderClient(t)
|
||||||
|
p := &GRPCProvider{
|
||||||
|
client: client,
|
||||||
|
}
|
||||||
|
|
||||||
|
client.EXPECT().CallFunction(
|
||||||
|
gomock.Any(),
|
||||||
|
gomock.Any(),
|
||||||
|
).Return(&proto.CallFunction_Response{
|
||||||
|
Result: &proto.DynamicValue{Json: []byte(`"foo"`)},
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
resp := p.CallFunction(providers.CallFunctionRequest{
|
||||||
|
Name: "fn",
|
||||||
|
Arguments: []cty.Value{cty.StringVal("bar"), cty.NilVal},
|
||||||
|
})
|
||||||
|
|
||||||
|
if resp.Error != nil {
|
||||||
|
t.Fatal(resp.Error)
|
||||||
|
}
|
||||||
|
if resp.Result != cty.StringVal("foo") {
|
||||||
|
t.Fatalf("%v", resp.Result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -147,6 +147,10 @@ func (s simple) ReadDataSource(req providers.ReadDataSourceRequest) (resp provid
|
|||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s simple) CallFunction(r providers.CallFunctionRequest) providers.CallFunctionResponse {
|
||||||
|
panic("Not Implemented")
|
||||||
|
}
|
||||||
|
|
||||||
func (s simple) Close() error {
|
func (s simple) Close() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -138,6 +138,10 @@ func (s simple) ReadDataSource(req providers.ReadDataSourceRequest) (resp provid
|
|||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s simple) CallFunction(r providers.CallFunctionRequest) providers.CallFunctionResponse {
|
||||||
|
panic("Not Implemented")
|
||||||
|
}
|
||||||
|
|
||||||
func (s simple) Close() error {
|
func (s simple) Close() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,11 @@ import (
|
|||||||
// Interface represents the set of methods required for a complete resource
|
// Interface represents the set of methods required for a complete resource
|
||||||
// provider plugin.
|
// provider plugin.
|
||||||
type Interface interface {
|
type Interface interface {
|
||||||
|
// GetMetadata is not yet implemented or used at this time. It may
|
||||||
|
// be used in the future to avoid loading a provider's full schema
|
||||||
|
// for initial validation. This could result in some potential
|
||||||
|
// memory savings.
|
||||||
|
|
||||||
// GetSchema returns the complete schema for the provider.
|
// GetSchema returns the complete schema for the provider.
|
||||||
GetProviderSchema() GetProviderSchemaResponse
|
GetProviderSchema() GetProviderSchemaResponse
|
||||||
|
|
||||||
@ -72,6 +77,13 @@ type Interface interface {
|
|||||||
// ReadDataSource returns the data source's current state.
|
// ReadDataSource returns the data source's current state.
|
||||||
ReadDataSource(ReadDataSourceRequest) ReadDataSourceResponse
|
ReadDataSource(ReadDataSourceRequest) ReadDataSourceResponse
|
||||||
|
|
||||||
|
// GetFunctions not yet implemented or used at this stage as it is not required.
|
||||||
|
// tofu queries a full set of provider schemas early on in the process which contain
|
||||||
|
// the required information.
|
||||||
|
|
||||||
|
// CallFunction requests that the given function is called and response returned.
|
||||||
|
CallFunction(CallFunctionRequest) CallFunctionResponse
|
||||||
|
|
||||||
// Close shuts down the plugin process if applicable.
|
// Close shuts down the plugin process if applicable.
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
@ -99,6 +111,9 @@ type GetProviderSchemaResponse struct {
|
|||||||
|
|
||||||
// ServerCapabilities lists optional features supported by the provider.
|
// ServerCapabilities lists optional features supported by the provider.
|
||||||
ServerCapabilities ServerCapabilities
|
ServerCapabilities ServerCapabilities
|
||||||
|
|
||||||
|
// Functions lists all functions supported by this provider.
|
||||||
|
Functions map[string]FunctionSpec
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schema pairs a provider or resource schema with that schema's version.
|
// Schema pairs a provider or resource schema with that schema's version.
|
||||||
@ -130,6 +145,44 @@ type ServerCapabilities struct {
|
|||||||
GetProviderSchemaOptional bool
|
GetProviderSchemaOptional bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FunctionSpec struct {
|
||||||
|
// List of parameters required to call the function
|
||||||
|
Parameters []FunctionParameterSpec
|
||||||
|
// Optional Spec for variadic parameters
|
||||||
|
VariadicParameter *FunctionParameterSpec
|
||||||
|
// Type which the function will return
|
||||||
|
Return cty.Type
|
||||||
|
// Human-readable shortened documentation for the function
|
||||||
|
Summary string
|
||||||
|
// Human-readable documentation for the function
|
||||||
|
Description string
|
||||||
|
// Formatting type of the Description field
|
||||||
|
DescriptionFormat TextFormatting
|
||||||
|
// Human-readable message present if the function is deprecated
|
||||||
|
DeprecationMessage string
|
||||||
|
}
|
||||||
|
|
||||||
|
type FunctionParameterSpec struct {
|
||||||
|
// Human-readable display name for the parameter
|
||||||
|
Name string
|
||||||
|
// Type constraint for the parameter
|
||||||
|
Type cty.Type
|
||||||
|
// Null values alowed for the parameter
|
||||||
|
AllowNullValue bool
|
||||||
|
// Unknown Values allowd for the parameter
|
||||||
|
// Implies the Return type of the function is also Unknown
|
||||||
|
AllowUnknownValues bool
|
||||||
|
// Human-readable documentation for the parameter
|
||||||
|
Description string
|
||||||
|
// Formatting type of the Description field
|
||||||
|
DescriptionFormat TextFormatting
|
||||||
|
}
|
||||||
|
|
||||||
|
type TextFormatting string
|
||||||
|
|
||||||
|
const TextFormattingPlain = TextFormatting("Plain")
|
||||||
|
const TextFormattingMarkdown = TextFormatting("Markdown")
|
||||||
|
|
||||||
type ValidateProviderConfigRequest struct {
|
type ValidateProviderConfigRequest struct {
|
||||||
// Config is the raw configuration value for the provider.
|
// Config is the raw configuration value for the provider.
|
||||||
Config cty.Value
|
Config cty.Value
|
||||||
@ -421,3 +474,22 @@ type ReadDataSourceResponse struct {
|
|||||||
// Diagnostics contains any warnings or errors from the method call.
|
// Diagnostics contains any warnings or errors from the method call.
|
||||||
Diagnostics tfdiags.Diagnostics
|
Diagnostics tfdiags.Diagnostics
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CallFunctionRequest struct {
|
||||||
|
Name string
|
||||||
|
Arguments []cty.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
type CallFunctionResponse struct {
|
||||||
|
Result cty.Value
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
|
||||||
|
type CallFunctionArgumentError struct {
|
||||||
|
Text string
|
||||||
|
FunctionArgument int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *CallFunctionArgumentError) Error() string {
|
||||||
|
return err.Text
|
||||||
|
}
|
||||||
|
@ -516,6 +516,10 @@ func (p *MockProvider) ReadDataSource(r providers.ReadDataSourceRequest) (resp p
|
|||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *MockProvider) CallFunction(r providers.CallFunctionRequest) providers.CallFunctionResponse {
|
||||||
|
panic("Not Implemented")
|
||||||
|
}
|
||||||
|
|
||||||
func (p *MockProvider) Close() error {
|
func (p *MockProvider) Close() error {
|
||||||
p.Lock()
|
p.Lock()
|
||||||
defer p.Unlock()
|
defer p.Unlock()
|
||||||
|
Loading…
Reference in New Issue
Block a user