Merge pull request #34603 from hashicorp/jbardin/remove-provider-funtion-warnings

provider functions can only return an error
pull/34740/head
James Bardin 2 years ago committed by GitHub
commit 300f66b71d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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"
@ -751,7 +752,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
}
@ -765,15 +766,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))
@ -787,7 +788,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{
@ -800,17 +801,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"
@ -740,7 +741,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
}
@ -754,15 +755,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))
@ -776,7 +777,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{
@ -789,17 +790,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
}

@ -2536,23 +2536,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…
Cancel
Save