mirror of
https://github.com/hashicorp/terraform.git
synced 2026-06-04 22:27:25 -04:00
provider functions return an error
The call site for language functions doesn't currently have a way to handle complex diagnostics, so rather than appear to support them in the protocol we remove the concepts of diagnostics for now. We do however retain the argument index fields, which we can wrap in a function.ArgError and get a little more precise hcl diagnostic from expression.
This commit is contained in:
parent
f44e743f90
commit
a8701f6ddd
12 changed files with 2323 additions and 2135 deletions
|
|
@ -41,10 +41,13 @@ message Diagnostic {
|
|||
string summary = 2;
|
||||
string detail = 3;
|
||||
AttributePath attribute = 4;
|
||||
}
|
||||
|
||||
// function_argument is the positional function argument for aligning
|
||||
// configuration source.
|
||||
optional int64 function_argument = 5;
|
||||
message FunctionError {
|
||||
string text = 1;
|
||||
// The optional function_argument records the index position of the
|
||||
// argument which caused the error.
|
||||
optional int64 function_argument = 2;
|
||||
}
|
||||
|
||||
message AttributePath {
|
||||
|
|
@ -569,6 +572,6 @@ message CallFunction {
|
|||
}
|
||||
message Response {
|
||||
DynamicValue result = 1;
|
||||
repeated Diagnostic diagnostics = 2;
|
||||
FunctionError error = 2;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,10 +41,13 @@ message Diagnostic {
|
|||
string summary = 2;
|
||||
string detail = 3;
|
||||
AttributePath attribute = 4;
|
||||
}
|
||||
|
||||
// function_argument is the positional function argument for aligning
|
||||
// configuration source.
|
||||
optional int64 function_argument = 5;
|
||||
message FunctionError {
|
||||
string text = 1;
|
||||
// The optional function_argument records the index position of the
|
||||
// argument which caused the error.
|
||||
optional int64 function_argument = 2;
|
||||
}
|
||||
|
||||
message AttributePath {
|
||||
|
|
@ -549,6 +552,6 @@ message CallFunction {
|
|||
}
|
||||
message Response {
|
||||
DynamicValue result = 1;
|
||||
repeated Diagnostic diagnostics = 2;
|
||||
FunctionError error = 2;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ package grpcwrap
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
ctyjson "github.com/zclconf/go-cty/cty/json"
|
||||
"github.com/zclconf/go-cty/cty/msgpack"
|
||||
"google.golang.org/grpc/codes"
|
||||
|
|
@ -444,15 +444,18 @@ func (p *provider) CallFunction(_ context.Context, req *tfplugin5.CallFunction_R
|
|||
if len(req.Arguments) != 0 {
|
||||
args = make([]cty.Value, len(req.Arguments))
|
||||
for i, rawArg := range req.Arguments {
|
||||
idx := int64(i)
|
||||
|
||||
var argTy cty.Type
|
||||
if i < len(funcSchema.Parameters) {
|
||||
argTy = funcSchema.Parameters[i].Type
|
||||
} else {
|
||||
if funcSchema.VariadicParameter == nil {
|
||||
resp.Diagnostics = convert.AppendProtoDiag(
|
||||
resp.Diagnostics, fmt.Errorf("too many arguments for non-variadic function"),
|
||||
)
|
||||
|
||||
resp.Error = &tfplugin5.FunctionError{
|
||||
Text: "too many arguments for non-variadic function",
|
||||
FunctionArgument: &idx,
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
argTy = funcSchema.VariadicParameter.Type
|
||||
|
|
@ -460,9 +463,13 @@ func (p *provider) CallFunction(_ context.Context, req *tfplugin5.CallFunction_R
|
|||
|
||||
argVal, err := decodeDynamicValue(rawArg, argTy)
|
||||
if err != nil {
|
||||
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
|
||||
resp.Error = &tfplugin5.FunctionError{
|
||||
Text: err.Error(),
|
||||
FunctionArgument: &idx,
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
args[i] = argVal
|
||||
}
|
||||
}
|
||||
|
|
@ -471,14 +478,26 @@ func (p *provider) CallFunction(_ context.Context, req *tfplugin5.CallFunction_R
|
|||
FunctionName: req.Name,
|
||||
Arguments: args,
|
||||
})
|
||||
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, callResp.Diagnostics)
|
||||
if callResp.Diagnostics.HasErrors() {
|
||||
|
||||
if callResp.Err != nil {
|
||||
resp.Error = &tfplugin5.FunctionError{
|
||||
Text: callResp.Err.Error(),
|
||||
}
|
||||
|
||||
if argErr, ok := callResp.Err.(function.ArgError); ok {
|
||||
idx := int64(argErr.Index)
|
||||
resp.Error.FunctionArgument = &idx
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
resp.Result, err = encodeDynamicValue(callResp.Result, funcSchema.ReturnType)
|
||||
if err != nil {
|
||||
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
|
||||
resp.Error = &tfplugin5.FunctionError{
|
||||
Text: err.Error(),
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ package grpcwrap
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
ctyjson "github.com/zclconf/go-cty/cty/json"
|
||||
"github.com/zclconf/go-cty/cty/msgpack"
|
||||
"google.golang.org/grpc/codes"
|
||||
|
|
@ -445,15 +445,17 @@ func (p *provider6) CallFunction(_ context.Context, req *tfplugin6.CallFunction_
|
|||
if len(req.Arguments) != 0 {
|
||||
args = make([]cty.Value, len(req.Arguments))
|
||||
for i, rawArg := range req.Arguments {
|
||||
idx := int64(i)
|
||||
|
||||
var argTy cty.Type
|
||||
if i < len(funcSchema.Parameters) {
|
||||
argTy = funcSchema.Parameters[i].Type
|
||||
} else {
|
||||
if funcSchema.VariadicParameter == nil {
|
||||
resp.Diagnostics = convert.AppendProtoDiag(
|
||||
resp.Diagnostics, fmt.Errorf("too many arguments for non-variadic function"),
|
||||
)
|
||||
resp.Error = &tfplugin6.FunctionError{
|
||||
Text: "too many arguments for non-variadic function",
|
||||
FunctionArgument: &idx,
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
argTy = funcSchema.VariadicParameter.Type
|
||||
|
|
@ -461,9 +463,13 @@ func (p *provider6) CallFunction(_ context.Context, req *tfplugin6.CallFunction_
|
|||
|
||||
argVal, err := decodeDynamicValue6(rawArg, argTy)
|
||||
if err != nil {
|
||||
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
|
||||
resp.Error = &tfplugin6.FunctionError{
|
||||
Text: err.Error(),
|
||||
FunctionArgument: &idx,
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
args[i] = argVal
|
||||
}
|
||||
}
|
||||
|
|
@ -472,14 +478,25 @@ func (p *provider6) CallFunction(_ context.Context, req *tfplugin6.CallFunction_
|
|||
FunctionName: req.Name,
|
||||
Arguments: args,
|
||||
})
|
||||
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, callResp.Diagnostics)
|
||||
if callResp.Diagnostics.HasErrors() {
|
||||
if callResp.Err != nil {
|
||||
resp.Error = &tfplugin6.FunctionError{
|
||||
Text: callResp.Err.Error(),
|
||||
}
|
||||
|
||||
if argErr, ok := callResp.Err.(function.ArgError); ok {
|
||||
idx := int64(argErr.Index)
|
||||
resp.Error.FunctionArgument = &idx
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
resp.Result, err = encodeDynamicValue6(callResp.Result, funcSchema.ReturnType)
|
||||
if err != nil {
|
||||
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
|
||||
resp.Error = &tfplugin6.FunctionError{
|
||||
Text: err.Error(),
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
plugin "github.com/hashicorp/go-plugin"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
ctyjson "github.com/zclconf/go-cty/cty/json"
|
||||
"github.com/zclconf/go-cty/cty/msgpack"
|
||||
"google.golang.org/grpc"
|
||||
|
|
@ -741,7 +742,7 @@ func (p *GRPCProvider) CallFunction(r providers.CallFunctionRequest) (resp provi
|
|||
|
||||
schema := p.GetProviderSchema()
|
||||
if schema.Diagnostics.HasErrors() {
|
||||
resp.Diagnostics = schema.Diagnostics
|
||||
resp.Err = schema.Diagnostics.Err()
|
||||
return resp
|
||||
}
|
||||
|
||||
|
|
@ -755,15 +756,15 @@ func (p *GRPCProvider) CallFunction(r providers.CallFunctionRequest) (resp provi
|
|||
// Should only get here if the caller has a bug, because we should
|
||||
// have detected earlier any attempt to call a function that the
|
||||
// provider didn't declare.
|
||||
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("provider has no function named %q", r.FunctionName))
|
||||
resp.Err = fmt.Errorf("provider has no function named %q", r.FunctionName)
|
||||
return resp
|
||||
}
|
||||
if len(r.Arguments) < len(funcDecl.Parameters) {
|
||||
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("not enough arguments for function %q", r.FunctionName))
|
||||
resp.Err = fmt.Errorf("not enough arguments for function %q", r.FunctionName)
|
||||
return resp
|
||||
}
|
||||
if funcDecl.VariadicParameter == nil && len(r.Arguments) > len(funcDecl.Parameters) {
|
||||
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("too many arguments for function %q", r.FunctionName))
|
||||
resp.Err = fmt.Errorf("too many arguments for function %q", r.FunctionName)
|
||||
return resp
|
||||
}
|
||||
args := make([]*proto.DynamicValue, len(r.Arguments))
|
||||
|
|
@ -777,7 +778,7 @@ func (p *GRPCProvider) CallFunction(r providers.CallFunctionRequest) (resp provi
|
|||
|
||||
argValRaw, err := msgpack.Marshal(argVal, paramDecl.Type)
|
||||
if err != nil {
|
||||
resp.Diagnostics = resp.Diagnostics.Append(err)
|
||||
resp.Err = err
|
||||
return resp
|
||||
}
|
||||
args[i] = &proto.DynamicValue{
|
||||
|
|
@ -790,17 +791,28 @@ func (p *GRPCProvider) CallFunction(r providers.CallFunctionRequest) (resp provi
|
|||
Arguments: args,
|
||||
})
|
||||
if err != nil {
|
||||
resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err))
|
||||
// functions can only support simple errors, but use our grpcError
|
||||
// diagnostic function to format common problems is a more
|
||||
// user-friendly manner.
|
||||
resp.Err = grpcErr(err).Err()
|
||||
return resp
|
||||
}
|
||||
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
|
||||
if resp.Diagnostics.HasErrors() {
|
||||
|
||||
if protoResp.Error != nil {
|
||||
resp.Err = errors.New(protoResp.Error.Text)
|
||||
|
||||
// If this is a problem with a specific argument, we can wrap the error
|
||||
// in a function.ArgError
|
||||
if protoResp.Error.FunctionArgument != nil {
|
||||
resp.Err = function.NewArgError(int(*protoResp.Error.FunctionArgument), resp.Err)
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
resultVal, err := decodeDynamicValue(protoResp.Result, funcDecl.ReturnType)
|
||||
if err != nil {
|
||||
resp.Diagnostics = resp.Diagnostics.Append(err)
|
||||
resp.Err = err
|
||||
return resp
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
plugin "github.com/hashicorp/go-plugin"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
ctyjson "github.com/zclconf/go-cty/cty/json"
|
||||
"github.com/zclconf/go-cty/cty/msgpack"
|
||||
"google.golang.org/grpc"
|
||||
|
|
@ -730,7 +731,7 @@ func (p *GRPCProvider) CallFunction(r providers.CallFunctionRequest) (resp provi
|
|||
|
||||
schema := p.GetProviderSchema()
|
||||
if schema.Diagnostics.HasErrors() {
|
||||
resp.Diagnostics = schema.Diagnostics
|
||||
resp.Err = schema.Diagnostics.Err()
|
||||
return resp
|
||||
}
|
||||
|
||||
|
|
@ -744,15 +745,15 @@ func (p *GRPCProvider) CallFunction(r providers.CallFunctionRequest) (resp provi
|
|||
// Should only get here if the caller has a bug, because we should
|
||||
// have detected earlier any attempt to call a function that the
|
||||
// provider didn't declare.
|
||||
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("provider has no function named %q", r.FunctionName))
|
||||
resp.Err = fmt.Errorf("provider has no function named %q", r.FunctionName)
|
||||
return resp
|
||||
}
|
||||
if len(r.Arguments) < len(funcDecl.Parameters) {
|
||||
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("not enough arguments for function %q", r.FunctionName))
|
||||
resp.Err = fmt.Errorf("not enough arguments for function %q", r.FunctionName)
|
||||
return resp
|
||||
}
|
||||
if funcDecl.VariadicParameter == nil && len(r.Arguments) > len(funcDecl.Parameters) {
|
||||
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("too many arguments for function %q", r.FunctionName))
|
||||
resp.Err = fmt.Errorf("too many arguments for function %q", r.FunctionName)
|
||||
return resp
|
||||
}
|
||||
args := make([]*proto6.DynamicValue, len(r.Arguments))
|
||||
|
|
@ -766,7 +767,7 @@ func (p *GRPCProvider) CallFunction(r providers.CallFunctionRequest) (resp provi
|
|||
|
||||
argValRaw, err := msgpack.Marshal(argVal, paramDecl.Type)
|
||||
if err != nil {
|
||||
resp.Diagnostics = resp.Diagnostics.Append(err)
|
||||
resp.Err = err
|
||||
return resp
|
||||
}
|
||||
args[i] = &proto6.DynamicValue{
|
||||
|
|
@ -779,17 +780,28 @@ func (p *GRPCProvider) CallFunction(r providers.CallFunctionRequest) (resp provi
|
|||
Arguments: args,
|
||||
})
|
||||
if err != nil {
|
||||
resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err))
|
||||
// functions can only support simple errors, but use our grpcError
|
||||
// diagnostic function to format common problems is a more
|
||||
// user-friendly manner.
|
||||
resp.Err = grpcErr(err).Err()
|
||||
return resp
|
||||
}
|
||||
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
|
||||
if resp.Diagnostics.HasErrors() {
|
||||
|
||||
if protoResp.Error != nil {
|
||||
resp.Err = errors.New(protoResp.Error.Text)
|
||||
|
||||
// If this is a problem with a specific argument, we can wrap the error
|
||||
// in a function.ArgError
|
||||
if protoResp.Error.FunctionArgument != nil {
|
||||
resp.Err = function.NewArgError(int(*protoResp.Error.FunctionArgument), resp.Err)
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
resultVal, err := decodeDynamicValue(protoResp.Result, funcDecl.ReturnType)
|
||||
if err != nil {
|
||||
resp.Diagnostics = resp.Diagnostics.Append(err)
|
||||
resp.Err = err
|
||||
return resp
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ func (s simple) ReadDataSource(req providers.ReadDataSourceRequest) (resp provid
|
|||
|
||||
func (s simple) CallFunction(req providers.CallFunctionRequest) (resp providers.CallFunctionResponse) {
|
||||
if req.FunctionName != "noop" {
|
||||
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("CallFunction for undefined function %q", req.FunctionName))
|
||||
resp.Err = fmt.Errorf("CallFunction for undefined function %q", req.FunctionName)
|
||||
return resp
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -114,11 +114,8 @@ func (d FunctionDecl) BuildFunction(providerAddr addrs.Provider, name string, re
|
|||
FunctionName: name,
|
||||
Arguments: args,
|
||||
})
|
||||
// NOTE: We don't actually have any way to surface warnings
|
||||
// from the function here, because functions just return normal
|
||||
// Go errors rather than diagnostics.
|
||||
if resp.Diagnostics.HasErrors() {
|
||||
return cty.UnknownVal(retType), resp.Diagnostics.Err()
|
||||
if resp.Err != nil {
|
||||
return cty.UnknownVal(retType), resp.Err
|
||||
}
|
||||
|
||||
if resp.Result == cty.NilVal {
|
||||
|
|
|
|||
|
|
@ -501,6 +501,8 @@ type CallFunctionResponse struct {
|
|||
// so can be left as cty.NilVal to represent the absense of a value.
|
||||
Result cty.Value
|
||||
|
||||
// Diagnostics contains any warnings or errors from the function call.
|
||||
Diagnostics tfdiags.Diagnostics
|
||||
// Err is the error value from the function call. This may be an instance
|
||||
// of function.ArgError from the go-cty package to specify a problem with a
|
||||
// specific argument.
|
||||
Err error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2534,23 +2534,23 @@ func TestContext2Validate_providerContributedFunctions(t *testing.T) {
|
|||
}
|
||||
p.CallFunctionFn = func(req providers.CallFunctionRequest) (resp providers.CallFunctionResponse) {
|
||||
if req.FunctionName != "count_e" {
|
||||
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("incorrect function name %q", req.FunctionName))
|
||||
resp.Err = fmt.Errorf("incorrect function name %q", req.FunctionName)
|
||||
return resp
|
||||
}
|
||||
if len(req.Arguments) != 1 {
|
||||
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("wrong number of arguments %d", len(req.Arguments)))
|
||||
resp.Err = fmt.Errorf("wrong number of arguments %d", len(req.Arguments))
|
||||
return resp
|
||||
}
|
||||
if req.Arguments[0].Type() != cty.String {
|
||||
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("wrong argument type %#v", req.Arguments[0].Type()))
|
||||
resp.Err = fmt.Errorf("wrong argument type %#v", req.Arguments[0].Type())
|
||||
return resp
|
||||
}
|
||||
if !req.Arguments[0].IsKnown() {
|
||||
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("argument is unknown"))
|
||||
resp.Err = fmt.Errorf("argument is unknown")
|
||||
return resp
|
||||
}
|
||||
if req.Arguments[0].IsNull() {
|
||||
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("argument is null"))
|
||||
resp.Err = fmt.Errorf("argument is null")
|
||||
return resp
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue