Skip to content

Commit

Permalink
tfprotov5+tfprotov6: Initial provider defined functions implementation (
Browse files Browse the repository at this point in the history
#351)

Reference: hashicorp/terraform#34383
Reference: #353

The next version of the plugin protocol (5.5/6.5) includes support for provider defined functions. This change introduces the initial implementation of that support including:

- Updated Protocol Buffers definitions
- Re-generated Protocol Buffers Go code
- Initial implementations of `tfprotov5` and `tfprotov6` package abstractions and wiring between those abstractions and the Protocol Buffers generated Go code
- Initial implementations of `tfprotov5/tf5server` and `tfprotov6/tf6server` for the new `GetFunctions` and `CallFunction` RPCs

This temporarily will not require `ProviderServer` implementations to include `FunctionServer` implementation, however that change will occur in a subsequent release.
  • Loading branch information
bflad authored Dec 14, 2023
1 parent 60527ee commit 4179bf0
Show file tree
Hide file tree
Showing 28 changed files with 4,722 additions and 1,539 deletions.
6 changes: 6 additions & 0 deletions .changes/unreleased/FEATURES-20231026-164300.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: FEATURES
body: 'tfprotov5+tfprotov6: Upgraded protocols and added types to support
provider-defined functions'
time: 2023-10-26T16:43:00.024481-04:00
custom:
Issue: "351"
6 changes: 6 additions & 0 deletions .changes/unreleased/NOTES-20231026-164359.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: NOTES
body: 'tfprotov5+tfprotov6: An upcoming release will require the FunctionServer
implementation as part of ProviderServer.'
time: 2023-10-26T16:43:59.845786-04:00
custom:
Issue: "351"
6 changes: 6 additions & 0 deletions .changes/unreleased/NOTES-20231214-083437.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: NOTES
body: Provider-defined function support is in technical preview and offered without
compatibility promises until Terraform 1.8 is generally available.
time: 2023-12-14T08:34:37.329545-05:00
custom:
Issue: "351"
3 changes: 3 additions & 0 deletions internal/logging/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ const (
// Attribute of the diagnostic being logged.
KeyDiagnosticAttribute = "diagnostic_attribute"

// Function Argument of the diagnostic being logged.
KeyDiagnosticFunctionArgument = "diagnostic_function_argument"

// Number of the error diagnostics.
KeyDiagnosticErrorCount = "diagnostic_error_count"

Expand Down
4 changes: 4 additions & 0 deletions tfprotov5/diagnostic.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ type Diagnostic struct {
// indicate that the problem is with a certain field in the resource,
// which helps users find the source of the problem.
Attribute *tftypes.AttributePath

// FunctionArgument is the positional function argument for aligning
// configuration source.
FunctionArgument *int64
}

// DiagnosticSeverity represents different classes of Diagnostic which affect
Expand Down
141 changes: 141 additions & 0 deletions tfprotov5/function.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package tfprotov5

import (
"context"

"github.com/hashicorp/terraform-plugin-go/tftypes"
)

// Function describes the definition of a function. Result must be defined.
type Function struct {
// Parameters is the ordered list of positional function parameters.
Parameters []*FunctionParameter

// VariadicParameter is an optional final parameter which accepts zero or
// more argument values, in which Terraform will send an ordered list of the
// parameter type.
VariadicParameter *FunctionParameter

// Return is the function result.
Return *FunctionReturn

// Summary is the shortened human-readable documentation for the function.
Summary string

// Description is the longer human-readable documentation for the function.
Description string

// DescriptionKind indicates the formatting and encoding that the
// Description field is using.
DescriptionKind StringKind

// DeprecationMessage is the human-readable documentation if the function
// is deprecated. This message should be practitioner oriented to explain
// how their configuration should be updated.
DeprecationMessage string
}

// FunctionMetadata describes metadata for a function in the GetMetadata RPC.
type FunctionMetadata struct {
// Name is the name of the function.
Name string
}

// FunctionParameter describes the definition of a function parameter. Type must
// be defined.
type FunctionParameter struct {
// AllowNullValue when enabled denotes that a null argument value can be
// passed to the provider. When disabled, Terraform returns an error if the
// argument value is null.
AllowNullValue bool

// AllowUnknownValues when enabled denotes that any unknown argument value
// (recursively checked for collections) can be passed to the provider. When
// disabled and an unknown value is present, Terraform skips the function
// call entirely and returns an unknown value result from the function.
AllowUnknownValues bool

// Description is the human-readable documentation for the parameter.
Description string

// DescriptionKind indicates the formatting and encoding that the
// Description field is using.
DescriptionKind StringKind

// Name is the human-readable display name for the parameter. Parameters
// are by definition positional and this name is only used in documentation.
Name string

// Type indicates the type of data the parameter expects.
Type tftypes.Type
}

// FunctionReturn describes the definition of a function result. Type must be
// defined.
type FunctionReturn struct {
// Type indicates the type of return data.
Type tftypes.Type
}

// FunctionServer is an interface containing the methods a function
// implementation needs to fill.
type FunctionServer interface {
// CallFunction is called when Terraform wants to execute the logic of a
// function referenced in the configuration.
CallFunction(context.Context, *CallFunctionRequest) (*CallFunctionResponse, error)

// GetFunctions is called when Terraform wants to lookup which functions a
// provider supports when not calling GetProviderSchema.
GetFunctions(context.Context, *GetFunctionsRequest) (*GetFunctionsResponse, error)
}

// CallFunctionRequest is the request Terraform sends when it wants to execute
// the logic of function referenced in the configuration.
type CallFunctionRequest struct {
// Name is the function name being called.
Name string

// Arguments is the configuration value of each argument the practitioner
// supplied for the function call. The ordering and value of each element
// matches the function parameters and their associated type. If the
// function definition includes a final variadic parameter, its value is an
// ordered list of the variadic parameter type.
Arguments []*DynamicValue
}

// CallFunctionResponse is the response from the provider with the result of
// executing the logic of the function.
type CallFunctionResponse struct {
// Diagnostics report errors or warnings related to the execution of the
// function logic. Returning an empty slice indicates a successful response
// with no warnings or errors presented to practitioners.
Diagnostics []*Diagnostic

// Result is the return value from the called function, matching the result
// type in the function definition.
Result *DynamicValue
}

// GetFunctionsRequest is the request Terraform sends when it wants to lookup
// which functions a provider supports when not calling GetProviderSchema.
type GetFunctionsRequest struct{}

// GetFunctionsResponse is the response from the provider about the implemented
// functions.
type GetFunctionsResponse struct {
// Diagnostics report errors or warnings related to the provider
// implementation. Returning an empty slice indicates a successful response
// with no warnings or errors presented to practitioners.
Diagnostics []*Diagnostic

// Functions is a map of function names to their definition.
//
// Unlike data resources and managed resources, the name should NOT be
// prefixed with the provider name and an underscore. Configuration
// references to functions use a separate namespacing syntax that already
// includes the provider name.
Functions map[string]*Function
}
4 changes: 4 additions & 0 deletions tfprotov5/internal/diag/diagnostics.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ func (d Diagnostics) Log(ctx context.Context) {
diagnosticFields[logging.KeyDiagnosticAttribute] = diagnostic.Attribute.String()
}

if diagnostic.FunctionArgument != nil {
diagnosticFields[logging.KeyDiagnosticFunctionArgument] = *diagnostic.FunctionArgument
}

switch diagnostic.Severity {
case tfprotov5.DiagnosticSeverityError:
logging.ProtocolError(ctx, "Response contains error diagnostic", diagnosticFields)
Expand Down
36 changes: 36 additions & 0 deletions tfprotov5/internal/fromproto/function.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package fromproto

import (
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tfprotov5/internal/tfplugin5"
)

func CallFunctionRequest(in *tfplugin5.CallFunction_Request) (*tfprotov5.CallFunctionRequest, error) {
if in == nil {
return nil, nil
}

resp := &tfprotov5.CallFunctionRequest{
Arguments: make([]*tfprotov5.DynamicValue, 0, len(in.Arguments)),
Name: in.Name,
}

for _, argument := range in.Arguments {
resp.Arguments = append(resp.Arguments, DynamicValue(argument))
}

return resp, nil
}

func GetFunctionsRequest(in *tfplugin5.GetFunctions_Request) (*tfprotov5.GetFunctionsRequest, error) {
if in == nil {
return nil, nil
}

resp := &tfprotov5.GetFunctionsRequest{}

return resp, nil
}
Loading

0 comments on commit 4179bf0

Please sign in to comment.