May 25, 2023·6 min read·engineeringterraformawsprovidersupgrades

AWS provider 5.0 changes database identity and tag planning

AWS provider 5.0 changes the meaning of RDS instance IDs, repairs default-tag behavior, and removes deprecated configuration forms.

An attribute can keep its name while changing what its value identifies. In AWS provider 5.0, aws_db_instance.id stops representing the database identifier and becomes the DBI resource ID. Existing references can therefore remain valid Terraform expressions while supplying the wrong kind of identifier to another resource. The May 25 release includes many removals, but this change requires attention beyond configuration syntax.

The same release improves default-tag handling and allows an RDS instance identifier to change without requiring destruction and recreation. Those changes concern how the provider translates AWS behavior into Terraform's resource model. The major-version boundary gives the maintainers room to change that translation where preserving the old behavior would preserve defects or outdated assumptions.

I value Terraform because a reusable language can describe infrastructure across APIs. Providers make that possible, and their schema decisions determine what the language actually means. A provider upgrade deserves review at the references between resources, where a syntactically ordinary string can carry an API-specific identity.

RDS identity is no longer the display identifier

The version-pinned changelog states that aws_db_instance.id now holds dbi-resource-id. Code that needs the database's identifier must refer to identifier. This is independent of the removal of the old name argument, whose replacement is db_name. The database name, instance identifier, and provider resource ID have separate roles.

The changelog names several consumers affected by the ID change. Event subscriptions using database instance IDs in source_ids must use identifier. DB instance role associations and proxy targets need the same adjustment in db_instance_identifier. References used by snapshots and replication configuration also require attention. A module that exports the old id value as a general-purpose database identifier can pass the changed meaning to callers without changing its output declaration.

For an existing database resource named example, an event subscription's relevant expression becomes the following. This is an attribute fragment for that subscription, whose other settings are omitted.

source_ids = [aws_db_instance.example.identifier]

The correction is about selecting the attribute expected by the receiving API. Replacing every use of id indiscriminately would lose the distinction in the other direction. A consumer that needs the DBI resource ID should use the new value. Each reference needs interpretation according to what the downstream argument accepts.

The RDS resource documentation at 5.0.0 separately documents importing by the instance identifier. The identifier used by an import command need not have the same meaning as the resource's exported id. That difference makes assumptions based only on the attribute name unreliable during an upgrade.

Changes to identifier and identifier_prefix no longer force destruction and recreation of a database instance. That is a substantial improvement in lifecycle modeling. An API-supported identity change can use an update path, while configuration that needs the human-selected identifier now names it explicitly. Together the changes reduce the dependence of Terraform resource identity on that editable string.

Default tags need to survive planning

HashiCorp's release announcement describes fixes for computed tags, overlap between provider defaults and resource tags, and persistent tag differences. Provider-level defaults apply common metadata to supported resources. The difficulty is merging defaults with resource-specific values while preserving Terraform's distinction between planned and computed results.

The changelog gives more precise behavior. Computed resource tags are now allowed. Both default tags and resource tags can contain empty-string values. Duplicate default tags are allowed, and resource tags overwrite them. These are useful semantics for a configuration in which a provider establishes common values and individual resources specialize them.

This example supplies provider defaults and overrides one key on a bucket. The bucket name is illustrative and must be available for an actual deployment.

provider "aws" {
  region = "us-east-1"

  default_tags {
    tags = {
      environment = "development"
      owner       = "platform"
    }
  }
}

resource "aws_s3_bucket" "example" {
  bucket = "replace-with-an-available-bucket-name"

  tags = {
    environment = "production"
  }
}

The overlapping environment key uses the resource value. Provider configuration supplies the common owner value. The important behavior is a defined merge, including the case where a resource repeats a default value. Configuration authors should not have to remove a resource tag solely because a provider default happens to contain the same pair.

Computed tags add a separate requirement. Their final values may be unavailable when Terraform first constructs a plan. A provider must preserve enough information to reconcile those values during apply without producing an inconsistent result. The release's tag changes address the provider's handling of that process. They do not make an expression known before its dependencies produce a value.

I prefer common metadata in provider configuration when that configuration states an actual shared policy. The override behavior still matters because a reusable module can legitimately need a resource-specific value. A default and an enforced invariant are different configuration choices, and overwrite semantics make this distinction explicit.

Removed arguments have different replacements

Many changes remove deprecated names, but the replacements vary. aws_iam_policy_document removes source_json and override_json in favor of source_policy_documents and override_policy_documents. The change uses document collections, so updating an argument requires checking its expected value shape. aws_autoscaling_attachment replaces alb_target_group_arn with lb_target_group_arn.

ElastiCache replication groups remove number_cache_clusters and replication_group_description in favor of num_cache_clusters and description. Their cluster_mode block also disappears. Its node-group settings move to top-level num_node_groups and replicas_per_node_group. A configuration rewrite therefore needs to account for nesting as well as spelling.

Routes no longer accept instance_id in configuration. They use network_interface_id, with the changelog suggesting an instance's primary_network_interface_id as the source. This changes the reference to the object expected by the route configuration. It is another case where keeping the value as a string is insufficient to establish compatibility.

The provider also removes EC2-Classic functionality across several resource types. The changelog connects those removals to retirement of the AWS service functionality. Keeping an old Terraform schema indefinitely would not keep the corresponding remote capability available. The release removes obsolete arguments and whole resource types where they represented that retired model.

Other changes correct replacement behavior. For DocumentDB, Neptune, and RDS clusters, changing snapshot_identifier now requires replacement. The changelog explains that the previous in-place behavior could report success without actually restoring the cluster from the designated snapshot. Correcting that behavior can produce a more disruptive plan, but the older successful apply did not perform the requested operation.

Defaults can issue unwanted API settings

Some fixes remove an implicit setting without replacing it with another default. The S3 object and object-copy resources remove their ACL defaults so they can work with buckets that have ACLs disabled. The old default could make the provider request behavior that the bucket did not support, even when configuration had not explicitly asked for that behavior.

Launch templates also lose default values in metadata_options, which the changelog describes as allowing the default condition. These changes show why an omitted Terraform argument and an explicitly chosen provider default are operationally different. A provider that fills in a value takes responsibility for that choice instead of leaving the service's own behavior in effect.

RDS clusters and cluster instances take a different approach for engine. That argument becomes required and loses its default. Here the provider requires the configuration author to choose. The major release therefore does not apply a uniform rule that all omitted settings should defer to AWS. Each schema change specifies whether the value should be explicit, service-selected, or computed from the result.

Provider selection is a separate dependency decision

AWS provider 5.0 is a provider release. Its version is separate from Terraform CLI's version. Terraform's dependency-lock documentation explains how constraints determine eligible provider versions and the lock file records the selected version. An existing selection is normally reused. terraform init -upgrade instead selects the newest eligible version.

A root configuration deliberately accepting the 5.0 minor series can express that constraint explicitly. The following requirement allows patch releases within that series while excluding 5.1.

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0.0"
    }
  }
}

The constraint expresses compatibility policy, while the lock file records the concrete package selection and its checksums. They answer different questions. Changing the requirement makes new versions eligible, and changing the selection chooses which implementation interprets the resource schemas. A major-version upgrade therefore includes both configuration compatibility and the dependency decision that activates it.

The useful review unit is the module contract as well as the resource block. An output that exposes an RDS id, a route that consumes an instance reference, or a wrapper around replication-group settings can transmit provider assumptions into otherwise unchanged code. In 5.0, those contracts need to name the database identifier or network interface where that is what the receiving API requires. The resulting configuration describes the expected AWS object directly.