Skip to content

Commit ae94eb8

Browse files
committed
add docs for list resources
1 parent 080daac commit ae94eb8

File tree

5 files changed

+383
-0
lines changed

5 files changed

+383
-0
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
---
2+
page_title: Configuring list resources
3+
description: >-
4+
Learn how to configure list resources with provider data or clients in
5+
the Terraform plugin framework.
6+
---
7+
8+
# Configuring list resources
9+
10+
[List Resources](/terraform/plugin/framework/list-resources) may require provider-level data or API clients to operate correctly. The framework supports the ability to configure this data and/or clients once within the provider, then pass that information to actions by adding the `Configure` method.
11+
12+
## Prepare Provider Configure Method
13+
14+
Implement the [`provider.ConfigureResponse.ListResourceData` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ConfigureResponse.ListResourceData) in the [`Provider` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Configure).
15+
16+
This value can be set to any type, whether an existing client or vendor SDK type, a provider-defined custom type, or the provider implementation itself.
17+
18+
It is recommended to use pointer types so that the list resource can determine if this value was configured before attempting to use it.
19+
20+
In this example, the Go standard library [`net/http.Client`](https://pkg.go.dev/net/http#Client) is configured in the provider and made available for the list resource:
21+
22+
```go
23+
func (p *ExampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
24+
resp.ListResourceData = &http.Client{/* ... */}
25+
}
26+
```
27+
28+
## Define List Resource Configure Method
29+
30+
Implement the [`list.ListResourceWithConfigure` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/list#ListResourceWithConfigure) which receives the provider configured data from the [`Provider` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Configure) and saves it into the [`list.ListResource` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/list#ListResource) implementation.
31+
32+
The [`list.ListResourceWithConfigure` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/list#ListResourceWithConfigure.Configure) is called during execution of any Terraform command, however the provider is not configured during "offline" operations like `terraform validate` or `terraform fmt`, so implementations need to account for that situation.
33+
34+
In this example, the provider configured the Go standard library [`net/http.Client`](https://pkg.go.dev/net/http#Client) which the list resource uses during `List`:
35+
36+
```go
37+
type ThingListResource struct {
38+
client *http.Client
39+
}
40+
41+
func (r *ThingListResource) Configure(ctx context.Context, req list.ConfigureRequest, resp *list.ConfigureResponse) {
42+
// Always perform a nil check when handling ProviderData because Terraform
43+
// sets that data after it calls the ConfigureProvider RPC.
44+
if req.ProviderData == nil {
45+
return
46+
}
47+
48+
client, ok := req.ProviderData.(*http.Client)
49+
50+
if !ok {
51+
resp.Diagnostics.AddError(
52+
"Unexpected List Configure Type",
53+
fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
54+
)
55+
56+
return
57+
}
58+
59+
d.client = client
60+
}
61+
62+
func (r *ThingListResource) List(ctx context.Context, req list.ListRequest, stream *list.ListResultsStream) {
63+
httpReq, _ := http.NewRequest(
64+
http.MethodPost,
65+
"http://example.com/api/list_thing",
66+
bytes.NewBuffer([]byte(`{"fake": "data"}`)),
67+
)
68+
69+
httpResp, err := d.client.Do(httpReq)
70+
/* ... */
71+
}
72+
```
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
---
2+
page_title: List Resources Overview
3+
description: >-
4+
List resources allows providers to extend existing resources with list capabilities to
5+
enable searching for a specific resource type within a given scope. Learn how
6+
to implement list in the Terraform plugin framework.
7+
---
8+
9+
# List
10+
11+
<Highlight>
12+
13+
List support is in technical preview and offered without compatibility promises until Terraform 1.14 is generally available.
14+
15+
</Highlight>
16+
17+
[List resources](/terraform/language/v1.14.x/resources/list) are an abstraction that allows Terraform to search for a specific resource type within a given scope, for example listing all EC2 instances within an AWS account or all virtual networks within a resource group. Taking a list configuration as input, remote objects are retrieved and returned to Terraform as list result data which is displayed to the user.
18+
19+
Searching is invoked directly with the Terraform CLI via the `terraform query` command with list blocks authored in a `.tfquery.hcl` file.
20+
21+
When implementing list there needs to be a corresponding resource implementation since the results that are returned rely on the pre-existing definitions of the resource's identity and schema.
22+
23+
This page describes the basic implementation details required for supporting a list resource within the provider. List resources must implement:
24+
25+
- [List](/terraform/plugin/framework/list-resources/list) a resource by receiving Terraform configuration, retrieving remote objects, and returning list result data to Terraform.
26+
27+
Further documentation is available for deeper list resource concepts:
28+
29+
- [Configure](/terraform/plugin/framework/list-resources/configure) a list resource with provider-level data types or clients.
30+
- [Validate](/terraform/plugin/framework/list-resources/validate-configuration) practitioner configuration against acceptable values.
31+
32+
For details on how to implement a list resource with a non-framework resource see the [guide for non-framework resources](/terraform/plugin/framework/list-resources/non-framework-resource)
33+
34+
See the [Guide for non-Framework Resources](/terraform/plugin/framework/list-resources/non-framework-resource) for more details.
35+
36+
## Define List Resource Type
37+
38+
Implement the [`listresource.ListResource` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/listresource#ListResource). Ensure the [Add List Resource To Provider](#add-list-resource-to-provider) documentation is followed so the list resource becomes part of the provider implementation, and therefore available to practitioners.
39+
40+
## Metadata method
41+
42+
The [`list.ListResource` interface `Metadata` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/list#ListResource.Metadata) defines the name of the resource type that a list resource is associated with.
43+
44+
This name should include the provider type prefix, an underscore, then the resource specific name. For example, a provider named `examplecloud` and a resource that reads "thing" resources would be named `examplecloud_thing`.
45+
46+
Since this method shares the same signature as the [`resource.Resource` interface `Metadata` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource.Metadata), if the list resource extends a framework resource then the Metadata does not need to be defined again since it will already exist for the resource.
47+
48+
In cases where the list resource extends a non-framework resource, the Metadata must be defined. See the [RawV5Schemas and RawV6Schemas Method](#rawv5schemas-and-rawv6schemas-method) section for more details on using a list resource with a non-framework resource.
49+
50+
In this example, the provider defines the `examplecloud` name for itself, and the resource is named `examplecloud_thing`:
51+
52+
```go
53+
// With the provider.Provider implementation
54+
func (p *ExampleCloudProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) {
55+
resp.TypeName = "examplecloud"
56+
}
57+
58+
// With the list.ListResource implementation
59+
func (r *ThingListResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
60+
resp.TypeName = req.ProviderTypeName + "_thing"
61+
}
62+
```
63+
64+
Alternatively the entire name can be hardcoded:
65+
```go
66+
func (r *ThingListResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
67+
resp.TypeName = "examplecloud_thing"
68+
}
69+
```
70+
71+
## Schema method
72+
73+
The [`list.ListResource` interface `ListResourceConfigSchema` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/list#ListResource.ListResourceConfigSchema) defines a [schema](/terraform/plugin/framework/handling-data/schemas) describing what data is available in the list resource configuration.
74+
75+
## Add List Resource to Provider
76+
77+
List resources become available to practitioners when they are included in the [provider](/terraform/plugin/framework/providers) implementation via the optional [`provider.ProviderWithListResources` interface `ListResources` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ProviderWithListResources.ListResources).
78+
79+
In this example, the `ThingListResource` type, which implements the `list.ListResource` interface, is added to the provider implementation:
80+
81+
```go
82+
var _ provider.ProviderWithListResources = (*ExampleCloudProvider)(nil)
83+
84+
func (p *ExampleCloudProvider) ListResources(_ context.Context) []func() list.ListResource {
85+
return []func() list.ListResource{
86+
NewThingListResource,
87+
}
88+
}
89+
90+
func NewThingListResource() list.ListResource {
91+
return &ThingListResource{}
92+
}
93+
```
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
---
2+
page_title: Listing resources
3+
description: >-
4+
Learn how to list a resource in the Terraform plugin framework.
5+
---
6+
7+
# Listing resources
8+
9+
List is called when a `terraform query` is executed, the `ListResource` RPC is called which in turn calls the [`list.ListResource` interface `List`] method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/list#ListResource.List). The request contains the configuration supplied to Terraform by the list block and the response contains a stream of the list results.
10+
11+
## Define List Method
12+
13+
Implement the `List` method by:
14+
15+
1. [Accessing the `Config` data](/terraform/plugin/framework/handling-data/accessing-values) from the [`list.ListRequest` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/list#ListRequest).
16+
1. Performing external calls and logic for the list operation.
17+
1. For each list result instantiate a new result object using the [`list.ListRequest.NewListResult`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/list#ListRequest.NewListResult) method.
18+
1. Return an iterator function that pushes list results into the response stream.
19+
20+
If the logic needs to return [warning or error diagnostics](/terraform/plugin/framework/diagnostics), they can be added into the [`list.ListResult.Diagnostics`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/list#ListResult.Diagnostics) field of an empty list result and pushed into the stream.
21+
22+
In this example, the list method on the resource `examplecloud_thing` is defined:
23+
24+
```go
25+
// ThingListResource defines the list implementation.
26+
// Some list.ListResource interface methods are omitted for brevity.
27+
type ThingListResource struct{}
28+
29+
type ThingListResourceModel struct {
30+
ResourceGroupName types.String `tfsdk:"resource_group_name"`
31+
}
32+
33+
func (r *ThingListResource) ListResourceConfigSchema(_ context.Context, _ list.ListResourceConfigSchemaRequest, resp *list.ListResourceConfigSchemaResponse) {
34+
resp.Schema = listschema.Schema{
35+
Attributes: map[string]listschema.Attribute{
36+
"resource_group_name": listschema.StringAttribute{
37+
Description: "Name of the resource group to list things in.",
38+
Required: true,
39+
},
40+
},
41+
}
42+
}
43+
44+
func (r *ThingListResource) List(ctx context.Context, req list.ListRequest, stream *list.ListResultsStream) {
45+
var data ThingListResourceModel
46+
47+
// Read list config data into the model
48+
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
49+
if resp.Diagnostics.HasError() {
50+
return
51+
}
52+
53+
// Typically lists will make external calls here using data from the config
54+
// as input. For brevity, we assume the `things` slice below was returned by an
55+
// API call here
56+
57+
// Define the function that will push results into the stream
58+
stream.Results = func(push func(list.ListResult) bool) {
59+
for _, thing := range things {
60+
// Initialize a new result object for each thing
61+
result := req.NewListResult(ctx)
62+
63+
// Set the user-friendly name of this thing
64+
result.DisplayName = thing.Name
65+
66+
// Set resource identity data on the result
67+
resp.Diagnostics.Append(result.Identity.Set(ctx, thing.ID))
68+
if resp.Diagnostics.HasError() {
69+
return
70+
}
71+
72+
// Set the resource information on the result
73+
resp.Diagnostics.Append(result.Resource.Set(ctx, thing.Resource))
74+
if resp.Diagnostics.HasError() {
75+
return
76+
}
77+
78+
// Send the result to the stream.
79+
if !push(result) {
80+
return
81+
}
82+
}
83+
}
84+
}
85+
```
86+
87+
## Caveats
88+
89+
* An error is returned if a list result in the stream contains a null identity unless it contains a warning in the diagnostics.
90+
* An error is returned if `include_resource` is set to `true` and a list result in the stream has a null resource.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
page_title: Implement list resource with non-framework resource
3+
description: >-
4+
Learn how to implement a list resource with a non-framework resource.
5+
---
6+
7+
# List with a Non-Framework Resource
8+
9+
If the corresponding resource implementation is not a framework resource then the list resource must define the [metadata](#metadata-method) as well as either the [`list.ListResourceWithRawV5Schemas` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/list#ListResourceWithRawV5Schemas) or [`list.ListResourceWithRawV6Schemas` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/list#ListResourceWithRawV6Schemas) interface.
10+
11+
These interfaces expose either a [RawV5Schemas or RawV6Schemas method](#rawv5schemas-and-rawv6schemas-method) that allows the practitioner to supply the resource as well as identity schema information upfront.
12+
13+
In addition to the schema information, the list resource must also implement the [Metadata method](#metadata-method) to define the resource type name.
14+
15+
16+
## RawV5Schemas and RawV6Schemas Method
17+
18+
A list resource that extends a non-framework resource must supply the relevant schema information upfront as well as define the `Metadata` method. This is done by implementing either of the methods below, depending on the protocol version the resource is based on.
19+
20+
* [`list.ListResourceWithRawV5Schemas` interface `RawV5Schemas` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/list#ListResourceWithRawV5Schemas.RawV5Schemas)
21+
* [`list.ListResourceWithRawV6Schemas` interface `RawV6Schemas` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/list#ListResourceWithRawV6Schemas.RawV6Schemas)
22+
23+
In this example, the resource `examplecloud_thing` is not defined with framework but is based on protocol v5:
24+
25+
```go
26+
type ThingListResource struct {}
27+
28+
func (r *ThingListResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
29+
resp.TypeName = "examplecloud_thing"
30+
}
31+
32+
func (r *ThingListResource) RawV5Schemas(ctx context.Context, req list.RawV5SchemaRequest, resp *list.RawV5SchemaResponse) {
33+
// This sets the resource schema information for the associated resource
34+
resp.ProtoV5Schema = &tfprotov5.Schema{
35+
/* ... */
36+
}
37+
// This sets the resource identity schema information for the associated resource
38+
resp.ProtoV5IdentitySchema = &tfprotov5.Schema{
39+
/* ... */
40+
}
41+
}
42+
```
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
---
2+
page_title: Validate list resource configurations
3+
description: >-
4+
Learn how to validate list resource configurations with the Terraform
5+
plugin framework.
6+
---
7+
8+
# Validate list resource configurations
9+
10+
List resource supports validating the entire practitioner configuration in either declarative or imperative ways. Feedback such as required syntax or acceptable combination of values, is returned via [diagnostics](/terraform/plugin/framework/diagnostics).
11+
12+
This section describes implementation details for validating entire list configurations, typically referencing multiple attributes.
13+
14+
Further documentation is available for other configuration validation concepts:
15+
16+
- [Single attribute validation](/terraform/plugin/framework/validation#attribute-validation) is a schema-based mechanism for implementing attribute-specific validation logic.
17+
- [Type validation](/terraform/plugin/framework/validation#type-validation) is a schema-based mechanism for implementing reusable validation logic for any attribute using the type.
18+
19+
-> Configuration validation in Terraform occurs without provider configuration ("offline"), therefore the list resource `Configure` method will not have been called.
20+
21+
## ConfigValidators Method
22+
23+
The [`list.ListResourceWithConfigValidators` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/list#ListResourceWithConfigValidators) follows a similar pattern to attribute validation and allows for a more declarative approach.
24+
25+
This enables consistent validation logic across multiple list resources. Each validation intended for this interface must implement the [`list.ConfigValidator` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/list#ConfigValidator).
26+
27+
The [`terraform-plugin-framework-validators` Go module](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) has a collection of common use case list configuration validators in the [`listresourcevalidator` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/listresourcevalidator).
28+
29+
These use [path expressions](/terraform/plugin/framework/path-expressions) for matching attributes.
30+
31+
This example will raise an error if a practitioner attempts to configure both `attribute_one` and `attribute_two`:
32+
33+
```go
34+
// Other methods to implement the list.ListResource interface are omitted for brevity
35+
type ThingListResource struct {}
36+
37+
func (r ThingListResource) ConfigValidators(ctx context.Context) []list.ConfigValidator {
38+
return []list.ConfigValidator{
39+
listvalidator.Conflicting(
40+
path.MatchRoot("attribute_one"),
41+
path.MatchRoot("attribute_two"),
42+
),
43+
}
44+
}
45+
```
46+
47+
### ValidateConfig Method
48+
49+
The [`list.ListResourceWithValidateConfig` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/list#ListResourceWithValidateConfig) allows for imperative validation logic and is useful for validating unique functionality across multiple attributes that typically applies to a single list resource.
50+
51+
```go
52+
// Other methods to implement the list.ListResource interface are omitted for brevity
53+
type ThingListResource struct {}
54+
55+
type ThingListResourceModel struct {
56+
AttributeOne types.String `tfsdk:"attribute_one"`
57+
AttributeTwo types.String `tfsdk:"attribute_two"`
58+
}
59+
60+
func (r ThingListResource) ValidateConfig(ctx context.Context, req list.ValidateConfigRequest, resp *list.ValidateConfigResponse) {
61+
var data ThingListResourceModel
62+
63+
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
64+
65+
if resp.Diagnostics.HasError() {
66+
return
67+
}
68+
69+
// If attribute_one is not configured, return without warning.
70+
if data.AttributeOne.IsNull() || data.AttributeOne.IsUnknown() {
71+
return
72+
}
73+
74+
// If attribute_two is not null, return without warning.
75+
if !data.AttributeTwo.IsNull() {
76+
return
77+
}
78+
79+
resp.Diagnostics.AddAttributeWarning(
80+
path.Root("attribute_two"),
81+
"Missing Attribute Configuration",
82+
"Expected attribute_two to be configured with attribute_one. "+
83+
"The list resource may return unexpected results.",
84+
)
85+
}
86+
```

0 commit comments

Comments
 (0)