Skip to content

internal: Migrate ValidateDataSourceConfig RPC to fromproto6, fwserver, and toproto6 #324

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 12, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions internal/fromproto6/validatedatasourceconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package fromproto6

import (
"context"

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

// ValidateDataSourceConfigRequest returns the *fwserver.ValidateDataSourceConfigRequest
// equivalent of a *tfprotov6.ValidateDataSourceConfigRequest.
func ValidateDataSourceConfigRequest(ctx context.Context, proto6 *tfprotov6.ValidateDataResourceConfigRequest, dataSourceType tfsdk.DataSourceType, dataSourceSchema *tfsdk.Schema) (*fwserver.ValidateDataSourceConfigRequest, diag.Diagnostics) {
if proto6 == nil {
return nil, nil
}

fw := &fwserver.ValidateDataSourceConfigRequest{}

config, diags := Config(ctx, proto6.Config, dataSourceSchema)

fw.Config = config
fw.DataSourceType = dataSourceType

return fw, diags
}
106 changes: 106 additions & 0 deletions internal/fromproto6/validatedatasourceconfig_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package fromproto6_test

import (
"context"
"testing"

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

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

testProto6Type := tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"test_attribute": tftypes.String,
},
}

testProto6Value := tftypes.NewValue(testProto6Type, map[string]tftypes.Value{
"test_attribute": tftypes.NewValue(tftypes.String, "test-value"),
})

testProto6DynamicValue, err := tfprotov6.NewDynamicValue(testProto6Type, testProto6Value)

if err != nil {
t.Fatalf("unexpected error calling tfprotov6.NewDynamicValue(): %s", err)
}

testFwSchema := &tfsdk.Schema{
Attributes: map[string]tfsdk.Attribute{
"test_attribute": {
Required: true,
Type: types.StringType,
},
},
}

testCases := map[string]struct {
input *tfprotov6.ValidateDataResourceConfigRequest
dataSourceSchema *tfsdk.Schema
dataSourceType tfsdk.DataSourceType
expected *fwserver.ValidateDataSourceConfigRequest
expectedDiagnostics diag.Diagnostics
}{
"nil": {
input: nil,
expected: nil,
},
"empty": {
input: &tfprotov6.ValidateDataResourceConfigRequest{},
expected: &fwserver.ValidateDataSourceConfigRequest{},
},
"config-missing-schema": {
input: &tfprotov6.ValidateDataResourceConfigRequest{
Config: &testProto6DynamicValue,
},
expected: &fwserver.ValidateDataSourceConfigRequest{},
expectedDiagnostics: diag.Diagnostics{
diag.NewErrorDiagnostic(
"Unable to Convert Configuration",
"An unexpected error was encountered when converting the configuration from the protocol type. "+
"This is always an issue in the Terraform Provider SDK used to implement the provider and should be reported to the provider developers.\n\n"+
"Please report this to the provider developer:\n\n"+
"Missing schema.",
),
},
},
"config": {
input: &tfprotov6.ValidateDataResourceConfigRequest{
Config: &testProto6DynamicValue,
},
dataSourceSchema: testFwSchema,
expected: &fwserver.ValidateDataSourceConfigRequest{
Config: &tfsdk.Config{
Raw: testProto6Value,
Schema: *testFwSchema,
},
},
},
}

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

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

got, diags := fromproto6.ValidateDataSourceConfigRequest(context.Background(), testCase.input, testCase.dataSourceType, testCase.dataSourceSchema)

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

if diff := cmp.Diff(diags, testCase.expectedDiagnostics); diff != "" {
t.Errorf("unexpected diagnostics difference: %s", diff)
}
})
}
}
124 changes: 124 additions & 0 deletions internal/fwserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package fwserver

import (
"context"
"fmt"
"sync"

"github.com/hashicorp/terraform-plugin-framework/diag"
Expand All @@ -15,6 +16,34 @@ import (
type Server struct {
Provider tfsdk.Provider

// dataSourceSchemas is the cached DataSource Schemas for RPCs that need to
// convert configuration data from the protocol. If not found, it will be
// fetched from the DataSourceType.GetSchema() method.
dataSourceSchemas map[string]*tfsdk.Schema

// dataSourceSchemasDiags is the cached Diagnostics obtained while populating
// dataSourceSchemas. This is to ensure any warnings or errors are also
// returned appropriately when fetching dataSourceSchemas.
dataSourceSchemasDiags diag.Diagnostics

// dataSourceSchemasMutex is a mutex to protect concurrent dataSourceSchemas
// access from race conditions.
dataSourceSchemasMutex sync.Mutex

// dataSourceTypes is the cached DataSourceTypes for RPCs that need to
// access data sources. If not found, it will be fetched from the
// Provider.GetDataSources() method.
dataSourceTypes map[string]tfsdk.DataSourceType

// dataSourceTypesDiags is the cached Diagnostics obtained while populating
// dataSourceTypes. This is to ensure any warnings or errors are also
// returned appropriately when fetching dataSourceTypes.
dataSourceTypesDiags diag.Diagnostics

// dataSourceTypesMutex is a mutex to protect concurrent dataSourceTypes
// access from race conditions.
dataSourceTypesMutex sync.Mutex

// providerSchema is the cached Provider Schema for RPCs that need to
// convert configuration data from the protocol. If not found, it will be
// fetched from the Provider.GetSchema() method.
Expand All @@ -30,6 +59,101 @@ type Server struct {
providerSchemaMutex sync.Mutex
}

// DataSourceSchema returns the Schema associated with the DataSourceType for
// the given type name.
func (s *Server) DataSourceSchema(ctx context.Context, typeName string) (*tfsdk.Schema, diag.Diagnostics) {
dataSourceSchemas, diags := s.DataSourceSchemas(ctx)

dataSourceSchema, ok := dataSourceSchemas[typeName]

if !ok {
diags.AddError(
"Data Source Schema Not Found",
fmt.Sprintf("No data source type named %q was found in the provider to fetch the schema. ", typeName)+
"This is always an issue in the Terraform Provider SDK used to implement the provider and should be reported to the provider developers.",
)

return nil, diags
}

return dataSourceSchema, diags
}

// DataSourceSchemas returns the map of DataSourceType Schemas. The results are
// cached on first use.
func (s *Server) DataSourceSchemas(ctx context.Context) (map[string]*tfsdk.Schema, diag.Diagnostics) {
logging.FrameworkTrace(ctx, "Checking DataSourceSchemas lock")
s.dataSourceSchemasMutex.Lock()
defer s.dataSourceSchemasMutex.Unlock()

if s.dataSourceSchemas != nil {
return s.dataSourceSchemas, s.dataSourceSchemasDiags
}

dataSourceTypes, diags := s.DataSourceTypes(ctx)

s.dataSourceSchemas = map[string]*tfsdk.Schema{}
s.dataSourceSchemasDiags = diags

if s.dataSourceSchemasDiags.HasError() {
return s.dataSourceSchemas, s.dataSourceSchemasDiags
}

for dataSourceTypeName, dataSourceType := range dataSourceTypes {
logging.FrameworkTrace(ctx, "Found data source type", map[string]interface{}{logging.KeyDataSourceType: dataSourceTypeName})

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

s.dataSourceSchemasDiags.Append(diags...)

if s.dataSourceSchemasDiags.HasError() {
return s.dataSourceSchemas, s.dataSourceSchemasDiags
}

s.dataSourceSchemas[dataSourceTypeName] = &schema
}

return s.dataSourceSchemas, s.dataSourceSchemasDiags
}

// DataSourceType returns the DataSourceType for a given type name.
func (s *Server) DataSourceType(ctx context.Context, typeName string) (tfsdk.DataSourceType, diag.Diagnostics) {
dataSourceTypes, diags := s.DataSourceTypes(ctx)

dataSourceType, ok := dataSourceTypes[typeName]

if !ok {
diags.AddError(
"Data Source Type Not Found",
fmt.Sprintf("No data source type named %q was found in the provider.", typeName),
)

return nil, diags
}

return dataSourceType, diags
}

// DataSourceTypes returns the map of DataSourceTypes. The results are cached
// on first use.
func (s *Server) DataSourceTypes(ctx context.Context) (map[string]tfsdk.DataSourceType, diag.Diagnostics) {
logging.FrameworkTrace(ctx, "Checking DataSourceTypes lock")
s.dataSourceTypesMutex.Lock()
defer s.dataSourceTypesMutex.Unlock()

if s.dataSourceTypes != nil {
return s.dataSourceTypes, s.dataSourceTypesDiags
}

logging.FrameworkDebug(ctx, "Calling provider defined Provider GetDataSources")
s.dataSourceTypes, s.dataSourceTypesDiags = s.Provider.GetDataSources(ctx)
logging.FrameworkDebug(ctx, "Called provider defined Provider GetDataSources")

return s.dataSourceTypes, s.dataSourceTypesDiags
}

// ProviderSchema returns the Schema associated with the Provider. The Schema
// and Diagnostics are cached on first use.
func (s *Server) ProviderSchema(ctx context.Context) (*tfsdk.Schema, diag.Diagnostics) {
Expand Down
27 changes: 2 additions & 25 deletions internal/fwserver/server_getproviderschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,36 +83,13 @@ func (s *Server) GetProviderSchema(ctx context.Context, req *GetProviderSchemaRe
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")
dataSourceSchemas, diags := s.DataSourceSchemas(ctx)

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
}
resp.DataSourceSchemas = dataSourceSchemas
}
3 changes: 2 additions & 1 deletion internal/fwserver/server_getproviderschema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ func TestServerGetProviderSchema(t *testing.T) {
Provider: &emptyprovider.Provider{},
},
expectedResponse: &fwserver.GetProviderSchemaResponse{
Provider: &tfsdk.Schema{},
DataSourceSchemas: map[string]*tfsdk.Schema{},
Provider: &tfsdk.Schema{},
},
},
}
Expand Down
Loading