Skip to content

Commit ed02781

Browse files
committed
Add ListResource RPC
1 parent d07f61a commit ed02781

26 files changed

+1893
-38
lines changed

go.mod

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ toolchain go1.23.7
66

77
require (
88
github.com/google/go-cmp v0.7.0
9-
github.com/hashicorp/terraform-plugin-go v0.28.0
9+
github.com/hashicorp/terraform-plugin-go v0.28.1-0.20250612194609-92d41e5e5c0a
1010
github.com/hashicorp/terraform-plugin-log v0.9.0
1111
)
1212

@@ -16,7 +16,7 @@ require (
1616
github.com/hashicorp/go-hclog v1.5.0 // indirect
1717
github.com/hashicorp/go-plugin v1.6.3 // indirect
1818
github.com/hashicorp/go-uuid v1.0.3 // indirect
19-
github.com/hashicorp/terraform-registry-address v0.2.5 // indirect
19+
github.com/hashicorp/terraform-registry-address v0.3.0 // indirect
2020
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
2121
github.com/hashicorp/yamux v0.1.1 // indirect
2222
github.com/mattn/go-colorable v0.1.12 // indirect
@@ -25,10 +25,10 @@ require (
2525
github.com/oklog/run v1.0.0 // indirect
2626
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
2727
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
28-
golang.org/x/net v0.39.0 // indirect
29-
golang.org/x/sys v0.32.0 // indirect
30-
golang.org/x/text v0.24.0 // indirect
31-
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
32-
google.golang.org/grpc v1.72.1 // indirect
28+
golang.org/x/net v0.41.0 // indirect
29+
golang.org/x/sys v0.33.0 // indirect
30+
golang.org/x/text v0.26.0 // indirect
31+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect
32+
google.golang.org/grpc v1.73.0 // indirect
3333
google.golang.org/protobuf v1.36.6 // indirect
3434
)

go.sum

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0U
2121
github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0=
2222
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
2323
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
24-
github.com/hashicorp/terraform-plugin-go v0.28.0 h1:zJmu2UDwhVN0J+J20RE5huiF3XXlTYVIleaevHZgKPA=
25-
github.com/hashicorp/terraform-plugin-go v0.28.0/go.mod h1:FDa2Bb3uumkTGSkTFpWSOwWJDwA7bf3vdP3ltLDTH6o=
24+
github.com/hashicorp/terraform-plugin-go v0.28.1-0.20250612194609-92d41e5e5c0a h1:nVzFWFdKJ1DB0j9Q8DXd9uyrsTYq2ehZGAYuYWyqi2w=
25+
github.com/hashicorp/terraform-plugin-go v0.28.1-0.20250612194609-92d41e5e5c0a/go.mod h1:hL//wLEfYo0YVt0TC/VLzia/ADQQto3HEm4/jX2gkdY=
2626
github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=
2727
github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow=
28-
github.com/hashicorp/terraform-registry-address v0.2.5 h1:2GTftHqmUhVOeuu9CW3kwDkRe4pcBDq0uuK5VJngU1M=
29-
github.com/hashicorp/terraform-registry-address v0.2.5/go.mod h1:PpzXWINwB5kuVS5CA7m1+eO2f1jKb5ZDIxrOPfpnGkg=
28+
github.com/hashicorp/terraform-registry-address v0.3.0 h1:HMpK3nqaGFPS9VmgRXrJL/dzHNdheGVKk5k7VlFxzCo=
29+
github.com/hashicorp/terraform-registry-address v0.3.0/go.mod h1:jRGCMiLaY9zii3GLC7hqpSnwhfnCN5yzvY0hh4iCGbM=
3030
github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ=
3131
github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc=
3232
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
@@ -56,32 +56,32 @@ github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAh
5656
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
5757
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
5858
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
59-
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
60-
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
61-
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
62-
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
63-
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
64-
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
65-
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
66-
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
67-
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
68-
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
69-
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
70-
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
59+
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
60+
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
61+
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
62+
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
63+
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
64+
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
65+
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
66+
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
67+
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
68+
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
69+
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
70+
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
7171
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
7272
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
7373
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
7474
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
7575
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
7676
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
77-
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
78-
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
79-
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
80-
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
81-
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
82-
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
83-
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
84-
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
77+
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
78+
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
79+
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
80+
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
81+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g=
82+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
83+
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
84+
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
8585
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
8686
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
8787
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

internal/fwschemadata/data_description.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ const (
1212
// a plan-based value.
1313
DataDescriptionPlan DataDescription = "plan"
1414

15+
// DataDescriptionResource is used for Data that represents
16+
// a resource value.
17+
DataDescriptionResource DataDescription = "resource"
18+
1519
// DataDescriptionState is used for Data that represents
1620
// a state-based value.
1721
DataDescriptionState DataDescription = "state"
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package fwserver
5+
6+
import (
7+
"context"
8+
"errors"
9+
"iter"
10+
11+
"github.com/hashicorp/terraform-plugin-framework/diag"
12+
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
13+
"github.com/hashicorp/terraform-plugin-framework/internal/logging"
14+
"github.com/hashicorp/terraform-plugin-framework/list"
15+
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
16+
)
17+
18+
// ListRequest is the framework server request for the ListResource RPC.
19+
type ListRequest struct {
20+
// ListResource is an instance of the provider's list resource
21+
// implementation for a specific managed resource type.
22+
ListResource list.ListResource
23+
24+
// Config is the configuration the user supplied for listing resource
25+
// instances.
26+
Config *tfsdk.Config
27+
28+
// IncludeResource indicates whether the provider should populate the
29+
// Resource field in the ListResult struct.
30+
IncludeResource bool
31+
32+
ResourceSchema fwschema.Schema
33+
ResourceIdentitySchema fwschema.Schema
34+
}
35+
36+
// ListResultsStream represents a streaming response to a [ListRequest]. An
37+
// instance of this struct is supplied as an argument to the provider's List
38+
// function. The provider should set a Results iterator function that pushes
39+
// zero or more results of type [ListResult].
40+
//
41+
// For convenience, a provider implementation may choose to convert a slice of
42+
// results into an iterator using [slices.Values].
43+
type ListResultsStream struct {
44+
// Results is a function that emits [ListResult] values via its push
45+
// function argument.
46+
Results iter.Seq[ListResult]
47+
}
48+
49+
func ListResultError(summary string, detail string) ListResult {
50+
return ListResult{
51+
Diagnostics: diag.Diagnostics{
52+
diag.NewErrorDiagnostic(summary, detail),
53+
},
54+
}
55+
}
56+
57+
// ListResult represents a listed managed resource instance.
58+
type ListResult struct {
59+
// Identity is the identity of the managed resource instance. A nil value
60+
// will raise will raise a diagnostic.
61+
Identity *tfsdk.ResourceIdentity
62+
63+
// Resource is the provider's representation of the attributes of the
64+
// listed managed resource instance.
65+
//
66+
// If [ListRequest.IncludeResource] is true, a nil value will raise
67+
// a warning diagnostic.
68+
Resource *tfsdk.Resource
69+
70+
// DisplayName is a provider-defined human-readable description of the
71+
// listed managed resource instance, intended for CLI and browser UIs.
72+
DisplayName string
73+
74+
// Diagnostics report errors or warnings related to the listed managed
75+
// resource instance. An empty slice indicates a successful operation with
76+
// no warnings or errors generated.
77+
Diagnostics diag.Diagnostics
78+
}
79+
80+
// ListResource implements the framework server ListResource RPC.
81+
func (s *Server) ListResource(ctx context.Context, fwReq *ListRequest, fwStream *ListResultsStream) error {
82+
listResource := fwReq.ListResource
83+
84+
if fwReq.Config == nil {
85+
return errors.New("Invalid ListResource request: Config cannot be nil")
86+
}
87+
88+
req := list.ListRequest{
89+
Config: *fwReq.Config,
90+
IncludeResource: fwReq.IncludeResource,
91+
ResourceSchema: fwReq.ResourceSchema, // TODO: revisit
92+
ResourceIdentitySchema: fwReq.ResourceIdentitySchema, // TODO: revisit
93+
}
94+
95+
stream := &list.ListResultsStream{}
96+
97+
logging.FrameworkTrace(ctx, "Calling provider defined ListResource")
98+
listResource.List(ctx, req, stream)
99+
logging.FrameworkTrace(ctx, "Called provider defined ListResource")
100+
101+
// If the provider returned a nil results stream, we return an empty stream.
102+
if stream.Results == nil {
103+
stream.Results = list.NoListResults
104+
}
105+
106+
fwStream.Results = processListResults(req, stream.Results)
107+
return nil
108+
}
109+
110+
func processListResults(req list.ListRequest, stream iter.Seq[list.ListResult]) iter.Seq[ListResult] {
111+
return func(push func(ListResult) bool) {
112+
for result := range stream {
113+
if !push(processListResult(req, result)) {
114+
return
115+
}
116+
}
117+
}
118+
}
119+
120+
// processListResult validates the content of a list.ListResult and returns a
121+
// ListResult
122+
func processListResult(req list.ListRequest, result list.ListResult) ListResult {
123+
if result.Diagnostics.HasError() {
124+
return ListResult(result)
125+
}
126+
127+
if result.Identity == nil { // TODO: is result.Identity.Raw.IsNull() a practical concern?
128+
return ListResultError(
129+
"Incomplete List Result",
130+
"The provider did not populate the Identity field in the ListResourceResult. This may be due to an error in the provider's implementation.",
131+
)
132+
}
133+
134+
if req.IncludeResource {
135+
if result.Resource == nil { // TODO: is result.Resource.Raw.IsNull() a practical concern?
136+
result.Resource = nil
137+
result.Diagnostics.AddWarning(
138+
"Incomplete List Result",
139+
"The provider did not populate the Resource field in the ListResourceResult. This may be due to the provider not supporting this functionality or an error in the provider's implementation.",
140+
)
141+
}
142+
}
143+
144+
return ListResult(result) // TODO: do we need to .Copy() the raw Identity and Resource values?
145+
}

0 commit comments

Comments
 (0)