Skip to content

Add [email protected] documentation #355

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
page_title: Acceptance tests
description: >-
Learn how to write acceptance tests for providers built on the framework.
Acceptance tests help ensure your provider works as expected by imitating
Terraform operations.
---

# Acceptance tests

Implement provider resource and data source acceptance tests with the [terraform-plugin-testing module](/terraform/plugin/testing). These tests are designed to execute Terraform commands against real Terraform configurations, simulating practitioner experiences with creating, refreshing, updating, and deleting infrastructure.

This page only describes requirements of using the testing module with a framework provider. Refer to the [testing module documentation](/terraform/plugin/testing) for additional information on all available functionality in tests.

## Requirements

The testing module must know how to reference your provider code before Terraform commands and configurations can succeed. This is achieved by pointing the testing module at a [provider server](/terraform/plugin/framework/provider-servers) which wraps your [provider](/terraform/plugin/framework/providers).

Use one of the [`resource.TestCase` type](/terraform/plugin/testing/acceptance-tests/testcase) [`ProtoV6ProviderFactories` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCase.ProtoV6ProviderFactories) for [protocol version 6](/terraform/plugin/terraform-plugin-protocol#protocol-version-6) or [`ProtoV5ProviderFactories` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCase.ProtoV5ProviderFactories) for [protocol version 5](/terraform/plugin/terraform-plugin-protocol#protocol-version-5). It is only necessary to test with the single protocol version matching the production provider server, typically defined in the `main.go` file of the provider codebase.

### Protocol Version 6

Use the [`providerserver.NewProtocol6WithError`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/providerserver#NewProtocol6WithError) helper function to implement the provider server in the [`ProtoV6ProviderFactories` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCase.ProtoV6ProviderFactories).

```go
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error) {
// newProvider is an example function that returns a provider.Provider
"examplecloud": providerserver.NewProtocol6WithError(newProvider()),
},
Steps: []resource.TestStep{/* ... */},
})
```

### Protocol Version 5

Use the [`providerserver.NewProtocol5WithError`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/providerserver#NewProtocol5WithError) helper function to implement the provider server in the [`ProtoV5ProviderFactories` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCase.ProtoV5ProviderFactories).

```go
resource.Test(t, resource.TestCase{
ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error) {
// newProvider is an example function that returns a provider.Provider
"examplecloud": providerserver.NewProtocol5WithError(newProvider()),
},
Steps: []resource.TestStep{/* ... */},
})
```

## Troubleshooting

### No id found in attributes

<Highlight>

terraform-plugin-testing version 1.5.0 and later no longer require managed resources and data resources to implement the `id` attribute.

</Highlight>

In SDKv2, resources and data sources automatically included an implicit, root level `id` attribute. In the framework, the `id` attribute is not implicitly added.

When testing resources and data sources without the `id` attribute, the acceptance testing framework will return errors such as:

```text
testing_new_config.go:111: no "id" found in attributes
testing_new.go:53: no "id" found in attributes
```

To avoid this, add a root level `id` attribute to resource and data source schemas. Ensure the attribute value is appropriately [written to state](/terraform/plugin/framework/writing-state). Conventionally, `id` is a computed attribute that contains the identifier for the resource.

For example, in the `Schema` method implementation of a [`datasource.DataSource`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSource) or [`resource.Resource`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource):

```go
func (r *ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
// ... potentially other schema configuration ...
Attributes: map[string]schema.Attribute{
// ... potentially other schema attributes ...
"id": schema.StringAttribute{
Computed: true,
},
},
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
---
page_title: Configure data sources
description: >-
Learn how to configure data sources with provider data or clients in the
Terraform plugin framework.
---

# Configure data sources

[Data sources](/terraform/plugin/framework/data-sources) may require provider-level data or remote system 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 data sources by adding the `Configure` method.

## Prepare Provider Configure Method

Implement the [`provider.ConfigureResponse.DataSourceData` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ConfigureResponse.DataSourceData) 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 data sources can determine if this value was configured before attempting to use it.

During execution of the [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the [`ConfigureProvider`](/terraform/plugin/framework/internals/rpcs#configureprovider-rpc) RPC, in which the framework calls the [`provider.Provider` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Configure).

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 data sources:

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

In this example, the code defines an `ExampleClient` type that is made available for data sources:

```go
type ExampleClient struct {
/* ... */
}

// With the provider.Provider implementation
func (p *ExampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
resp.DataSourceData = &ExampleClient{/* ... */}
}
```

In this example, the `ExampleCloudProvider` type itself is made available for data sources:

```go
// With the provider.Provider implementation
type ExampleCloudProvider struct {
/* ... */
}

func (p *ExampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
resp.DataSourceData = p
}
```

## Define Data Source Configure Method

Implement the [`datasource.DataSourceWithConfigure` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSourceWithConfigure) 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 [`datasource.DataSource` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSource) implementation.

The [`datasource.DataSourceWithConfigure` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSourceWithConfigure.Configure) is called during execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands when the [`ValidateDataResourceConfig`](/terraform/plugin/framework/internals/rpcs#validatedataresourceconfig-rpc) RPC is sent. Additionally, the [`datasource.DataSourceWithConfigure` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSourceWithConfigure.Configure) is called during execution of the [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands when the [`ReadDataSource`](/terraform/plugin/framework/internals/rpcs#readdatasource-rpc) RPC is sent.

-> Note that Terraform calling the [`ValidateDataResourceConfig`](/terraform/plugin/framework/internals/rpcs#validatedataresourceconfig-rpc) RPC would not call the [`ConfigureProvider`](/terraform/plugin/framework/internals/rpcs#configureprovider-rpc) RPC first, so implementations need to account for that situation. Configuration validation in Terraform occurs without provider configuration ("offline").

In this example, the provider configured the Go standard library [`net/http.Client`](https://pkg.go.dev/net/http#Client) which the data source uses during `Read`:

```go
// With the datasource.DataSource implementation
type ThingDataSource struct {
client *http.Client
}

func (d *ThingDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.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 Data Source Configure Type",
fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)

return
}

d.client = client
}

func (d *ThingDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
// Prevent panic if the provider has not been configured.
if d.client == nil {
resp.Diagnostics.AddError(
"Unconfigured HTTP Client",
"Expected configured HTTP client. Please report this issue to the provider developers.",
)

return
}

httpResp, err := d.client.Get("https://example.com")
/* ... */
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
---
page_title: Data sources
description: >-
Data sources allow Terraform to reference external data. Learn how the
framework can help you implement data sources.
---

# Data sources

[Data sources](/terraform/language/data-sources) are an abstraction that allow Terraform to reference external data. Unlike [managed resources](/terraform/language/resources), Terraform does not manage the lifecycle of the resource or data. Data sources are intended to have no side-effects.

This page describes the basic implementation details required for supporting a data source within the provider. Further documentation is available for deeper data source concepts:

- [Configure](/terraform/plugin/framework/data-sources/configure) data sources with provider-level data types or clients.
- [Validate](/terraform/plugin/framework/data-sources/validate-configuration) practitioner configuration against acceptable values.
- [Timeouts](/terraform/plugin/framework/data-sources/timeouts) in practitioner configuration for use in a data source read function.

## Define Data Source Type

Implement the [`datasource.DataSource` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSource). Each of the methods is described in more detail below.

In this example, a data source named `examplecloud_thing` with hardcoded behavior is defined:

```go
// Ensure the implementation satisfies the desired interfaces.
var _ datasource.DataSource = &ThingDataSource{}

type ThingDataSource struct {}

type ThingDataSourceModel struct {
ExampleAttribute types.String `tfsdk:"example_attribute"`
ID types.String `tfsdk:"id"`
}

func (d *ThingDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = "examplecloud_thing"
}

func (d *ThingDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"example_attribute": schema.StringAttribute{
Required: true,
},
"id": schema.StringAttribute{
Computed: true,
},
},
}
}

func (d *ThingDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data ThingDataSourceModel

// Read Terraform configuration data into the model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)

// Typically data sources will make external calls, however this example
// hardcodes setting the id attribute to a specific value for brevity.
data.ID = types.StringValue("example-id")

// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
```

### Metadata Method

The [`datasource.DataSource` interface `Metadata` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSource.Metadata) defines the data source name as it would appear in Terraform configurations. This name should include the provider type prefix, an underscore, then the data source specific name. For example, a provider named `examplecloud` and a data source that reads "thing" resources would be named `examplecloud_thing`. Ensure the [Add Data Source To Provider](#add-data-source-to-provider) documentation is followed so the data source becomes part of the provider implementation, and therefore available to practitioners.

In this example, the data source name in an `examplecloud` provider that reads "thing" resources is hardcoded to `examplecloud_thing`:

```go
// With the datasource.DataSource implementation
func (d *ThingDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = "examplecloud_thing"
}
```

To simplify data source implementations, the [`provider.MetadataResponse.TypeName` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#MetadataResponse.TypeName) from the [`provider.Provider` interface `Metadata` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Metadata) can set the provider name so it is available in the [`datasource.MetadataRequest.ProviderTypeName` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#MetadataRequest.ProviderTypeName).

In this example, the provider defines the `examplecloud` name for itself, and the data source 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 datasource.DataSource implementation
func (d *ThingDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_thing"
}
```

### Schema Method

The [`datasource.DataSource` interface `Schema` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSource.Schema) defines a [schema](/terraform/plugin/framework/schemas) describing what data is available in the data source's configuration and state.

### Read Method

The [`datasource.DataSource` interface `Read` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSource.Read) defines how the data source updates Terraform's state to reflect the retrieved data. There is no plan or prior state to work with in `Read` requests, only configuration.

During the [`terraform apply`](/terraform/cli/commands/apply), [`terraform plan`](/terraform/cli/commands/plan), and [`terraform refresh`](/terraform/cli/commands/refresh) commands, Terraform calls the provider [`ReadDataSource`](/terraform/plugin/framework/internals/rpcs#readdatasource-rpc) RPC, in which the framework calls the [`datasource.DataSource` interface `Read` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSource.Read).

Implement the `Read` method by:

1. [Accessing configuration data](/terraform/plugin/framework/accessing-values) from the [`datasource.ReadRequest.Config` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#ReadRequest.Config).
1. Retriving any additional data, such as remote system information.
1. [Writing state data](/terraform/plugin/framework/writing-state) into the [`datasource.ReadResponse.State` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#ReadResponse.State).

If the logic needs to return [warning or error diagnostics](/terraform/plugin/framework/diagnostics), they can added into the [`datasource.ReadResponse.Diagnostics` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#ReadResponse.Diagnostics).

## Add Data Source to Provider

Data sources become available to practitioners when they are included in the [provider](/terraform/plugin/framework/providers) implementation via the [`provider.ProviderWithDataSources` interface `DataSources` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ProviderWithDataSources.DataSources).

In this example, the `ThingDataSource` type, which implements the `datasource.DataSource` interface, is added to the provider implementation:

```go
// With the provider.Provider implementation
func (p *ExampleCloudProvider) DataSources(_ context.Context) []func() datasource.DataSource {
return []func() datasource.DataSource{
func() datasource.DataSource {
return &ThingDataSource{},
},
}
}
```

To simplify provider implementations, a named function can be created with the data source implementation.

In this example, the `ThingDataSource` code includes an additional `NewThingDataSource` function, which simplifies the provider implementation:

```go
// With the provider.Provider implementation
func (p *ExampleCloudProvider) DataSources(_ context.Context) []func() datasource.DataSource {
return []func() datasource.DataSource{
NewThingDataSource,
}
}

// With the datasource.DataSource implementation
func NewThingDataSource() datasource.DataSource {
return &ThingDataSource{}
}
```
Loading
Loading