Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,31 @@
}
]
},
{
"title": "List Resources",
"routes": [
{
"title": "Overview",
"path": "list-resources"
},
{
"title": "List",
"path": "list-resources/list"
},
{
"title": "Configure Clients",
"path": "list-resources/configure"
},
{
"title": "Validate Configuration",
"path": "list-resources/validate-configuration"
},
{
"title": "Non-Framework Resource",
"path": "list-resources/non-framework-resource"
}
]
},
{
"title": "Handling Data",
"routes": [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
page_title: Configuring list resources
description: >-
Learn how to configure list resources with provider data or clients in
the Terraform plugin framework.
---

# Configuring list resources

[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.

## Prepare Provider Configure Method

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).

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.

It is recommended to use pointer types so that the list resource can determine if this value was configured before attempting to use it.

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:

```go
func (p *ExampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
resp.ListResourceData = &http.Client{/* ... */}
}
```

## Define List Resource Configure Method

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.

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.

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`:

```go
type ThingListResource struct {
client *http.Client
}

func (r *ThingListResource) Configure(ctx context.Context, req list.ConfigureRequest, resp *list.ConfigureResponse) {
// Always perform a nil check when handling ProviderData because Terraform
// sets that data after it calls the ConfigureProvider RPC.
if req.ProviderData == nil {
return
}

client, ok := req.ProviderData.(*http.Client)

if !ok {
resp.Diagnostics.AddError(
"Unexpected List Configure Type",
fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)

return
}

d.client = client
}

func (r *ThingListResource) List(ctx context.Context, req list.ListRequest, stream *list.ListResultsStream) {
httpReq, _ := http.NewRequest(
http.MethodPost,
"http://example.com/api/list_thing",
bytes.NewBuffer([]byte(`{"fake": "data"}`)),
)

httpResp, err := d.client.Do(httpReq)
/* ... */
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
page_title: List Resources Overview
description: >-
List resources allows providers to extend existing resources with list capabilities to
enable searching for a specific resource type within a given scope. Learn how
to implement list in the Terraform plugin framework.
---

# List

<Highlight>

List support is in technical preview and offered without compatibility promises until Terraform 1.14 is generally available.

</Highlight>

[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.

Searching is invoked directly with the Terraform CLI via the `terraform query` command with list blocks authored in a `.tfquery.hcl` file.

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.

This page describes the basic implementation details required for supporting a list resource within the provider. List resources must implement:

- [List](/terraform/plugin/framework/list-resources/list) a resource by receiving Terraform configuration, retrieving remote objects, and returning list result data to Terraform.

Further documentation is available for deeper list resource concepts:

- [Configure](/terraform/plugin/framework/list-resources/configure) a list resource with provider-level data types or clients.
- [Validate](/terraform/plugin/framework/list-resources/validate-configuration) practitioner configuration against acceptable values.

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)

See the [Guide for non-Framework Resources](/terraform/plugin/framework/list-resources/non-framework-resource) for more details.

## Define List Resource Type

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.

## Metadata method

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.

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`.

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.

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.

In this example, the provider defines the `examplecloud` name for itself, and the resource is named `examplecloud_thing`:

```go
// With the provider.Provider implementation
func (p *ExampleCloudProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) {
resp.TypeName = "examplecloud"
}

// With the list.ListResource implementation
func (r *ThingListResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_thing"
}
```

Alternatively the entire name can be hardcoded:
```go
func (r *ThingListResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = "examplecloud_thing"
}
```

## Schema method

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.

## Add List Resource to Provider

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).

In this example, the `ThingListResource` type, which implements the `list.ListResource` interface, is added to the provider implementation:

```go
var _ provider.ProviderWithListResources = (*ExampleCloudProvider)(nil)

func (p *ExampleCloudProvider) ListResources(_ context.Context) []func() list.ListResource {
return []func() list.ListResource{
NewThingListResource,
}
}

func NewThingListResource() list.ListResource {
return &ThingListResource{}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
page_title: Listing resources
description: >-
Learn how to list a resource in the Terraform plugin framework.
---

# Listing resources

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.

## Define List Method

Implement the `List` method by:

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).
1. Performing external calls and logic for the list operation.
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.
1. Return an iterator function that pushes list results into the response stream.

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.

In this example, the list method on the resource `examplecloud_thing` is defined:

```go
// ThingListResource defines the list implementation.
// Some list.ListResource interface methods are omitted for brevity.
type ThingListResource struct{}

type ThingListResourceModel struct {
ResourceGroupName types.String `tfsdk:"resource_group_name"`
}

func (r *ThingListResource) ListResourceConfigSchema(_ context.Context, _ list.ListResourceConfigSchemaRequest, resp *list.ListResourceConfigSchemaResponse) {
resp.Schema = listschema.Schema{
Attributes: map[string]listschema.Attribute{
"resource_group_name": listschema.StringAttribute{
Description: "Name of the resource group to list things in.",
Required: true,
},
},
}
}

func (r *ThingListResource) List(ctx context.Context, req list.ListRequest, stream *list.ListResultsStream) {
var data ThingListResourceModel

// Read list config data into the model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}

// Typically lists will make external calls here using data from the config
// as input. For brevity, we assume the `things` slice below was returned by an
// API call here

// Define the function that will push results into the stream
stream.Results = func(push func(list.ListResult) bool) {
for _, thing := range things {
// Initialize a new result object for each thing
result := req.NewListResult(ctx)

// Set the user-friendly name of this thing
result.DisplayName = thing.Name

// Set resource identity data on the result
resp.Diagnostics.Append(result.Identity.Set(ctx, thing.ID))
if resp.Diagnostics.HasError() {
return
}

// Set the resource information on the result
resp.Diagnostics.Append(result.Resource.Set(ctx, thing.Resource))
if resp.Diagnostics.HasError() {
return
}

// Send the result to the stream.
if !push(result) {
return
}
}
}
}
```

## Caveats

* An error is returned if a list result in the stream contains a null identity unless it contains a warning in the diagnostics.
* An error is returned if `include_resource` is set to `true` and a list result in the stream has a null resource.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
page_title: Implement list resource with non-framework resource
description: >-
Learn how to implement a list resource with a non-framework resource.
---

# List with a Non-Framework Resource

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.

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.

In addition to the schema information, the list resource must also implement the [Metadata method](#metadata-method) to define the resource type name.


## RawV5Schemas and RawV6Schemas Method

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.

* [`list.ListResourceWithRawV5Schemas` interface `RawV5Schemas` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/list#ListResourceWithRawV5Schemas.RawV5Schemas)
* [`list.ListResourceWithRawV6Schemas` interface `RawV6Schemas` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/list#ListResourceWithRawV6Schemas.RawV6Schemas)

In this example, the resource `examplecloud_thing` is not defined with framework but is based on protocol v5:

```go
type ThingListResource struct {}

func (r *ThingListResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = "examplecloud_thing"
}

func (r *ThingListResource) RawV5Schemas(ctx context.Context, req list.RawV5SchemaRequest, resp *list.RawV5SchemaResponse) {
// This sets the resource schema information for the associated resource
resp.ProtoV5Schema = &tfprotov5.Schema{
/* ... */
}
// This sets the resource identity schema information for the associated resource
resp.ProtoV5IdentitySchema = &tfprotov5.Schema{
/* ... */
}
}
```
Loading
Loading