Skip to content

Commit

Permalink
tf6muxserver: Initial implementation
Browse files Browse the repository at this point in the history
Reference: #19

This new package implements a protocol version 6 compatible mux server. It is implemented similar to the existing protocol version 5 mux server, where validation and schema caching occurs upfront, then most RPCs act as a router.

The main differences between the existing implementation and this new one are as follows:

- Functionality is bundled into a package below the root package.
- Naming is simplified and clarified. Previously, the capabilities of the mux server were believed to become much more dynamic, however in practice this never materialized due to the protocol. The mux server serves much more than a "Schema" so it is named "Mux" to prevent confusion.
- There is no separation between a "Factory" and creating the mux server. There is not currently a benefit to introducing two layers of mux server handling. The main and only necessary entrypoint is a `NewMuxServer` function.
- A `ProviderServer` method is provided to further simplify implementations, rather than needing a wrapping `func() tfprotov6.ProviderServer`.

Once this package lands, the existing `SchemaServer` and `SchemaServerFactory` handling in the root package will be converted into a new `tf5muxserver` package with a similar API and structure as this one. That breaking change will occur afterwards before the next release.
  • Loading branch information
bflad committed Jan 13, 2022
1 parent d08a76f commit 33e2922
Show file tree
Hide file tree
Showing 34 changed files with 2,816 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .changelog/pending.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:feature
Added `tf6muxserver` package, a protocol version 6 compatible mux server
```
42 changes: 41 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,47 @@ Plugin SDK or higher to be able to be used with terraform-plugin-mux.

## Getting Started

terraform-plugin-mux exposes a minimal interface:
Functionality for a provider server is based on the protocol version. There are currently two main protocol versions in use today, protocol version 5 and protocol version 6, based on the development framework being used:

- terraform-plugin-framwork: Implements protocol version 6.
- terraform-plugin-sdk: Implements protocol version 5.
- terraform-plugin-go: Implements either protocol version, based on whether the `tf5server` package (protocol version 5) or `tf6server` package (protocol version 6) is being used.

To combine providers together, each must implement the same protocol version.

### Protocol Version 6

Protocol version 6 providers can be combined using the [`tf6muxserver.Mux` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-mux/tf6muxserver#NewMuxServer):

```go
func main() {
ctx := context.Background()
providers := []func() tfprotov6.ProviderServer{
// Example terraform-plugin-framework ProviderServer function
// frameworkprovider.Provider().ProviderServer,
//
// Example terraform-plugin-go ProviderServer function
// goprovider.Provider(),
}

muxServer, err := tf6muxserver.NewMuxServer(ctx, providers...)

if err != nil {
log.Fatalln(err.Error())
}

// Use the result to start a muxed provider
err = tf6server.Serve("registry.terraform.io/namespace/example", muxServer.ProviderServer)

if err != nil {
log.Fatalln(err.Error())
}
}
```

### Protocol Version 5

Protocol version 5 providers can be combined using the root package [`NewSchemaServerFactory()` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-mux#NewSchemaServerFactory):

```go
func main() {
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,23 @@ github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9
github.com/hashicorp/go-hclog v0.16.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v1.0.0 h1:bkKf0BeBXcSYa7f5Fyi9gMuQ8gNsxeiNpZjR6VxNZeo=
github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-plugin v1.3.0 h1:4d/wJojzvHV1I4i/rrjVaeuyxWrLzDE1mDCyDy8fXS8=
github.com/hashicorp/go-plugin v1.3.0/go.mod h1:F9eH4LrE/ZsRdbwhfjs9k9HoDUwAHnYtXdgmf1AVNs0=
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/terraform-plugin-go v0.5.0 h1:+gCDdF0hcYCm0YBTxrP4+K1NGIS5ZKZBKDORBewLJmg=
github.com/hashicorp/terraform-plugin-go v0.5.0/go.mod h1:PAVN26PNGpkkmsvva1qfriae5Arky3xl3NfzKa8XFVM=
github.com/hashicorp/terraform-plugin-log v0.2.0/go.mod h1:E1kJmapEHzqu1x6M++gjvhzM2yMQNXPVWZRCB8sgYjg=
github.com/hashicorp/terraform-plugin-log v0.2.1 h1:hl0G6ctSx7DRTE62VNsPWrq7d+JWy1kjk9ApOFrCq3I=
github.com/hashicorp/terraform-plugin-log v0.2.1/go.mod h1:RW/n0x4dyITmenuirZ1ViPQGP5JQdPTZ4Wwc0rLKi94=
github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896 h1:1FGtlkJw87UsTMg5s8jrekrHmUPUJaMcu6ELiVhQrNw=
github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896/go.mod h1:bzBPnUIkI0RxauU8Dqo+2KrZZ28Cf48s8V6IHt3p4co=
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0=
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE=
github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
Expand All @@ -69,6 +75,7 @@ github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJ
github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k+Mg7cowZ8yv4Trqw9UsJby758=
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs=
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand Down Expand Up @@ -112,6 +119,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
Expand All @@ -126,12 +134,14 @@ google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpC
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
Expand Down
20 changes: 20 additions & 0 deletions internal/logging/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"

"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-plugin-log/tfsdklog"
)
Expand All @@ -16,6 +17,15 @@ func InitContext(ctx context.Context) context.Context {
return ctx
}

// RpcContext injects the RPC name into logger contexts.
func RpcContext(ctx context.Context, rpc string) context.Context {
ctx = tflog.With(ctx, KeyTfRpc, rpc)
ctx = tfsdklog.With(ctx, KeyTfRpc, rpc)
ctx = tfsdklog.SubsystemWith(ctx, SubsystemMux, KeyTfRpc, rpc)

return ctx
}

// Tfprotov5ProviderServerContext injects the chosen provider Go type
func Tfprotov5ProviderServerContext(ctx context.Context, p tfprotov5.ProviderServer) context.Context {
providerType := fmt.Sprintf("%T", p)
Expand All @@ -25,3 +35,13 @@ func Tfprotov5ProviderServerContext(ctx context.Context, p tfprotov5.ProviderSer

return ctx
}

// Tfprotov6ProviderServerContext injects the chosen provider Go type
func Tfprotov6ProviderServerContext(ctx context.Context, p tfprotov6.ProviderServer) context.Context {
providerType := fmt.Sprintf("%T", p)
ctx = tflog.With(ctx, KeyTfMuxProvider, providerType)
ctx = tfsdklog.With(ctx, KeyTfMuxProvider, providerType)
ctx = tfsdklog.SubsystemWith(ctx, SubsystemMux, KeyTfMuxProvider, providerType)

return ctx
}
3 changes: 3 additions & 0 deletions internal/logging/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@ package logging
const (
// Go type of the provider selected by mux.
KeyTfMuxProvider = "tf_mux_provider"

// The RPC being run, such as "ApplyResourceChange"
KeyTfRpc = "tf_rpc"
)
155 changes: 155 additions & 0 deletions internal/tf6testserver/tf6testserver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package tf6testserver

import (
"context"

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

var _ tfprotov6.ProviderServer = &TestServer{}

type TestServer struct {
DataSourceSchemas map[string]*tfprotov6.Schema
ProviderMetaSchema *tfprotov6.Schema
ProviderSchema *tfprotov6.Schema
ResourceSchemas map[string]*tfprotov6.Schema

ApplyResourceChangeCalled map[string]bool

ConfigureProviderCalled bool

ImportResourceStateCalled map[string]bool

PlanResourceChangeCalled map[string]bool

ReadDataSourceCalled map[string]bool

ReadResourceCalled map[string]bool

StopProviderCalled bool
StopProviderError string

UpgradeResourceStateCalled map[string]bool

ValidateDataResourceConfigCalled map[string]bool

ValidateProviderConfigCalled bool
ValidateProviderConfigResponse *tfprotov6.ValidateProviderConfigResponse

ValidateResourceConfigCalled map[string]bool
}

func (s *TestServer) ProviderServer() tfprotov6.ProviderServer {
return s
}

func (s *TestServer) ApplyResourceChange(_ context.Context, req *tfprotov6.ApplyResourceChangeRequest) (*tfprotov6.ApplyResourceChangeResponse, error) {
if s.ApplyResourceChangeCalled == nil {
s.ApplyResourceChangeCalled = make(map[string]bool)
}

s.ApplyResourceChangeCalled[req.TypeName] = true
return nil, nil
}

func (s *TestServer) ConfigureProvider(_ context.Context, _ *tfprotov6.ConfigureProviderRequest) (*tfprotov6.ConfigureProviderResponse, error) {
s.ConfigureProviderCalled = true
return &tfprotov6.ConfigureProviderResponse{}, nil
}

func (s *TestServer) GetProviderSchema(_ context.Context, _ *tfprotov6.GetProviderSchemaRequest) (*tfprotov6.GetProviderSchemaResponse, error) {
if s.DataSourceSchemas == nil {
s.DataSourceSchemas = make(map[string]*tfprotov6.Schema)
}

if s.ResourceSchemas == nil {
s.ResourceSchemas = make(map[string]*tfprotov6.Schema)
}

return &tfprotov6.GetProviderSchemaResponse{
Provider: s.ProviderSchema,
ProviderMeta: s.ProviderMetaSchema,
ResourceSchemas: s.ResourceSchemas,
DataSourceSchemas: s.DataSourceSchemas,
}, nil
}

func (s *TestServer) ImportResourceState(_ context.Context, req *tfprotov6.ImportResourceStateRequest) (*tfprotov6.ImportResourceStateResponse, error) {
if s.ImportResourceStateCalled == nil {
s.ImportResourceStateCalled = make(map[string]bool)
}

s.ImportResourceStateCalled[req.TypeName] = true
return nil, nil
}

func (s *TestServer) PlanResourceChange(_ context.Context, req *tfprotov6.PlanResourceChangeRequest) (*tfprotov6.PlanResourceChangeResponse, error) {
if s.PlanResourceChangeCalled == nil {
s.PlanResourceChangeCalled = make(map[string]bool)
}

s.PlanResourceChangeCalled[req.TypeName] = true
return nil, nil
}

func (s *TestServer) ReadDataSource(_ context.Context, req *tfprotov6.ReadDataSourceRequest) (*tfprotov6.ReadDataSourceResponse, error) {
if s.ReadDataSourceCalled == nil {
s.ReadDataSourceCalled = make(map[string]bool)
}

s.ReadDataSourceCalled[req.TypeName] = true
return nil, nil
}

func (s *TestServer) ReadResource(_ context.Context, req *tfprotov6.ReadResourceRequest) (*tfprotov6.ReadResourceResponse, error) {
if s.ReadResourceCalled == nil {
s.ReadResourceCalled = make(map[string]bool)
}

s.ReadResourceCalled[req.TypeName] = true
return nil, nil
}

func (s *TestServer) StopProvider(_ context.Context, _ *tfprotov6.StopProviderRequest) (*tfprotov6.StopProviderResponse, error) {
s.StopProviderCalled = true

if s.StopProviderError != "" {
return &tfprotov6.StopProviderResponse{
Error: s.StopProviderError,
}, nil
}

return &tfprotov6.StopProviderResponse{}, nil
}

func (s *TestServer) UpgradeResourceState(_ context.Context, req *tfprotov6.UpgradeResourceStateRequest) (*tfprotov6.UpgradeResourceStateResponse, error) {
if s.UpgradeResourceStateCalled == nil {
s.UpgradeResourceStateCalled = make(map[string]bool)
}

s.UpgradeResourceStateCalled[req.TypeName] = true
return nil, nil
}

func (s *TestServer) ValidateDataResourceConfig(_ context.Context, req *tfprotov6.ValidateDataResourceConfigRequest) (*tfprotov6.ValidateDataResourceConfigResponse, error) {
if s.ValidateDataResourceConfigCalled == nil {
s.ValidateDataResourceConfigCalled = make(map[string]bool)
}

s.ValidateDataResourceConfigCalled[req.TypeName] = true
return nil, nil
}

func (s *TestServer) ValidateResourceConfig(_ context.Context, req *tfprotov6.ValidateResourceConfigRequest) (*tfprotov6.ValidateResourceConfigResponse, error) {
if s.ValidateResourceConfigCalled == nil {
s.ValidateResourceConfigCalled = make(map[string]bool)
}

s.ValidateResourceConfigCalled[req.TypeName] = true
return nil, nil
}

func (s *TestServer) ValidateProviderConfig(_ context.Context, req *tfprotov6.ValidateProviderConfigRequest) (*tfprotov6.ValidateProviderConfigResponse, error) {
s.ValidateProviderConfigCalled = true
return s.ValidateProviderConfigResponse, nil
}
Loading

0 comments on commit 33e2922

Please sign in to comment.