Skip to content

Commit

Permalink
Introduce internal/fwserver package and migrate GetProviderSchema (#319)
Browse files Browse the repository at this point in the history
Reference: #215

This step of the provider server refactoring introduces a new `internal/fwserver` package that will contain the provider server implementation for any protocol version, there only being a version 6 implementation at the moment. This package will contain only framework-native types that aren't already defined by the `tfsdk` package that are used by provider developers today, which is necessary to prevent import cycles. The protocol specific implementations will then wrap this framework server implementation and handle any necessary conversion of protocol specific types to framework specific types and back. Eventually, the existing `internal/proto6server` implementation will make no reference to any framework native types except the framework server and protocol type conversions.

For now, only the `GetProviderSchema` RPC is migrated to show what this refactoring will look like for the rest of the RPCs.

This has a few benefits which can be seen here including clear abstractions for protocol specific handling versus generic framework handling and unit testing at those abstraction boundaries.
  • Loading branch information
bflad committed May 6, 2022
1 parent 93ea6d3 commit 555a882
Show file tree
Hide file tree
Showing 14 changed files with 4,038 additions and 157 deletions.
3 changes: 3 additions & 0 deletions internal/fromproto6/doc.go
@@ -0,0 +1,3 @@
// Package fromproto6 contains functions to convert from protocol version 6
// (tfprotov6) types to framework types.
package fromproto6
20 changes: 20 additions & 0 deletions internal/fromproto6/getproviderschema.go
@@ -0,0 +1,20 @@
package fromproto6

import (
"context"

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

// GetProviderSchemaRequest returns the *fwserver.GetProviderSchemaRequest
// equivalent of a *tfprotov6.GetProviderSchemaRequest.
func GetProviderSchemaRequest(ctx context.Context, proto6 *tfprotov6.GetProviderSchemaRequest) *fwserver.GetProviderSchemaRequest {
if proto6 == nil {
return nil
}

fw := &fwserver.GetProviderSchemaRequest{}

return fw
}
43 changes: 43 additions & 0 deletions internal/fromproto6/getproviderschema_test.go
@@ -0,0 +1,43 @@
package fromproto6_test

import (
"context"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/internal/fromproto6"
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
)

func TestGetProviderSchemaRequest(t *testing.T) {
t.Parallel()

testCases := map[string]struct {
input *tfprotov6.GetProviderSchemaRequest
expected *fwserver.GetProviderSchemaRequest
}{
"nil": {
input: nil,
expected: nil,
},
"empty": {
input: &tfprotov6.GetProviderSchemaRequest{},
expected: &fwserver.GetProviderSchemaRequest{},
},
}

for name, testCase := range testCases {
name, testCase := name, testCase

t.Run(name, func(t *testing.T) {
t.Parallel()

got := fromproto6.GetProviderSchemaRequest(context.Background(), testCase.input)

if diff := cmp.Diff(got, testCase.expected); diff != "" {
t.Errorf("unexpected difference: %s", diff)
}
})
}
}
5 changes: 5 additions & 0 deletions internal/fwserver/doc.go
@@ -0,0 +1,5 @@
// Package fwserver contains the framework provider server implementation.
// This package should only ever contain framework-native types, while specific
// protocol version compatible implementations, such as proto6server, are
// implemented on top of this abstraction.
package fwserver
12 changes: 12 additions & 0 deletions internal/fwserver/server.go
@@ -0,0 +1,12 @@
package fwserver

import (
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
)

// Server implements the framework provider server. Protocol specific
// implementations wrap this handling along with calling all request and
// response type conversions.
type Server struct {
Provider tfsdk.Provider
}
120 changes: 120 additions & 0 deletions internal/fwserver/server_getproviderschema.go
@@ -0,0 +1,120 @@
package fwserver

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/logging"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
)

// GetProviderSchemaRequest is the framework server request for the
// GetProviderSchema RPC.
type GetProviderSchemaRequest struct{}

// GetProviderSchemaResponse is the framework server response for the
// GetProviderSchema RPC.
type GetProviderSchemaResponse struct {
Provider *tfsdk.Schema
ProviderMeta *tfsdk.Schema
ResourceSchemas map[string]*tfsdk.Schema
DataSourceSchemas map[string]*tfsdk.Schema
Diagnostics diag.Diagnostics
}

// GetProviderSchema implements the framework server GetProviderSchema RPC.
func (s *Server) GetProviderSchema(ctx context.Context, req *GetProviderSchemaRequest, resp *GetProviderSchemaResponse) {
logging.FrameworkDebug(ctx, "Calling provider defined Provider GetSchema")
providerSchema, diags := s.Provider.GetSchema(ctx)
logging.FrameworkDebug(ctx, "Called provider defined Provider GetSchema")

resp.Diagnostics.Append(diags...)

if diags.HasError() {
return
}

resp.Provider = &providerSchema

if pm, ok := s.Provider.(tfsdk.ProviderWithProviderMeta); ok {
logging.FrameworkTrace(ctx, "Provider implements ProviderWithProviderMeta")

logging.FrameworkDebug(ctx, "Calling provider defined Provider GetMetaSchema")
providerMetaSchema, diags := pm.GetMetaSchema(ctx)
logging.FrameworkDebug(ctx, "Called provider defined Provider GetMetaSchema")

resp.Diagnostics.Append(diags...)

if resp.Diagnostics.HasError() {
return
}

resp.ProviderMeta = &providerMetaSchema
}

// TODO: Cache GetDataSources call
// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/299
logging.FrameworkDebug(ctx, "Calling provider defined Provider GetResources")
resourceSchemas, diags := s.Provider.GetResources(ctx)
logging.FrameworkDebug(ctx, "Called provider defined Provider GetResources")

resp.Diagnostics.Append(diags...)

if resp.Diagnostics.HasError() {
return
}

if len(resourceSchemas) > 0 {
resp.ResourceSchemas = map[string]*tfsdk.Schema{}
}

for k, v := range resourceSchemas {
// KeyResourceType field only necessary here since we are in GetProviderSchema RPC
logging.FrameworkTrace(ctx, "Found resource type", map[string]interface{}{logging.KeyResourceType: k})

logging.FrameworkDebug(ctx, "Calling provider defined ResourceType GetSchema", map[string]interface{}{logging.KeyResourceType: k})
schema, diags := v.GetSchema(ctx)
logging.FrameworkDebug(ctx, "Called provider defined ResourceType GetSchema", map[string]interface{}{logging.KeyResourceType: k})

resp.Diagnostics.Append(diags...)

if resp.Diagnostics.HasError() {
return
}

resp.ResourceSchemas[k] = &schema
}

// TODO: Cache GetDataSources call
// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/299
logging.FrameworkDebug(ctx, "Calling provider defined Provider GetDataSources")
dataSourceSchemas, diags := s.Provider.GetDataSources(ctx)
logging.FrameworkDebug(ctx, "Called provider defined Provider GetDataSources")

resp.Diagnostics.Append(diags...)

if resp.Diagnostics.HasError() {
return
}

if len(dataSourceSchemas) > 0 {
resp.DataSourceSchemas = map[string]*tfsdk.Schema{}
}

for k, v := range dataSourceSchemas {
// KeyDataSourceType field only necessary here since we are in GetProviderSchema RPC
logging.FrameworkTrace(ctx, "Found data source type", map[string]interface{}{logging.KeyDataSourceType: k})

logging.FrameworkDebug(ctx, "Calling provider defined DataSourceType GetSchema", map[string]interface{}{logging.KeyDataSourceType: k})
schema, diags := v.GetSchema(ctx)
logging.FrameworkDebug(ctx, "Called provider defined DataSourceType GetSchema", map[string]interface{}{logging.KeyDataSourceType: k})

resp.Diagnostics.Append(diags...)

if resp.Diagnostics.HasError() {
return
}

resp.DataSourceSchemas[k] = &schema
}
}
53 changes: 53 additions & 0 deletions internal/fwserver/server_getproviderschema_test.go
@@ -0,0 +1,53 @@
package fwserver_test

import (
"context"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/emptyprovider"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
)

// TODO: Migrate tfsdk.Provider bits of proto6server.testProviderServer to
// new internal/testing/provider.Provider that allows customization of all
// method implementations via struct fields. Then, create additional test
// cases in this unit test.
//
// For now this testing is covered by proto6server.GetProviderSchema.
//
// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/215
func TestServerGetProviderSchema(t *testing.T) {
t.Parallel()

testCases := map[string]struct {
server fwserver.Server
request *fwserver.GetProviderSchemaRequest
expectedResponse *fwserver.GetProviderSchemaResponse
}{
"empty-provider": {
server: fwserver.Server{
Provider: &emptyprovider.Provider{},
},
expectedResponse: &fwserver.GetProviderSchemaResponse{
Provider: &tfsdk.Schema{},
},
},
}

for name, testCase := range testCases {
name, testCase := name, testCase

t.Run(name, func(t *testing.T) {
t.Parallel()

response := &fwserver.GetProviderSchemaResponse{}
testCase.server.GetProviderSchema(context.Background(), testCase.request, response)

if diff := cmp.Diff(response, testCase.expectedResponse); diff != "" {
t.Errorf("unexpected difference: %s", diff)
}
})
}
}

0 comments on commit 555a882

Please sign in to comment.