Skip to content

Commit

Permalink
doc: standardize use of the id attribute design decision (#40627)
Browse files Browse the repository at this point in the history
* doc: standardize use of the id attribute design decision

This is a public-facing copy of an internal RFC which has already been approved by maintainers of the AWS provider. The goal of publishing this is to share the research and tradeoffs behind the decision, and document best practices going forward.

* docs: apply code review feedback

Co-authored-by: Kit Ewbank <[email protected]>

---------

Co-authored-by: Kit Ewbank <[email protected]>
  • Loading branch information
jar-b and ewbankkit authored Jan 2, 2025
1 parent 30a677f commit 0ec666d
Show file tree
Hide file tree
Showing 2 changed files with 165 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/design-decision-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ as much as possible to allow for external feedback from the community and core c
| [SecretsManager Secret Target Attachment](./design-decisions/secretsmanager-secret-target-attachment.md) | Assess the feasibility of replicating the `AWS::SecretsManager::SecretTargetAttachment` CloudFormation function with Terraform. | [#9183](https://github.com/hashicorp/terraform-provider-aws/issues/9183) |
| [RDS Blue Green Deployments](./design-decisions/rds-bluegreen-deployments.md) | Assess the feasibility extending blue green deployment functionality found in `aws_rds_instance` to `aws_rds_cluster`. | [#28956](https://github.com/hashicorp/terraform-provider-aws/issues/28956) |
| [Exclusive Relationship Management Resources](./design-decisions/exclusive-relationship-management-resources.md)| A proposal describing the use case for "exclusive relationship management" resources and their function within the Terraform AWS provider. | [#39203](https://github.com/hashicorp/terraform-provider-aws/pull/39203) |
| [Standardize Use of the `id` Attribute](./design-decisions/standardize-use-of-the-id-attribute.md)| Define a standard for use of the "id" attribute given improvements to provider development and testing libraries have removed its requirement. | [#37628](https://github.com/hashicorp/terraform-provider-aws/pull/37628) |
164 changes: 164 additions & 0 deletions docs/design-decisions/standardize-use-of-the-id-attribute.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# Standardize Use of the `id` Attribute

**Summary:** Define a standard for use of the `id` attribute given improvements to provider development and testing libraries have removed its requirement.
**Created**: 2024-05-24
**Author**: [@jar-b](https://github.com/jar-b)

---

Historically all resources in the Terraform AWS provider have included a read-only `id` attribute, as [Terraform Plugin SDK V2](https://developer.hashicorp.com/terraform/plugin/sdkv2) and its associated acceptance testing library requires it. In most cases, this attribute corresponds to a unique identifier generated by AWS during resource creation. However, for some resources the identifier is a value provided by the user and the resulting `id` attribute inherently duplicates the value of some other required argument.

With the general availability of [Terraform Plugin Framework](https://developer.hashicorp.com/terraform/plugin/framework), the separation of the provider testing functionality into its own standalone library ([`terraform-plugin-testing`](https://github.com/hashicorp/terraform-plugin-testing)), and some [corresponding enhancements](https://github.com/hashicorp/terraform-plugin-testing/pull/164) made to this library, resources are no longer required to have an `id` attribute. This leaves an opportunity for provider developers to define a standard for when to use an `id` attribute. Broadly, the two options are:

- Continue to require an `id` attribute in all resources for consistency.
- Omit the `id` attribute when it is redundant with some other required argument(s).

This RFC will propose a standard for the Terraform AWS provider. A secondary goal is to gain feedback from other HashiCorp-owned provider teams to determine if a common standard can be shared across the provider ecosystem.

## Background

Prior to general availability of the Terraform Plugin Framework, all providers were built with the Terraform Plugin SDK. As the Terraform Plugin SDK was developed with pre-v1.0 versions of Terraform, an internal core implementation detail requiring the presence of an `id` attribute was incorporated into the library design. This requirement was accordingly passed along to provider developers in the form of an implicit requirement that all resources have an `id` attribute.

This convention is most noticeable in the method for setting `id` attributes (`d.SetId(“value”)`, versus `d.Set(“field”, “value”)`), the special syntax for removing objects from state during a read operation when deleted outside of Terraform (`d.SetId(“”)`), and the acceptance testing helpers for `import`, which rely on a populated `id` attribute to complete the import command and verify the results.

The corresponding method for removing Terraform Plugin Framework-based resources from state during read ([`State.RemoveResource`](https://pkg.go.dev/github.com/hashicorp/[email protected]/tfsdk#State.RemoveResource)) includes no mention of an ID. In version [`1.5.0`](https://github.com/hashicorp/terraform-plugin-testing/releases/tag/v1.5.0) of `terraform-plugin-testing`, [additional fields were added](https://github.com/hashicorp/terraform-plugin-testing/pull/164) to the `TestStep` struct to enable testing imports for resources that do not implement an `id` attribute. With these advancements, providers using Terraform Plugin Framework, either exclusively or via a [muxed](https://developer.hashicorp.com/terraform/plugin/framework/migrating/mux) provider configuration, and `terraform-plugin-testing` in place of the legacy testing packages embedded in Terraform Plugin SDK V2 are no longer bound to the requirement of including an `id` attribute in every resource.

From a Terraform core perspective, releases after 0.12 no longer treat `id` as a special attribute. Terraform 0.11 and earlier implicitly required it. Due to the linkage between Terraform 0.12 and protocol version 5, and the fact that supported provider SDKs today only speak protocol version 5 (and 6), the primary Hashicorp-owned providers, like `hashicorp/aws`, inherently only support Terraform 0.12 and later. Therefore it is safe to remove the `id` attribute since there is no concern for a Terraform version that requires it.

### `id` Attributes in the AWS Provider

In the AWS provider, the `id` attribute most commonly corresponds to an identifier generated by AWS during creation of the resource. The Create API typically returns this value as a field named `Id` or, in some cases, `Id` with a prefixed with a resource name (eg. `InstanceId`). When a resource can be referenced by a unique, remotely generated identifier, storing this value in a computed `id` is a straightforward choice and the meaning of the attribute is easily understood by the practitioner.

However, there are cases where AWS does not generate a unique identifier during resource creation, and instead one or more of the arguments supplied to the Create API functions as the identifier. For single value identifiers this is commonly a field like `name`. For “relationship” resources (ie. resources creating a link between two other resources, such as an [IAM policy attachment)](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment), the unique identifier may be a “multi-part key” made up of identifiers from both resources being linked. Historically, multi-part keys [have not used a consistent delimiter](https://github.com/hashicorp/terraform-provider-aws/issues/27843), further complicating the user experience for practitioners.

In these situations, the AWS provider copied the identifying value (or a delimited string of values for multi-part keys) to the `id` attribute. This approach leaves ambiguity for practitioners regarding which attribute should be used if the exported value is referenced somewhere else in the configuration.

## Proposal

**Going forward, all net-new resources for which an `id` attribute would be redundant with an existing argument(s) will omit this attribute.** In all other cases the `id` attribute should continue to be used as it has been historically. When an `id` is omitted and the unique identifier is a combination of arguments, these should always be delimited with a comma (`,`), and rely on the internal [`ExpandResourceID`](https://github.com/hashicorp/terraform-provider-aws/blob/v5.51.1/internal/flex/flex.go#L326-L354) function to handle splitting values within the import method.

The added clarity of removing redundant attribute values will directly benefit practitioners, especially for resources with complex multi-part keys.

The trade-offs being made with this decision are:

- A departure from a historical precedent where all resources include an `id` attribute, which some practitioners may have come to expect and rely on.
- A minor UI difference in how resources without an ID are rendered during `plan` and `apply` operations. Specifically, when no `id` attribute is present the logged line for this resource will omit this extra piece of information.

Team discussion around this topic consistently landed on a consensus that the clarity gained by having only a single attribute containing the identifier value outweighs the departure from historical conventions.

It should also be noted that this standard is enabled by an existing policy [requiring all net-new resources be implemented with Terraform Plugin Framework](https://github.com/hashicorp/terraform-provider-aws/issues/32917), and that the AWS provider has already migrated to the standalone `terraform-plugin-testing` library.

### Import Methods and Testing

For resources omitting an `id` argument, minor changes are required to customize the import method and acceptance test the import operation. For testing specifically, the import verification `TestStep` will now require the `ImportStateVerifyIdentifierAttribute` and one of `ImportStateID` or `ImportStateIdFunc` be configured. Examples for single non-id identifier and a multi-part key are included below.

#### Single Value

`ImportState` method:

```go
func (r *resourceEndpointPrivateDNS) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("vpc_endpoint_id"), req, resp)
}
```

`TestStep`:

```go
{
ResourceName: resourceName,
ImportState: true,
ImportStateIdFunc: testAccVPCEndpointPrivateDNSImportStateIdFunc(resourceName),
ImportStateVerify: true,
ImportStateVerifyIdentifierAttribute: "vpc_endpoint_id",
},
```

`ImportStateIdFunc`:

```go
func testAccVPCEndpointPrivateDNSImportStateIdFunc(resourceName string) resource.ImportStateIdFunc {
return func(s *terraform.State) (string, error) {
rs, ok := s.RootModule().Resources[resourceName]
if !ok {
return "", fmt.Errorf("Not found: %s", resourceName)
}

return rs.Primary.Attributes["vpc_endpoint_id"], nil
}
}
```

#### Multi-part Value

`ImportState` Method:

```go
func (r *resourceRuntimeManagementConfig) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
parts, err := intflex.ExpandResourceId(req.ID, runtimeManagementConfigIDParts, true)
if err != nil {
resp.Diagnostics.AddError(
"Unexpected Import Identifier",
fmt.Sprintf("Expected import identifier with format: function_name,qualifier. Got: %q", req.ID),
)
return
}

resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("function_name"), parts[0])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("qualifier"), parts[1])...)
}
```

`TestStep`:

```go
{
ResourceName: resourceName,
ImportState: true,
ImportStateIdFunc: testAccRuntimeManagementConfigImportStateIdFunc(resourceName),
ImportStateVerify: true,
ImportStateVerifyIdentifierAttribute: "function_name",
},
```

`ImportStateIdFunc`:

```go
func testAccRuntimeManagementConfigImportStateIdFunc(resourceName string) resource.ImportStateIdFunc {
return func(s *terraform.State) (string, error) {
rs, ok := s.RootModule().Resources[resourceName]
if !ok {
return "", fmt.Errorf("Not found: %s", resourceName)
}

return fmt.Sprintf("%s,%s", rs.Primary.Attributes["function_name"], rs.Primary.Attributes["qualifier"]), nil
}
}
```

## Abandoned Ideas

The alternative to omitting `id` attributes when possible is to continue requiring all resources to include an `id` attribute. While this approach provides consistency with the historical design of the provider, the drawbacks of redundant attribute values and complex (and often inconsistently delimited) multi-part identifiers remain. Consensus among the team has been that the benefits of clarity outweigh the departure from precedent in this case.

## References

Terraform Plugin Framework

- [https://developer.hashicorp.com/terraform/plugin/framework/acctests\#no-id-found-in-attributes](https://developer.hashicorp.com/terraform/plugin/framework/acctests#no-id-found-in-attributes)

Terraform Plugin Testing

- [https://github.com/hashicorp/terraform-plugin-testing/issues/84](https://github.com/hashicorp/terraform-plugin-testing/issues/84)
- [https://github.com/hashicorp/terraform-plugin-testing/pull/164](https://github.com/hashicorp/terraform-plugin-testing/pull/164)

Terraform AWS Provider

- Prototype resources implemented without an `id` attribute
- `aws_vpc_endpoint_private_dns`
- The identifier is a single value, `vpc_endpoint_id`
- [PR](https://github.com/hashicorp/terraform-provider-aws/pull/37628)
- [Registry documentation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_endpoint_private_dns)
- `aws_lambda_runtime_management_config`
- The identifier is a multi-part key, made up of the required `function_name` and optional `qualifier` arguments
- [PR](https://github.com/hashicorp/terraform-provider-aws/pull/37643)
- [Registry documentation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_runtime_management_config)

0 comments on commit 0ec666d

Please sign in to comment.