Skip to content

Commit 662e70f

Browse files
committed
fwserver: add list resources to GetMetadata
1 parent d7f3133 commit 662e70f

File tree

7 files changed

+419
-25
lines changed

7 files changed

+419
-25
lines changed

internal/fwserver/server.go

Lines changed: 100 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
)
@@ -172,6 +173,20 @@ type Server struct {
172173
// Provider.Resources() method.
173174
resourceFuncs map[string]func() resource.Resource
174175

176+
// listResourceFuncs is the cached Resource List functions for RPCs that need to
177+
// access resource lists. If not found, it will be fetched from the
178+
// Provider.ListResources() method.
179+
listResourceFuncs map[string]func() list.ListResource
180+
181+
// listResourceTypesDiags is the cached Diagnostics obtained while populating
182+
// listResourceTypes. This is to ensure any warnings or errors are also
183+
// returned appropriately when fetching listResourceTypes.
184+
listResourceTypesDiags diag.Diagnostics
185+
186+
// listResourceTypesMutex is a mutex to protect concurrent listResourceTypes
187+
// access from race conditions.
188+
listResourceTypesMutex sync.Mutex
189+
175190
// resourceTypesDiags is the cached Diagnostics obtained while populating
176191
// resourceTypes. This is to ensure any warnings or errors are also
177192
// returned appropriately when fetching resourceTypes.
@@ -804,3 +819,88 @@ func (s *Server) ResourceIdentitySchemas(ctx context.Context) (map[string]fwsche
804819

805820
return resourceIdentitySchemas, diags
806821
}
822+
823+
// ListResourceFuncs returns a map of ListResource functions. The results are
824+
// cached on first use.
825+
func (s *Server) ListResourceFuncs(ctx context.Context) (map[string]func() list.ListResource, diag.Diagnostics) {
826+
provider, ok := s.Provider.(provider.ProviderWithListResources)
827+
if !ok {
828+
return nil, nil
829+
}
830+
831+
logging.FrameworkTrace(ctx, "Checking ListResourceTypes lock")
832+
s.listResourceTypesMutex.Lock()
833+
defer s.listResourceTypesMutex.Unlock()
834+
835+
if s.listResourceFuncs != nil {
836+
return s.listResourceFuncs, s.resourceTypesDiags
837+
}
838+
839+
providerTypeName := s.ProviderTypeName(ctx)
840+
s.listResourceFuncs = make(map[string]func() list.ListResource)
841+
842+
logging.FrameworkTrace(ctx, "Calling provider defined ListResources")
843+
listResourceFuncSlice := provider.ListResources(ctx)
844+
logging.FrameworkTrace(ctx, "Called provider defined ListResources")
845+
846+
for _, listResourceFunc := range listResourceFuncSlice {
847+
listResource := listResourceFunc()
848+
849+
metadataReq := resource.MetadataRequest{
850+
ProviderTypeName: providerTypeName,
851+
}
852+
metadataResp := resource.MetadataResponse{}
853+
listResource.Metadata(ctx, metadataReq, &metadataResp)
854+
855+
typeName := metadataResp.TypeName
856+
if typeName == "" {
857+
s.listResourceTypesDiags.AddError(
858+
"ListResource Type Name Missing",
859+
fmt.Sprintf("The %T ListResource returned an empty string from the Metadata method. ", listResource)+
860+
"This is always an issue with the provider and should be reported to the provider developers.",
861+
)
862+
continue
863+
}
864+
865+
logging.FrameworkTrace(ctx, "Found resource type", map[string]interface{}{logging.KeyListResourceType: typeName}) // TODO: y?
866+
867+
if _, ok := s.listResourceFuncs[typeName]; ok {
868+
s.listResourceTypesDiags.AddError(
869+
"Duplicate ListResource Type Defined",
870+
fmt.Sprintf("The %s ListResource type name was returned for multiple list resources. ", typeName)+
871+
"ListResource type names must be unique. "+
872+
"This is always an issue with the provider and should be reported to the provider developers.",
873+
)
874+
continue
875+
}
876+
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+
886+
s.listResourceFuncs[typeName] = listResourceFunc
887+
}
888+
889+
return s.listResourceFuncs, s.listResourceTypesDiags
890+
}
891+
892+
// ListResourceMetadatas returns a slice of ListResourceMetadata for the GetMetadata
893+
// RPC.
894+
func (s *Server) ListResourceMetadatas(ctx context.Context) ([]ListResourceMetadata, diag.Diagnostics) {
895+
resourceFuncs, diags := s.ListResourceFuncs(ctx)
896+
897+
resourceMetadatas := make([]ListResourceMetadata, 0, len(resourceFuncs))
898+
899+
for typeName := range resourceFuncs {
900+
resourceMetadatas = append(resourceMetadatas, ListResourceMetadata{
901+
TypeName: typeName,
902+
})
903+
}
904+
905+
return resourceMetadatas, diags
906+
}

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
}

0 commit comments

Comments
 (0)