Skip to content
Closed
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
100 changes: 100 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 @@ -172,6 +173,20 @@ type Server struct {
// Provider.Resources() method.
resourceFuncs map[string]func() resource.Resource

// listResourceFuncs is the cached Resource List functions for RPCs that need to
// access resource lists. If not found, it will be fetched from the
// Provider.ListResources() method.
listResourceFuncs map[string]func() list.ListResource

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

// listResourceTypesMutex is a mutex to protect concurrent listResourceTypes
// access from race conditions.
listResourceTypesMutex sync.Mutex

// resourceTypesDiags is the cached Diagnostics obtained while populating
// resourceTypes. This is to ensure any warnings or errors are also
// returned appropriately when fetching resourceTypes.
Expand Down Expand Up @@ -804,3 +819,88 @@ func (s *Server) ResourceIdentitySchemas(ctx context.Context) (map[string]fwsche

return resourceIdentitySchemas, diags
}

// ListResourceFuncs returns a map of ListResource functions. The results are
// cached on first use.
func (s *Server) ListResourceFuncs(ctx context.Context) (map[string]func() list.ListResource, diag.Diagnostics) {
provider, ok := s.Provider.(provider.ProviderWithListResources)
if !ok {
return nil, nil
}

logging.FrameworkTrace(ctx, "Checking ListResourceTypes lock")
s.listResourceTypesMutex.Lock()
defer s.listResourceTypesMutex.Unlock()

if s.listResourceFuncs != nil {
return s.listResourceFuncs, s.resourceTypesDiags
}

providerTypeName := s.ProviderTypeName(ctx)
s.listResourceFuncs = make(map[string]func() list.ListResource)

logging.FrameworkTrace(ctx, "Calling provider defined ListResources")
listResourceFuncSlice := provider.ListResources(ctx)
logging.FrameworkTrace(ctx, "Called provider defined ListResources")

for _, listResourceFunc := range listResourceFuncSlice {
listResource := listResourceFunc()

metadataReq := resource.MetadataRequest{
ProviderTypeName: providerTypeName,
}
metadataResp := resource.MetadataResponse{}
listResource.Metadata(ctx, metadataReq, &metadataResp)

typeName := metadataResp.TypeName
if typeName == "" {
s.listResourceTypesDiags.AddError(
"ListResource Type Name Missing",
fmt.Sprintf("The %T ListResource returned an empty string from the Metadata method. ", listResource)+
"This is always an issue with the provider and should be reported to the provider developers.",
)
continue
}

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

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

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

s.listResourceFuncs[typeName] = listResourceFunc
}

return s.listResourceFuncs, s.listResourceTypesDiags
}

// ListResourceMetadatas returns a slice of ListResourceMetadata for the GetMetadata
// RPC.
func (s *Server) ListResourceMetadatas(ctx context.Context) ([]ListResourceMetadata, diag.Diagnostics) {
resourceFuncs, diags := s.ListResourceFuncs(ctx)

resourceMetadatas := make([]ListResourceMetadata, 0, len(resourceFuncs))

for typeName := range resourceFuncs {
resourceMetadatas = append(resourceMetadatas, ListResourceMetadata{
TypeName: typeName,
})
}

return resourceMetadatas, diags
}
18 changes: 15 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,38 @@ 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.
listResourceMetadatas, diags := s.ListResourceMetadatas(ctx)
resp.Diagnostics.Append(diags...)

if resp.Diagnostics.HasError() {
Expand All @@ -83,5 +94,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
}
Loading
Loading