Skip to content

Commit 4640688

Browse files
committed
fwserver: add ListResource method
1 parent 1da956e commit 4640688

File tree

3 files changed

+272
-0
lines changed

3 files changed

+272
-0
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package fwserver
2+
3+
import (
4+
"context"
5+
"iter"
6+
7+
"github.com/hashicorp/terraform-plugin-framework/diag"
8+
"github.com/hashicorp/terraform-plugin-framework/internal/logging"
9+
"github.com/hashicorp/terraform-plugin-framework/list"
10+
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
11+
)
12+
13+
type ListResourceRequest struct {
14+
// ListResource is ...
15+
ListResource list.ListResource
16+
17+
Config tfsdk.Config
18+
19+
IncludeResourceObject bool
20+
}
21+
22+
type ListResourceStream struct {
23+
Results iter.Seq[ListResourceEvent]
24+
Diagnostics diag.Diagnostics
25+
}
26+
27+
type ListResourceEvent struct {
28+
// Identity is the identity of the managed resource instance.
29+
// A nil value will raise a diagnostic.
30+
Identity *tfsdk.ResourceIdentity
31+
32+
// ResourceObject is the provider's representation of the attributes of the
33+
// listed managed resource instance.
34+
// If ListResourceRequest.IncludeResourceObject is true, a nil value will raise
35+
// a warning diagnostic.
36+
ResourceObject *tfsdk.ResourceObject
37+
38+
// DisplayName is a provider-defined human-readable description of the
39+
// listed managed resource instance, intended for CLI and browser UIs.
40+
DisplayName string
41+
42+
// Diagnostics report errors or warnings related to the listed managed
43+
// resource instance. An empty slice indicates a successful operation with
44+
// no warnings or errors generated.
45+
Diagnostics diag.Diagnostics
46+
}
47+
48+
// ListResource implements the framework server ListResource RPC.
49+
func (s *Server) ListResource(ctx context.Context, fwReq *ListResourceRequest, fwStream *ListResourceStream) {
50+
listResource := fwReq.ListResource
51+
52+
req := list.ListResourceRequest{
53+
Config: fwReq.Config,
54+
IncludeResourceObject: fwReq.IncludeResourceObject,
55+
}
56+
57+
stream := &list.ListResourceResponse{} // Stream{}
58+
59+
logging.FrameworkTrace(ctx, "Calling provider defined ListResource")
60+
listResource.ListResource(ctx, req, stream)
61+
logging.FrameworkTrace(ctx, "Called provider defined ListResource")
62+
63+
if stream.Diagnostics.HasError() {
64+
// fwStream.Results = slices.Values([]ListResourceEvent{})
65+
fwStream.Diagnostics = stream.Diagnostics
66+
return
67+
}
68+
69+
if stream.Results == nil {
70+
// If the provider returned a nil results stream, we treat it as an empty stream.
71+
stream.Results = iter.Seq[list.ListResourceEvent](func(yield func(list.ListResourceEvent) bool) {})
72+
}
73+
74+
fwStream.Results = listResourceEventStreamAdapter(stream.Results)
75+
}
76+
77+
func listResourceEventStreamAdapter(stream iter.Seq[list.ListResourceEvent]) iter.Seq[ListResourceEvent] {
78+
return func(yield func(ListResourceEvent) bool) {
79+
for event := range stream {
80+
if !yield(ListResourceEvent(event)) {
81+
break
82+
}
83+
}
84+
}
85+
}
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
package fwserver_test
2+
3+
import (
4+
"context"
5+
"slices"
6+
"testing"
7+
8+
"github.com/google/go-cmp/cmp"
9+
"github.com/hashicorp/terraform-plugin-framework/diag"
10+
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
11+
"github.com/hashicorp/terraform-plugin-framework/internal/testing/testprovider"
12+
"github.com/hashicorp/terraform-plugin-framework/list"
13+
"github.com/hashicorp/terraform-plugin-framework/resource/identityschema"
14+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
15+
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
16+
"github.com/hashicorp/terraform-plugin-go/tftypes"
17+
)
18+
19+
func TestServerListResource(t *testing.T) {
20+
t.Parallel()
21+
22+
testSchema := schema.Schema{
23+
Attributes: map[string]schema.Attribute{
24+
"test_computed": schema.StringAttribute{
25+
Computed: true,
26+
},
27+
"test_required": schema.StringAttribute{
28+
Required: true,
29+
},
30+
},
31+
}
32+
33+
testType := tftypes.Object{
34+
AttributeTypes: map[string]tftypes.Type{
35+
"test_attribute": tftypes.String,
36+
},
37+
}
38+
39+
testResourceObjectValue1 := tftypes.NewValue(testType, map[string]tftypes.Value{
40+
"test_attribute": tftypes.NewValue(tftypes.String, "test-value-1"),
41+
})
42+
43+
testResourceObjectValue2 := tftypes.NewValue(testType, map[string]tftypes.Value{
44+
"test_attribute": tftypes.NewValue(tftypes.String, "test-value-2"),
45+
})
46+
47+
testIdentitySchema := identityschema.Schema{
48+
Attributes: map[string]identityschema.Attribute{
49+
"test_id": identityschema.StringAttribute{
50+
RequiredForImport: true,
51+
},
52+
},
53+
}
54+
55+
testIdentityType := tftypes.Object{
56+
AttributeTypes: map[string]tftypes.Type{
57+
"test_id": tftypes.String,
58+
},
59+
}
60+
61+
testIdentityValue1 := tftypes.NewValue(testIdentityType, map[string]tftypes.Value{
62+
"test_id": tftypes.NewValue(tftypes.String, "new-id-123"),
63+
})
64+
65+
testIdentityValue2 := tftypes.NewValue(testIdentityType, map[string]tftypes.Value{
66+
"test_id": tftypes.NewValue(tftypes.String, "new-id-456"),
67+
})
68+
69+
// nilIdentityValue := tftypes.NewValue(testIdentityType, nil)
70+
71+
testCases := map[string]struct {
72+
server *fwserver.Server
73+
request *fwserver.ListResourceRequest
74+
expectedStreamEvents []fwserver.ListResourceEvent
75+
}{
76+
"success-with-zero-results": {
77+
server: &fwserver.Server{
78+
Provider: &testprovider.Provider{},
79+
},
80+
request: &fwserver.ListResourceRequest{
81+
ListResource: &testprovider.ListResource{
82+
ListResourceMethod: func(ctx context.Context, req list.ListResourceRequest, resp *list.ListResourceResponse) { // TODO
83+
resp.Results = slices.Values([]list.ListResourceEvent{})
84+
},
85+
},
86+
},
87+
expectedStreamEvents: []fwserver.ListResourceEvent{},
88+
},
89+
"success-with-nil-results": {
90+
server: &fwserver.Server{
91+
Provider: &testprovider.Provider{},
92+
},
93+
request: &fwserver.ListResourceRequest{
94+
ListResource: &testprovider.ListResource{
95+
ListResourceMethod: func(ctx context.Context, req list.ListResourceRequest, resp *list.ListResourceResponse) { // TODO
96+
// Do nothing, so that resp.Results is nil
97+
},
98+
},
99+
},
100+
expectedStreamEvents: []fwserver.ListResourceEvent{},
101+
},
102+
103+
"success-with-multiple-results": {
104+
server: &fwserver.Server{
105+
Provider: &testprovider.Provider{},
106+
},
107+
request: &fwserver.ListResourceRequest{
108+
ListResource: &testprovider.ListResource{
109+
ListResourceMethod: func(ctx context.Context, req list.ListResourceRequest, resp *list.ListResourceResponse) { // TODO
110+
resp.Results = slices.Values([]list.ListResourceEvent{
111+
{
112+
Identity: &tfsdk.ResourceIdentity{
113+
Schema: testIdentitySchema,
114+
Raw: testIdentityValue1,
115+
},
116+
ResourceObject: &tfsdk.ResourceObject{
117+
Schema: testSchema,
118+
Raw: testResourceObjectValue1,
119+
},
120+
DisplayName: "Test Resource 1",
121+
Diagnostics: diag.Diagnostics{},
122+
},
123+
{
124+
Identity: &tfsdk.ResourceIdentity{
125+
Schema: testIdentitySchema,
126+
Raw: testIdentityValue2,
127+
},
128+
ResourceObject: &tfsdk.ResourceObject{
129+
Schema: testSchema,
130+
Raw: testResourceObjectValue2,
131+
},
132+
DisplayName: "Test Resource 2",
133+
Diagnostics: diag.Diagnostics{},
134+
},
135+
})
136+
},
137+
},
138+
},
139+
expectedStreamEvents: []fwserver.ListResourceEvent{
140+
{
141+
Identity: &tfsdk.ResourceIdentity{
142+
Schema: testIdentitySchema,
143+
Raw: testIdentityValue1,
144+
},
145+
ResourceObject: &tfsdk.ResourceObject{
146+
Schema: testSchema,
147+
Raw: testResourceObjectValue1,
148+
},
149+
DisplayName: "Test Resource 1",
150+
Diagnostics: diag.Diagnostics{},
151+
},
152+
{
153+
Identity: &tfsdk.ResourceIdentity{
154+
Schema: testIdentitySchema,
155+
Raw: testIdentityValue2,
156+
},
157+
ResourceObject: &tfsdk.ResourceObject{
158+
Schema: testSchema,
159+
Raw: testResourceObjectValue2,
160+
},
161+
DisplayName: "Test Resource 2",
162+
Diagnostics: diag.Diagnostics{},
163+
},
164+
},
165+
},
166+
}
167+
168+
for name, testCase := range testCases {
169+
t.Run(name, func(t *testing.T) {
170+
t.Parallel()
171+
172+
response := &fwserver.ListResourceStream{}
173+
testCase.server.ListResource(context.Background(), testCase.request, response)
174+
175+
events := slices.AppendSeq([]fwserver.ListResourceEvent{}, response.Results)
176+
if diff := cmp.Diff(events, testCase.expectedStreamEvents); diff != "" {
177+
t.Errorf("unexpected difference: %s", diff)
178+
}
179+
})
180+
}
181+
}

list/list_resource.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ type ListResourceResponse struct {
113113
// Results is a function that emits ListResourceEvent values via its yield
114114
// function argument.
115115
Results iter.Seq[ListResourceEvent]
116+
117+
// Diagnostics report errors or warnings related to the list resource
118+
// operation. An empty slice indicates a successful operation with no
119+
// warnings or errors generated. Individual ListResourceEvents may have
120+
// specific diagnostics.
121+
Diagnostics diag.Diagnostics
116122
}
117123

118124
// ListResourceEvent represents a listed managed resource instance. A

0 commit comments

Comments
 (0)