Skip to content

Commit 7a0dfb3

Browse files
committed
fwserver: add list resources to GetMetadata
1 parent 6997cf5 commit 7a0dfb3

File tree

8 files changed

+397
-5
lines changed

8 files changed

+397
-5
lines changed

internal/fwserver/server.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/hashicorp/terraform-plugin-framework/function"
1515
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
1616
"github.com/hashicorp/terraform-plugin-framework/internal/logging"
17+
"github.com/hashicorp/terraform-plugin-framework/list"
1718
"github.com/hashicorp/terraform-plugin-framework/provider"
1819
"github.com/hashicorp/terraform-plugin-framework/resource"
1920
)
@@ -113,6 +114,17 @@ type Server struct {
113114
// access from race conditions.
114115
functionFuncsMutex sync.Mutex
115116

117+
// listResourceFuncs is a map of known ListResource factory functions.
118+
listResourceFuncs map[string]func() list.ListResource
119+
120+
// listResourceFuncsDiags are the cached Diagnostics obtained while
121+
// populating listResourceFuncs.
122+
listResourceFuncsDiags diag.Diagnostics
123+
124+
// listResourceFuncsMutex is a mutex to protect concurrent listResourceFuncs
125+
// access from race conditions.
126+
listResourceFuncsMutex sync.Mutex
127+
116128
// providerSchema is the cached Provider Schema for RPCs that need to
117129
// convert configuration data from the protocol. If not found, it will be
118130
// fetched from the Provider.GetSchema() method.

internal/fwserver/server_getmetadata.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type GetMetadataResponse struct {
2020
Diagnostics diag.Diagnostics
2121
EphemeralResources []EphemeralResourceMetadata
2222
Functions []FunctionMetadata
23+
ListResources []ListResourceMetadata
2324
Resources []ResourceMetadata
2425
ServerCapabilities *ServerCapabilities
2526
}
@@ -52,28 +53,39 @@ type ResourceMetadata struct {
5253
TypeName string
5354
}
5455

56+
// ListResourceMetadata is the framework server equivalent of the
57+
// tfprotov5.ListResourceMetadata and tfprotov6.ListResourceMetadata types.
58+
type ListResourceMetadata struct {
59+
// TypeName is the name of the list resource.
60+
TypeName string
61+
}
62+
5563
// GetMetadata implements the framework server GetMetadata RPC.
5664
func (s *Server) GetMetadata(ctx context.Context, req *GetMetadataRequest, resp *GetMetadataResponse) {
5765
resp.DataSources = []DataSourceMetadata{}
5866
resp.EphemeralResources = []EphemeralResourceMetadata{}
5967
resp.Functions = []FunctionMetadata{}
68+
resp.ListResources = []ListResourceMetadata{}
6069
resp.Resources = []ResourceMetadata{}
70+
6171
resp.ServerCapabilities = s.ServerCapabilities()
6272

6373
datasourceMetadatas, diags := s.DataSourceMetadatas(ctx)
64-
6574
resp.Diagnostics.Append(diags...)
6675

6776
ephemeralResourceMetadatas, diags := s.EphemeralResourceMetadatas(ctx)
68-
6977
resp.Diagnostics.Append(diags...)
7078

7179
functionMetadatas, diags := s.FunctionMetadatas(ctx)
72-
7380
resp.Diagnostics.Append(diags...)
7481

7582
resourceMetadatas, diags := s.ResourceMetadatas(ctx)
83+
resp.Diagnostics.Append(diags...)
7684

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

7991
if resp.Diagnostics.HasError() {
@@ -83,5 +95,6 @@ func (s *Server) GetMetadata(ctx context.Context, req *GetMetadataRequest, resp
8395
resp.DataSources = datasourceMetadatas
8496
resp.EphemeralResources = ephemeralResourceMetadatas
8597
resp.Functions = functionMetadatas
98+
resp.ListResources = listResourceMetadatas
8699
resp.Resources = resourceMetadatas
87100
}

internal/fwserver/server_getmetadata_test.go

Lines changed: 195 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ import (
99
"testing"
1010

1111
"github.com/google/go-cmp/cmp"
12+
"github.com/google/go-cmp/cmp/cmpopts"
1213

1314
"github.com/hashicorp/terraform-plugin-framework/datasource"
1415
"github.com/hashicorp/terraform-plugin-framework/diag"
1516
"github.com/hashicorp/terraform-plugin-framework/ephemeral"
1617
"github.com/hashicorp/terraform-plugin-framework/function"
1718
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
1819
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testprovider"
20+
"github.com/hashicorp/terraform-plugin-framework/list"
1921
"github.com/hashicorp/terraform-plugin-framework/provider"
2022
"github.com/hashicorp/terraform-plugin-framework/resource"
2123
)
@@ -485,6 +487,190 @@ func TestServerGetMetadata(t *testing.T) {
485487
},
486488
},
487489
},
490+
"listresources": {
491+
server: &fwserver.Server{
492+
Provider: &testprovider.Provider{
493+
ListResourcesMethod: func(_ context.Context) []func() list.ListResource {
494+
return []func() list.ListResource{
495+
func() list.ListResource {
496+
return &testprovider.ListResource{
497+
MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) {
498+
resp.TypeName = "test_resource_1"
499+
},
500+
}
501+
},
502+
}
503+
},
504+
ResourcesMethod: func(_ context.Context) []func() resource.Resource {
505+
return []func() resource.Resource{
506+
func() resource.Resource {
507+
return &testprovider.Resource{
508+
MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) {
509+
resp.TypeName = "test_resource_1"
510+
},
511+
}
512+
},
513+
}
514+
},
515+
},
516+
},
517+
request: &fwserver.GetMetadataRequest{},
518+
expectedResponse: &fwserver.GetMetadataResponse{
519+
DataSources: []fwserver.DataSourceMetadata{},
520+
EphemeralResources: []fwserver.EphemeralResourceMetadata{},
521+
Diagnostics: diag.Diagnostics{},
522+
Functions: []fwserver.FunctionMetadata{},
523+
ListResources: []fwserver.ListResourceMetadata{
524+
{TypeName: "test_resource_1"},
525+
},
526+
Resources: []fwserver.ResourceMetadata{
527+
{TypeName: "test_resource_1"},
528+
},
529+
ServerCapabilities: &fwserver.ServerCapabilities{
530+
GetProviderSchemaOptional: true,
531+
MoveResourceState: true,
532+
PlanDestroy: true,
533+
},
534+
},
535+
},
536+
"list-resources-empty-type-name": {
537+
server: &fwserver.Server{
538+
Provider: &testprovider.Provider{
539+
ListResourcesMethod: func(_ context.Context) []func() list.ListResource {
540+
return []func() list.ListResource{
541+
func() list.ListResource {
542+
return &testprovider.ListResource{
543+
MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) {
544+
resp.TypeName = ""
545+
},
546+
}
547+
},
548+
}
549+
},
550+
},
551+
},
552+
request: &fwserver.GetMetadataRequest{},
553+
expectedResponse: &fwserver.GetMetadataResponse{
554+
DataSources: []fwserver.DataSourceMetadata{},
555+
EphemeralResources: []fwserver.EphemeralResourceMetadata{},
556+
Diagnostics: diag.Diagnostics{
557+
diag.NewErrorDiagnostic(
558+
"ListResource Type Name Missing",
559+
"The *testprovider.ListResource ListResource returned an empty string from the Metadata method. "+
560+
"This is always an issue with the provider and should be reported to the provider developers.",
561+
),
562+
},
563+
Functions: []fwserver.FunctionMetadata{},
564+
Resources: []fwserver.ResourceMetadata{},
565+
ServerCapabilities: &fwserver.ServerCapabilities{
566+
GetProviderSchemaOptional: true,
567+
MoveResourceState: true,
568+
PlanDestroy: true,
569+
},
570+
},
571+
},
572+
"list-resources-duplicate-type-name": {
573+
server: &fwserver.Server{
574+
Provider: &testprovider.Provider{
575+
ListResourcesMethod: func(_ context.Context) []func() list.ListResource {
576+
return []func() list.ListResource{
577+
func() list.ListResource {
578+
return &testprovider.ListResource{
579+
MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) {
580+
resp.TypeName = "test_resource"
581+
},
582+
}
583+
},
584+
func() list.ListResource {
585+
return &testprovider.ListResource{
586+
MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) {
587+
resp.TypeName = "test_resource"
588+
},
589+
}
590+
},
591+
}
592+
},
593+
ResourcesMethod: func(_ context.Context) []func() resource.Resource {
594+
return []func() resource.Resource{
595+
func() resource.Resource {
596+
return &testprovider.Resource{
597+
MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) {
598+
resp.TypeName = "test_resource"
599+
},
600+
}
601+
},
602+
}
603+
},
604+
},
605+
},
606+
request: &fwserver.GetMetadataRequest{},
607+
expectedResponse: &fwserver.GetMetadataResponse{
608+
DataSources: []fwserver.DataSourceMetadata{},
609+
EphemeralResources: []fwserver.EphemeralResourceMetadata{},
610+
Diagnostics: diag.Diagnostics{
611+
diag.NewErrorDiagnostic(
612+
"Duplicate ListResource Type Defined",
613+
"The test_resource ListResource type name was returned for multiple list resources. "+
614+
"ListResource type names must be unique. "+
615+
"This is always an issue with the provider and should be reported to the provider developers.",
616+
),
617+
},
618+
Functions: []fwserver.FunctionMetadata{},
619+
Resources: []fwserver.ResourceMetadata{},
620+
ServerCapabilities: &fwserver.ServerCapabilities{
621+
GetProviderSchemaOptional: true,
622+
MoveResourceState: true,
623+
PlanDestroy: true,
624+
},
625+
},
626+
},
627+
"list-resources-no-matching-managed-resource-type": {
628+
server: &fwserver.Server{
629+
Provider: &testprovider.Provider{
630+
ListResourcesMethod: func(_ context.Context) []func() list.ListResource {
631+
return []func() list.ListResource{
632+
func() list.ListResource {
633+
return &testprovider.ListResource{
634+
MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) {
635+
resp.TypeName = "test_resource_1"
636+
},
637+
}
638+
},
639+
}
640+
},
641+
ResourcesMethod: func(_ context.Context) []func() resource.Resource {
642+
return []func() resource.Resource{
643+
func() resource.Resource {
644+
return &testprovider.Resource{
645+
MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) {
646+
resp.TypeName = "test_resource_2"
647+
},
648+
}
649+
},
650+
}
651+
},
652+
},
653+
},
654+
request: &fwserver.GetMetadataRequest{},
655+
expectedResponse: &fwserver.GetMetadataResponse{
656+
DataSources: []fwserver.DataSourceMetadata{},
657+
EphemeralResources: []fwserver.EphemeralResourceMetadata{},
658+
Diagnostics: diag.Diagnostics{
659+
diag.NewErrorDiagnostic(
660+
"ListResource Type Defined without a Matching Managed Resource Type",
661+
"The test_resource_1 ListResource type name was returned, but no matching managed Resource type was defined. "+
662+
"This is always an issue with the provider and should be reported to the provider developers.",
663+
),
664+
},
665+
Functions: []fwserver.FunctionMetadata{},
666+
Resources: []fwserver.ResourceMetadata{},
667+
ServerCapabilities: &fwserver.ServerCapabilities{
668+
GetProviderSchemaOptional: true,
669+
MoveResourceState: true,
670+
PlanDestroy: true,
671+
},
672+
},
673+
},
488674
"resources": {
489675
server: &fwserver.Server{
490676
Provider: &testprovider.Provider{
@@ -666,11 +852,19 @@ func TestServerGetMetadata(t *testing.T) {
666852
return response.Functions[i].Name < response.Functions[j].Name
667853
})
668854

855+
sort.Slice(response.ListResources, func(i int, j int) bool {
856+
return response.ListResources[i].TypeName < response.ListResources[j].TypeName
857+
})
858+
669859
sort.Slice(response.Resources, func(i int, j int) bool {
670860
return response.Resources[i].TypeName < response.Resources[j].TypeName
671861
})
672862

673-
if diff := cmp.Diff(response, testCase.expectedResponse); diff != "" {
863+
opts := cmp.Options{
864+
cmpopts.EquateEmpty(),
865+
}
866+
867+
if diff := cmp.Diff(response, testCase.expectedResponse, opts...); diff != "" {
674868
t.Errorf("unexpected difference: %s", diff)
675869
}
676870
})

0 commit comments

Comments
 (0)