Skip to content

fwserver: add list resources to GetProviderSchema #1152

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
12 changes: 12 additions & 0 deletions internal/fwserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/function"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/logging"
"github.com/hashicorp/terraform-plugin-framework/list"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/resource"
)
Expand Down Expand Up @@ -113,6 +114,17 @@ type Server struct {
// access from race conditions.
functionFuncsMutex sync.Mutex

// listResourceFuncs is a map of known ListResource factory functions.
listResourceFuncs map[string]func() list.ListResource

// listResourceFuncsDiags are the cached Diagnostics obtained while
// populating listResourceFuncs.
listResourceFuncsDiags diag.Diagnostics

// listResourceFuncsMutex is a mutex to protect concurrent listResourceFuncs
// access from race conditions.
listResourceFuncsMutex 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 Down
19 changes: 16 additions & 3 deletions internal/fwserver/server_getmetadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type GetMetadataResponse struct {
Diagnostics diag.Diagnostics
EphemeralResources []EphemeralResourceMetadata
Functions []FunctionMetadata
ListResources []ListResourceMetadata
Resources []ResourceMetadata
ServerCapabilities *ServerCapabilities
}
Expand Down Expand Up @@ -52,28 +53,39 @@ type ResourceMetadata struct {
TypeName string
}

// ListResourceMetadata is the framework server equivalent of the
// tfprotov5.ListResourceMetadata and tfprotov6.ListResourceMetadata types.
type ListResourceMetadata struct {
// TypeName is the name of the list resource.
TypeName string
}

// GetMetadata implements the framework server GetMetadata RPC.
func (s *Server) GetMetadata(ctx context.Context, req *GetMetadataRequest, resp *GetMetadataResponse) {
resp.DataSources = []DataSourceMetadata{}
resp.EphemeralResources = []EphemeralResourceMetadata{}
resp.Functions = []FunctionMetadata{}
resp.ListResources = []ListResourceMetadata{}
resp.Resources = []ResourceMetadata{}

resp.ServerCapabilities = s.ServerCapabilities()

datasourceMetadatas, diags := s.DataSourceMetadatas(ctx)

resp.Diagnostics.Append(diags...)

ephemeralResourceMetadatas, diags := s.EphemeralResourceMetadatas(ctx)

resp.Diagnostics.Append(diags...)

functionMetadatas, diags := s.FunctionMetadatas(ctx)

resp.Diagnostics.Append(diags...)

resourceMetadatas, diags := s.ResourceMetadatas(ctx)
resp.Diagnostics.Append(diags...)

// Metadata for list resources must be retrieved after metadata for managed
// resources. Server.ListResourceMetadatas checks that each list resource
// type nane matches a known managed Resource type name.
listResourceMetadatas, diags := s.ListResourceMetadatas(ctx)
resp.Diagnostics.Append(diags...)

if resp.Diagnostics.HasError() {
Expand All @@ -83,5 +95,6 @@ func (s *Server) GetMetadata(ctx context.Context, req *GetMetadataRequest, resp
resp.DataSources = datasourceMetadatas
resp.EphemeralResources = ephemeralResourceMetadatas
resp.Functions = functionMetadatas
resp.ListResources = listResourceMetadatas
resp.Resources = resourceMetadatas
}
196 changes: 195 additions & 1 deletion internal/fwserver/server_getmetadata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import (
"testing"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"

"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/ephemeral"
"github.com/hashicorp/terraform-plugin-framework/function"
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testprovider"
"github.com/hashicorp/terraform-plugin-framework/list"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/resource"
)
Expand Down Expand Up @@ -485,6 +487,190 @@ func TestServerGetMetadata(t *testing.T) {
},
},
},
"listresources": {
server: &fwserver.Server{
Provider: &testprovider.Provider{
ListResourcesMethod: func(_ context.Context) []func() list.ListResource {
return []func() list.ListResource{
func() list.ListResource {
return &testprovider.ListResource{
MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = "test_resource_1"
},
}
},
}
},
ResourcesMethod: func(_ context.Context) []func() resource.Resource {
return []func() resource.Resource{
func() resource.Resource {
return &testprovider.Resource{
MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = "test_resource_1"
},
}
},
}
},
},
},
request: &fwserver.GetMetadataRequest{},
expectedResponse: &fwserver.GetMetadataResponse{
DataSources: []fwserver.DataSourceMetadata{},
EphemeralResources: []fwserver.EphemeralResourceMetadata{},
Diagnostics: diag.Diagnostics{},
Functions: []fwserver.FunctionMetadata{},
ListResources: []fwserver.ListResourceMetadata{
{TypeName: "test_resource_1"},
},
Resources: []fwserver.ResourceMetadata{
{TypeName: "test_resource_1"},
},
ServerCapabilities: &fwserver.ServerCapabilities{
GetProviderSchemaOptional: true,
MoveResourceState: true,
PlanDestroy: true,
},
},
},
"list-resources-empty-type-name": {
server: &fwserver.Server{
Provider: &testprovider.Provider{
ListResourcesMethod: func(_ context.Context) []func() list.ListResource {
return []func() list.ListResource{
func() list.ListResource {
return &testprovider.ListResource{
MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = ""
},
}
},
}
},
},
},
request: &fwserver.GetMetadataRequest{},
expectedResponse: &fwserver.GetMetadataResponse{
DataSources: []fwserver.DataSourceMetadata{},
EphemeralResources: []fwserver.EphemeralResourceMetadata{},
Diagnostics: diag.Diagnostics{
diag.NewErrorDiagnostic(
"ListResource Type Name Missing",
"The *testprovider.ListResource ListResource returned an empty string from the Metadata method. "+
"This is always an issue with the provider and should be reported to the provider developers.",
),
},
Functions: []fwserver.FunctionMetadata{},
Resources: []fwserver.ResourceMetadata{},
ServerCapabilities: &fwserver.ServerCapabilities{
GetProviderSchemaOptional: true,
MoveResourceState: true,
PlanDestroy: true,
},
},
},
"list-resources-duplicate-type-name": {
server: &fwserver.Server{
Provider: &testprovider.Provider{
ListResourcesMethod: func(_ context.Context) []func() list.ListResource {
return []func() list.ListResource{
func() list.ListResource {
return &testprovider.ListResource{
MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = "test_resource"
},
}
},
func() list.ListResource {
return &testprovider.ListResource{
MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = "test_resource"
},
}
},
}
},
ResourcesMethod: func(_ context.Context) []func() resource.Resource {
return []func() resource.Resource{
func() resource.Resource {
return &testprovider.Resource{
MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = "test_resource"
},
}
},
}
},
},
},
request: &fwserver.GetMetadataRequest{},
expectedResponse: &fwserver.GetMetadataResponse{
DataSources: []fwserver.DataSourceMetadata{},
EphemeralResources: []fwserver.EphemeralResourceMetadata{},
Diagnostics: diag.Diagnostics{
diag.NewErrorDiagnostic(
"Duplicate ListResource Type Defined",
"The test_resource ListResource type name was returned for multiple list resources. "+
"ListResource type names must be unique. "+
"This is always an issue with the provider and should be reported to the provider developers.",
),
},
Functions: []fwserver.FunctionMetadata{},
Resources: []fwserver.ResourceMetadata{},
ServerCapabilities: &fwserver.ServerCapabilities{
GetProviderSchemaOptional: true,
MoveResourceState: true,
PlanDestroy: true,
},
},
},
"list-resources-no-matching-managed-resource-type": {
server: &fwserver.Server{
Provider: &testprovider.Provider{
ListResourcesMethod: func(_ context.Context) []func() list.ListResource {
return []func() list.ListResource{
func() list.ListResource {
return &testprovider.ListResource{
MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = "test_resource_1"
},
}
},
}
},
ResourcesMethod: func(_ context.Context) []func() resource.Resource {
return []func() resource.Resource{
func() resource.Resource {
return &testprovider.Resource{
MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = "test_resource_2"
},
}
},
}
},
},
},
request: &fwserver.GetMetadataRequest{},
expectedResponse: &fwserver.GetMetadataResponse{
DataSources: []fwserver.DataSourceMetadata{},
EphemeralResources: []fwserver.EphemeralResourceMetadata{},
Diagnostics: diag.Diagnostics{
diag.NewErrorDiagnostic(
"ListResource Type Defined without a Matching Managed Resource Type",
"The test_resource_1 ListResource type name was returned, but no matching managed Resource type was defined. "+
"This is always an issue with the provider and should be reported to the provider developers.",
),
},
Functions: []fwserver.FunctionMetadata{},
Resources: []fwserver.ResourceMetadata{},
ServerCapabilities: &fwserver.ServerCapabilities{
GetProviderSchemaOptional: true,
MoveResourceState: true,
PlanDestroy: true,
},
},
},
"resources": {
server: &fwserver.Server{
Provider: &testprovider.Provider{
Expand Down Expand Up @@ -666,11 +852,19 @@ func TestServerGetMetadata(t *testing.T) {
return response.Functions[i].Name < response.Functions[j].Name
})

sort.Slice(response.ListResources, func(i int, j int) bool {
return response.ListResources[i].TypeName < response.ListResources[j].TypeName
})

sort.Slice(response.Resources, func(i int, j int) bool {
return response.Resources[i].TypeName < response.Resources[j].TypeName
})

if diff := cmp.Diff(response, testCase.expectedResponse); diff != "" {
opts := cmp.Options{
cmpopts.EquateEmpty(),
}

if diff := cmp.Diff(response, testCase.expectedResponse, opts...); diff != "" {
t.Errorf("unexpected difference: %s", diff)
}
})
Expand Down
26 changes: 8 additions & 18 deletions internal/fwserver/server_getproviderschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type GetProviderSchemaResponse struct {
DataSourceSchemas map[string]fwschema.Schema
EphemeralResourceSchemas map[string]fwschema.Schema
FunctionDefinitions map[string]function.Definition
ListResourceSchemas map[string]fwschema.Schema
Diagnostics diag.Diagnostics
}

Expand All @@ -33,62 +34,51 @@ func (s *Server) GetProviderSchema(ctx context.Context, req *GetProviderSchemaRe
resp.ServerCapabilities = s.ServerCapabilities()

providerSchema, diags := s.ProviderSchema(ctx)

resp.Diagnostics.Append(diags...)

if diags.HasError() {
return
}

resp.Provider = providerSchema

providerMetaSchema, diags := s.ProviderMetaSchema(ctx)

resp.Diagnostics.Append(diags...)

if diags.HasError() {
return
}

resp.ProviderMeta = providerMetaSchema

resourceSchemas, diags := s.ResourceSchemas(ctx)

resp.Diagnostics.Append(diags...)

if resp.Diagnostics.HasError() {
return
}

resp.ResourceSchemas = resourceSchemas

dataSourceSchemas, diags := s.DataSourceSchemas(ctx)

resp.Diagnostics.Append(diags...)

if resp.Diagnostics.HasError() {
return
}

resp.DataSourceSchemas = dataSourceSchemas

functions, diags := s.FunctionDefinitions(ctx)

resp.Diagnostics.Append(diags...)

if resp.Diagnostics.HasError() {
return
}

resp.FunctionDefinitions = functions

ephemeralResourceSchemas, diags := s.EphemeralResourceSchemas(ctx)

resp.Diagnostics.Append(diags...)

if resp.Diagnostics.HasError() {
return
}

resp.EphemeralResourceSchemas = ephemeralResourceSchemas

listResourceSchemas, diags := s.ListResourceSchemas(ctx)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
resp.ListResourceSchemas = listResourceSchemas
}
Loading
Loading