hashicorp / hashicorp/terraform-plugin-framework
Consider exposing Resource "add-on" interfaces as stand-alone interfaces
- Dominant language
- Go
- Stars
- 384
- Forks
- 107
- Avg merge
- 3m
- Merged PRs (30d)
- 1
Description
### Use-cases
In addition to the core [`resource.Resource` interface](https://github.com/hashicorp/terraform-plugin-framework/blob/8a7303c9a6ca60b8a7282848c910b2024c762eac/resource/resource.go#L28), the Plugin Framework defines many "add-on" interfaces to extend functionality, such as
* [`resource.ResourceWithConfigure`](https://github.com/hashicorp/terraform-plugin-framework/blob/8a7303c9a6ca60b8a7282848c910b2024c762eac/resource/resource.go#L67C6-L67C27)
* [`resource.ResourceWithImportState`](https://github.com/hashicorp/terraform-plugin-framework/blob/8a7303c9a6ca60b8a7282848c910b2024c762eac/resource/resource.go#L95C6-L95C29)
* [`resource.ResourceWithIdentity`](https://github.com/hashicorp/terraform-plugin-framework/blob/8a7303c9a6ca60b8a7282848c910b2024c762eac/resource/resource.go#L220C6-L220C26)
In all cases, these "add-on" interfaces follow the pattern
```go
type ResourceWithSomethingExtra interface {
Resource
DoSomethingExtra(context.Context, SomethingExtraRequest, *SomethingExtraResponse)
}
```
Because these interfaces include `resource.Resource`, the interface types exported by the `resource` package cannot be used directly to create composable types or in testing.
For example, the AWS provider uses various embedded composable types for resource functionality, e.g. the resource type `aws_apprunner_default_auto_scaling_configuration_version` is defined as https://github.com/hashicorp/terraform-provider-aws/blob/4e1c9eee98e542dd211fef196e0017799eaf02ef/internal/service/apprunner/default_auto_scaling_configuration_version.go#L31-L35
```go
type defaultAutoScalingConfigurationVersionResource struct {
framework.ResourceWithConfigure
framework.WithNoOpDelete
framework.WithImportByID
}
```
Where `framework.WithImportByID` is defined as https://github.com/hashicorp/terraform-provider-aws/blob/4e1c9eee98e542dd211fef196e0017799eaf02ef/internal/framework/with_import_by_id.go#L16
```go
type WithImportByID struct{}
func (w *WithImportByID) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root(names.AttrID), request, response)
}
```
As `framework.WithImportByID` is intended for embedding in other types, it only implements the `ImportState` function from `resource.ResourceWithImportState`.
When referencing full resource type implementations, such as determining if a resource type supports importing, checking if it implements `resource.ResourceWithImportState` works. However, when testing or otherwise referencing the embeddable struct, there is no way to validate that the interface is implemented without defining our own interface, such as
```go
type importStater interface {
ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse)
}
```
### Proposal
Consider exporting additional interfaces that define only the "add-on" behaviour and composing the existing "add-on" interfaces from those. For example
```go
type WithImportState interface {
// ImportState is called when the provider must import the state of a
// resource instance. This method must return enough state so the Read
// method can properly refresh the full resource.
//
// If setting an attribute with the import identifier, it is recommended
// to use the ImportStatePassthroughID() call in this method.
/ImportState(context.Context, ImportStateRequest, *ImportStateResponse)
}
type ResourceWithImportState interface {
Resource
WithImportState
}
```
Contributor guide
Assessment
This issue has not been assessed yet.