Skip to content

Commit 88c77f0

Browse files
committed
Check that a ListResource type has a matching Resource type
1 parent 5f48f41 commit 88c77f0

File tree

6 files changed

+192
-54
lines changed

6 files changed

+192
-54
lines changed

internal/fwserver/server.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -862,22 +862,31 @@ func (s *Server) ListResourceFuncs(ctx context.Context) (map[string]func() list.
862862
continue
863863
}
864864

865-
logging.FrameworkTrace(ctx, "Found resource type", map[string]interface{}{logging.KeyListResourceType: typeName})
865+
logging.FrameworkTrace(ctx, "Found resource type", map[string]interface{}{logging.KeyListResourceType: typeName}) // TODO: y?
866866

867867
if _, ok := s.listResourceFuncs[typeName]; ok {
868868
s.listResourceTypesDiags.AddError(
869869
"Duplicate ListResource Type Defined",
870-
fmt.Sprintf("The %s ListResource type name was returned for multiple resources. ", typeName)+
870+
fmt.Sprintf("The %s ListResource type name was returned for multiple list resources. ", typeName)+
871871
"ListResource type names must be unique. "+
872872
"This is always an issue with the provider and should be reported to the provider developers.",
873873
)
874874
continue
875875
}
876876

877+
if _, ok := s.resourceFuncs[typeName]; !ok {
878+
s.listResourceTypesDiags.AddError(
879+
"ListResource Type Defined without a Matching Managed Resource Type",
880+
fmt.Sprintf("The %s ListResource type name was returned, but no matching managed Resource type was defined. ", typeName)+
881+
"This is always an issue with the provider and should be reported to the provider developers.",
882+
)
883+
continue
884+
}
885+
877886
s.listResourceFuncs[typeName] = listResourceFunc
878887
}
879888

880-
return s.listResourceFuncs, s.resourceTypesDiags
889+
return s.listResourceFuncs, s.listResourceTypesDiags
881890
}
882891

883892
// ListResourceMetadatas returns a slice of ListResourceMetadata for the GetMetadata

internal/fwserver/server_getmetadata.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ func (s *Server) GetMetadata(ctx context.Context, req *GetMetadataRequest, resp
8282
resourceMetadatas, diags := s.ResourceMetadatas(ctx)
8383
resp.Diagnostics.Append(diags...)
8484

85+
// Metadata for list resources must be retrieved after metadata for managed
86+
// resources.
8587
listResourceMetadatas, diags := s.ListResourceMetadatas(ctx)
8688
resp.Diagnostics.Append(diags...)
8789

internal/fwserver/server_getmetadata_test.go

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/hashicorp/terraform-plugin-framework/function"
1717
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
1818
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testprovider"
19+
"github.com/hashicorp/terraform-plugin-framework/list"
1920
"github.com/hashicorp/terraform-plugin-framework/provider"
2021
"github.com/hashicorp/terraform-plugin-framework/resource"
2122
)
@@ -497,6 +498,147 @@ func TestServerGetMetadata(t *testing.T) {
497498
},
498499
},
499500
},
501+
"list-resources-empty-type-name": {
502+
server: &fwserver.Server{
503+
Provider: &testprovider.Provider{
504+
ListResourcesMethod: func(_ context.Context) []func() list.ListResource {
505+
return []func() list.ListResource{
506+
func() list.ListResource {
507+
return &testprovider.ListResource{
508+
MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) {
509+
resp.TypeName = ""
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+
diag.NewErrorDiagnostic(
523+
"ListResource Type Name Missing",
524+
"The *testprovider.ListResource ListResource returned an empty string from the Metadata method. "+
525+
"This is always an issue with the provider and should be reported to the provider developers.",
526+
),
527+
},
528+
Functions: []fwserver.FunctionMetadata{},
529+
ListResources: []fwserver.ListResourceMetadata{},
530+
Resources: []fwserver.ResourceMetadata{},
531+
ServerCapabilities: &fwserver.ServerCapabilities{
532+
GetProviderSchemaOptional: true,
533+
MoveResourceState: true,
534+
PlanDestroy: true,
535+
},
536+
},
537+
},
538+
"list-resources-duplicate-type-name": {
539+
server: &fwserver.Server{
540+
Provider: &testprovider.Provider{
541+
ListResourcesMethod: func(_ context.Context) []func() list.ListResource {
542+
return []func() list.ListResource{
543+
func() list.ListResource {
544+
return &testprovider.ListResource{
545+
MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) {
546+
resp.TypeName = "test_resource"
547+
},
548+
}
549+
},
550+
func() list.ListResource {
551+
return &testprovider.ListResource{
552+
MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) {
553+
resp.TypeName = "test_resource"
554+
},
555+
}
556+
},
557+
}
558+
},
559+
ResourcesMethod: func(_ context.Context) []func() resource.Resource {
560+
return []func() resource.Resource{
561+
func() resource.Resource {
562+
return &testprovider.Resource{
563+
MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) {
564+
resp.TypeName = "test_resource"
565+
},
566+
}
567+
},
568+
}
569+
},
570+
},
571+
},
572+
request: &fwserver.GetMetadataRequest{},
573+
expectedResponse: &fwserver.GetMetadataResponse{
574+
DataSources: []fwserver.DataSourceMetadata{},
575+
EphemeralResources: []fwserver.EphemeralResourceMetadata{},
576+
Diagnostics: diag.Diagnostics{
577+
diag.NewErrorDiagnostic(
578+
"Duplicate ListResource Type Defined",
579+
"The test_resource ListResource type name was returned for multiple list resources. "+
580+
"ListResource type names must be unique. "+
581+
"This is always an issue with the provider and should be reported to the provider developers.",
582+
),
583+
},
584+
Functions: []fwserver.FunctionMetadata{},
585+
ListResources: []fwserver.ListResourceMetadata{},
586+
Resources: []fwserver.ResourceMetadata{},
587+
ServerCapabilities: &fwserver.ServerCapabilities{
588+
GetProviderSchemaOptional: true,
589+
MoveResourceState: true,
590+
PlanDestroy: true,
591+
},
592+
},
593+
},
594+
"list-resources-no-matching-managed-resource-type": {
595+
server: &fwserver.Server{
596+
Provider: &testprovider.Provider{
597+
ListResourcesMethod: func(_ context.Context) []func() list.ListResource {
598+
return []func() list.ListResource{
599+
func() list.ListResource {
600+
return &testprovider.ListResource{
601+
MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) {
602+
resp.TypeName = "test_resource_1"
603+
},
604+
}
605+
},
606+
}
607+
},
608+
ResourcesMethod: func(_ context.Context) []func() resource.Resource {
609+
return []func() resource.Resource{
610+
func() resource.Resource {
611+
return &testprovider.Resource{
612+
MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) {
613+
resp.TypeName = "test_resource_2"
614+
},
615+
}
616+
},
617+
}
618+
},
619+
},
620+
},
621+
request: &fwserver.GetMetadataRequest{},
622+
expectedResponse: &fwserver.GetMetadataResponse{
623+
DataSources: []fwserver.DataSourceMetadata{},
624+
EphemeralResources: []fwserver.EphemeralResourceMetadata{},
625+
Diagnostics: diag.Diagnostics{
626+
diag.NewErrorDiagnostic(
627+
"ListResource Type Defined without a Matching Managed Resource Type",
628+
"The test_resource_1 ListResource type name was returned, but no matching managed Resource type was defined. "+
629+
"This is always an issue with the provider and should be reported to the provider developers.",
630+
),
631+
},
632+
Functions: []fwserver.FunctionMetadata{},
633+
ListResources: []fwserver.ListResourceMetadata{},
634+
Resources: []fwserver.ResourceMetadata{},
635+
ServerCapabilities: &fwserver.ServerCapabilities{
636+
GetProviderSchemaOptional: true,
637+
MoveResourceState: true,
638+
PlanDestroy: true,
639+
},
640+
},
641+
},
500642
"resources": {
501643
server: &fwserver.Server{
502644
Provider: &testprovider.Provider{
@@ -682,6 +824,10 @@ func TestServerGetMetadata(t *testing.T) {
682824
return response.Functions[i].Name < response.Functions[j].Name
683825
})
684826

827+
sort.Slice(response.ListResources, func(i int, j int) bool {
828+
return response.ListResources[i].TypeName < response.ListResources[j].TypeName
829+
})
830+
685831
sort.Slice(response.Resources, func(i int, j int) bool {
686832
return response.Resources[i].TypeName < response.Resources[j].TypeName
687833
})

internal/testing/testprovider/provider.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/hashicorp/terraform-plugin-framework/datasource"
1010
"github.com/hashicorp/terraform-plugin-framework/ephemeral"
11+
"github.com/hashicorp/terraform-plugin-framework/list"
1112
"github.com/hashicorp/terraform-plugin-framework/provider"
1213
"github.com/hashicorp/terraform-plugin-framework/resource"
1314
)
@@ -21,8 +22,9 @@ type Provider struct {
2122
ConfigureMethod func(context.Context, provider.ConfigureRequest, *provider.ConfigureResponse)
2223
SchemaMethod func(context.Context, provider.SchemaRequest, *provider.SchemaResponse)
2324
DataSourcesMethod func(context.Context) []func() datasource.DataSource
24-
ResourcesMethod func(context.Context) []func() resource.Resource
2525
EphemeralResourcesMethod func(context.Context) []func() ephemeral.EphemeralResource
26+
ListResourcesMethod func(context.Context) []func() list.ListResource
27+
ResourcesMethod func(context.Context) []func() resource.Resource
2628
}
2729

2830
// Configure satisfies the provider.Provider interface.
@@ -61,6 +63,15 @@ func (p *Provider) Schema(ctx context.Context, req provider.SchemaRequest, resp
6163
p.SchemaMethod(ctx, req, resp)
6264
}
6365

66+
// ListResources satisfies the provider.Provider interface.
67+
func (p *Provider) ListResources(ctx context.Context) []func() list.ListResource {
68+
if p == nil || p.ListResourcesMethod == nil {
69+
return nil
70+
}
71+
72+
return p.ListResourcesMethod(ctx)
73+
}
74+
6475
// Resources satisfies the provider.Provider interface.
6576
func (p *Provider) Resources(ctx context.Context) []func() resource.Resource {
6677
if p == nil || p.ResourcesMethod == nil {

internal/testing/testprovider/resource.go

Lines changed: 18 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,72 +6,42 @@ package testprovider
66
import (
77
"context"
88

9+
"github.com/hashicorp/terraform-plugin-framework/list"
910
"github.com/hashicorp/terraform-plugin-framework/resource"
1011
)
1112

12-
var _ resource.Resource = &Resource{}
13+
var _ list.ListResource = &ListResource{}
1314

14-
// Declarative resource.Resource for unit testing.
15-
type Resource struct {
16-
// Resource interface methods
17-
MetadataMethod func(context.Context, resource.MetadataRequest, *resource.MetadataResponse)
18-
SchemaMethod func(context.Context, resource.SchemaRequest, *resource.SchemaResponse)
19-
CreateMethod func(context.Context, resource.CreateRequest, *resource.CreateResponse)
20-
DeleteMethod func(context.Context, resource.DeleteRequest, *resource.DeleteResponse)
21-
ReadMethod func(context.Context, resource.ReadRequest, *resource.ReadResponse)
22-
UpdateMethod func(context.Context, resource.UpdateRequest, *resource.UpdateResponse)
15+
// Declarative list.ListResource for unit testing.
16+
type ListResource struct {
17+
// ListResource interface methods
18+
MetadataMethod func(context.Context, resource.MetadataRequest, *resource.MetadataResponse)
19+
ListResourceConfigSchemaMethod func(context.Context, resource.SchemaRequest, *resource.SchemaResponse)
20+
ListResourceMethod func(context.Context, list.ListResourceRequest, *list.ListResourceResponse)
2321
}
2422

25-
// Metadata satisfies the resource.Resource interface.
26-
func (r *Resource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
23+
// Metadata satisfies the list.ListResource interface.
24+
func (r *ListResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
2725
if r.MetadataMethod == nil {
2826
return
2927
}
3028

3129
r.MetadataMethod(ctx, req, resp)
3230
}
3331

34-
// Schema satisfies the resource.Resource interface.
35-
func (r *Resource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
36-
if r.SchemaMethod == nil {
32+
// ListResourceConfigSchema satisfies the list.ListResource interface.
33+
func (r *ListResource) ListResourceConfigSchema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
34+
if r.ListResourceConfigSchemaMethod == nil {
3735
return
3836
}
3937

40-
r.SchemaMethod(ctx, req, resp)
38+
r.ListResourceConfigSchemaMethod(ctx, req, resp)
4139
}
4240

43-
// Create satisfies the resource.Resource interface.
44-
func (r *Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
45-
if r.CreateMethod == nil {
41+
// ListResource satisfies the list.ListResource interface.
42+
func (r *ListResource) ListResource(ctx context.Context, req list.ListResourceRequest, resp *list.ListResourceResponse) {
43+
if r.ListResourceMethod == nil {
4644
return
4745
}
48-
49-
r.CreateMethod(ctx, req, resp)
50-
}
51-
52-
// Delete satisfies the resource.Resource interface.
53-
func (r *Resource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
54-
if r.DeleteMethod == nil {
55-
return
56-
}
57-
58-
r.DeleteMethod(ctx, req, resp)
59-
}
60-
61-
// Read satisfies the resource.Resource interface.
62-
func (r *Resource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
63-
if r.ReadMethod == nil {
64-
return
65-
}
66-
67-
r.ReadMethod(ctx, req, resp)
68-
}
69-
70-
// Update satisfies the resource.Resource interface.
71-
func (r *Resource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
72-
if r.UpdateMethod == nil {
73-
return
74-
}
75-
76-
r.UpdateMethod(ctx, req, resp)
46+
r.ListResourceMethod(ctx, req, resp)
7747
}

list/list_resource.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ type ListResource interface {
2626
Metadata(context.Context, resource.MetadataRequest, *resource.MetadataResponse)
2727

2828
// ListConfigSchema should return the schema for list blocks.
29-
ListResourceConfigSchema(context.Context, resource.SchemaRequest, resource.SchemaResponse)
29+
ListResourceConfigSchema(context.Context, resource.SchemaRequest, *resource.SchemaResponse) // TODO: list.Schema{Request,Response}
3030

3131
// ListResource is called when the provider must list instances of a
3232
// managed resource type that satisfy a user-provided request.
33-
ListResource(context.Context, ListResourceRequest, ListResourceResponse)
33+
ListResource(context.Context, ListResourceRequest, *ListResourceResponse)
3434
}
3535

3636
// ListResourceWithConfigure is an interface type that extends ListResource to include a method

0 commit comments

Comments
 (0)