Skip to content

Terraform AWS Provider Version 3 Upgrade Guide

Version 3.0.0 of the AWS provider for Terraform is a major release and includes some changes that you will need to consider when upgrading. This guide is intended to help with that process and focuses only on changes from version 2.X to version 3.0.0. See the Version 2 Upgrade Guide for information about upgrading from 1.X to version 2.0.0.

Most of the changes outlined in this guide have been previously marked as deprecated in the Terraform plan/apply output throughout previous provider releases. These changes, such as deprecation notices, can always be found in the Terraform AWS Provider CHANGELOG.

\~> NOTE: Version 3.0.0 and later of the AWS Provider can only be automatically installed on Terraform 0.12 and later.

Upgrade topics:

Provider Version Configuration

-> Before upgrading to version 3.0.0, it is recommended to upgrade to the most recent 2.X version of the provider and ensure that your environment successfully runs terraformPlan without unexpected changes or deprecation notices.

We recommend using version constraints when configuring Terraform providers. If you are following that recommendation, update the version constraints in your Terraform configuration and run terraformInit to download the new version.

For example, given this previous configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.provider.AwsProvider(this, "aws", {});

Update to latest 3.X version:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.provider.AwsProvider(this, "aws", {});

Provider Authentication Updates

Authentication Ordering

Previously, the provider preferred credentials in the following order:

  • Static credentials (those defined in the Terraform configuration)
  • Environment variables (e.g., awsAccessKeyId or awsProfile)
  • Shared credentials file (e.g., ~/Aws/credentials)
  • EC2 Instance Metadata Service
  • Default AWS Go SDK handling (shared configuration, CodeBuild/ECS/EKS)

The provider now prefers the following credential ordering:

  • Static credentials (those defined in the Terraform configuration)
  • Environment variables (e.g., awsAccessKeyId or awsProfile)
  • Shared credentials and/or configuration file (e.g., ~/Aws/credentials and ~/Aws/config)
  • Default AWS Go SDK handling (shared configuration, CodeBuild/ECS/EKS, EC2 Instance Metadata Service)

This means workarounds of disabling the EC2 Instance Metadata Service handling to enable CodeBuild/ECS/EKS credentials or to enable other credential methods such as credentialProcess in the AWS shared configuration are no longer necessary.

Shared Configuration File Automatically Enabled

The awsSdkLoadConfig environment variable is no longer necessary for the provider to automatically load the AWS shared configuration file (e.g., ~/Aws/config).

Removal of AWS_METADATA_TIMEOUT Environment Variable Usage

The provider now relies on the default AWS Go SDK timeouts for interacting with the EC2 Instance Metadata Service.

Provider Custom Service Endpoint Updates

Removal of kinesis_analytics and r53 Arguments

The custom service endpoints for Kinesis Analytics and Route 53 now use the kinesisanalytics and route53 argument names in the provider configuration.

For example, given this previous configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.provider.AwsProvider(this, "aws", {
  endpoints: [
    {
      kinesisAnalytics: "https://example.com",
      r53: "https://example.com",
    },
  ],
});

An updated configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.provider.AwsProvider(this, "aws", {
  endpoints: [
    {
      kinesisanalytics: "https://example.com",
      route53: "https://example.com",
    },
  ],
});

Data Source: aws_availability_zones

blacklisted_names Attribute Removal

Switch your Terraform configuration to the excludeNames attribute instead.

For example, given this previous configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.dataAwsAvailabilityZones.DataAwsAvailabilityZones(this, "example", {
  blacklisted_names: ["us-west-2d"],
});

An updated configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.dataAwsAvailabilityZones.DataAwsAvailabilityZones(this, "example", {
  excludeNames: ["us-west-2d"],
});

blacklisted_zone_ids Attribute Removal

Switch your Terraform configuration to the excludeZoneIds attribute instead.

For example, given this previous configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.dataAwsAvailabilityZones.DataAwsAvailabilityZones(this, "example", {
  blacklisted_zone_ids: ["usw2-az4"],
});

An updated configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.dataAwsAvailabilityZones.DataAwsAvailabilityZones(this, "example", {
  excludeZoneIds: ["usw2-az4"],
});

Data Source: aws_lambda_invocation

result_map Attribute Removal

Switch your Terraform configuration to the result attribute with the jsondecode() function instead.

For example, given this previous configuration:

import * as cdktf from "cdktf";
new cdktf.TerraformOutput(this, "lambda_result", {
  value: '${data.aws_lambda_invocation.example.result_map["key1"]}',
});

An updated configuration:

import * as cdktf from "cdktf";
new cdktf.TerraformOutput(this, "lambda_result", {
  value: '${jsondecode(data.aws_lambda_invocation.example.result)["key1"]}',
});

Data Source: aws_launch_template

Error raised if no matching launch template is found

Previously, when a launch template matching the criteria was not found the data source would have been null. Now this could produce errors similar to the below:

data.aws_launch_template.current: Refreshing state...

Error: error reading launch template: empty output

Configuration that depend on the previous behavior will need to be updated.

Data Source: aws_route53_resolver_rule

Removal of trailing period in domain_name argument

Previously the data-source returned the Resolver Rule Domain Name directly from the API, which included a . suffix. This proves difficult when many other AWS services do not accept this trailing period (e.g., ACM Certificate). This period is now automatically removed. For example, when the attribute would previously return a Resolver Rule Domain Name such as exampleCom, the attribute now will be returned as exampleCom. While the returned value will omit the trailing period, use of configurations with trailing periods will not be interrupted.

Data Source: aws_route53_zone

Removal of trailing period in name argument

Previously the data-source returned the Hosted Zone Domain Name directly from the API, which included a . suffix. This proves difficult when many other AWS services do not accept this trailing period (e.g., ACM Certificate). This period is now automatically removed. For example, when the attribute would previously return a Hosted Zone Domain Name such as exampleCom, the attribute now will be returned as exampleCom. While the returned value will omit the trailing period, use of configurations with trailing periods will not be interrupted.

Resource: aws_acm_certificate

domain_validation_options Changed from List to Set

Previously, the domainValidationOptions attribute was a list type and completely unknown until after an initial terraformApply. This generally required complicated configuration workarounds to properly create DNS validation records since referencing this attribute directly could produce errors similar to the below:

Error: Invalid for_each argument

  on main.tf line 16, in resource "aws_route53_record" "existing":
  16:   for_each = aws_acm_certificate.existing.domain_validation_options

The `for_each` value depends on resource attributes that cannot be determined
until apply, so Terraform cannot predict how many instances will be created.
To work around this, use the -target argument to first apply only the
resources that the for_each depends on.

The domainValidationOptions attribute is now a set type and the resource will attempt to populate the information necessary during the planning phase to handle the above situation in most environments without workarounds. This change also prevents Terraform from showing unexpected differences if the API returns the results in varying order.

Configuration references to this attribute will likely require updates since sets cannot be indexed (e.g., domainValidationOptions[0] or the older domainValidationOptions0 syntax will return errors). If the domainValidationOptions list previously contained only a single element like the two examples just shown, it may be possible to wrap these references using the tolist() function

(e.g., tolist(awsAcmCertificateExampleDomainValidationOptions)[0]) as a quick configuration update. However given the complexity and workarounds required with the previous domainValidationOptions attribute implementation, different environments will require different configuration updates and migration steps. Below is a more advanced example. Further questions on potential update steps can be submitted to the community forums.

For example, given this previous configuration using a count based resource approach that may have been used in certain environments:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
const awsAcmCertificateExisting = new aws.acmCertificate.AcmCertificate(
  this,
  "existing",
  {
    domainName: "existing.${var.public_root_domain}",
    subjectAlternativeNames: [
      "existing1.${var.public_root_domain}",
      "existing2.${var.public_root_domain}",
      "existing3.${var.public_root_domain}",
    ],
    validationMethod: "DNS",
  }
);
const dataAwsRoute53ZonePublicRootDomain =
  new aws.dataAwsRoute53Zone.DataAwsRoute53Zone(this, "public_root_domain", {
    name: "${var.public_root_domain}",
  });
const awsRoute53RecordExisting = new aws.route53Record.Route53Record(
  this,
  "existing_2",
  {
    allowOverwrite: true,
    name: `\${${awsAcmCertificateExisting.domainValidationOptions.fqn}[count.index].resource_record_name}`,
    records: [
      `\${${awsAcmCertificateExisting.domainValidationOptions.fqn}[count.index].resource_record_value}`,
    ],
    ttl: 60,
    type: `\${${awsAcmCertificateExisting.domainValidationOptions.fqn}[count.index].resource_record_type}`,
    zoneId: dataAwsRoute53ZonePublicRootDomain.zoneId,
  }
);
/*This allows the Terraform resource name to match the original name. You can remove the call if you don't need them to match.*/
awsRoute53RecordExisting.overrideLogicalId("existing");
/*In most cases loops should be handled in the programming language context and 
not inside of the Terraform context. If you are looping over something external, e.g. a variable or a file input
you should consider using a for loop. If you are looping over something only known to Terraform, e.g. a result of a data source
you need to keep this like it is.*/
awsRoute53RecordExisting.addOverride(
  "count",
  `\${length(${awsAcmCertificateExisting.subjectAlternativeNames}) + 1}`
);
const awsAcmCertificateValidationExisting =
  new aws.acmCertificateValidation.AcmCertificateValidation(
    this,
    "existing_3",
    {
      certificateArn: awsAcmCertificateExisting.arn,
      validationRecordFqdns: `\${${awsRoute53RecordExisting.fqn}[*].fqdn}`,
    }
  );
/*This allows the Terraform resource name to match the original name. You can remove the call if you don't need them to match.*/
awsAcmCertificateValidationExisting.overrideLogicalId("existing");

It will receive errors like the below after upgrading:

Error: Invalid index

  on main.tf line 14, in resource "aws_route53_record" "existing":
  14:   name    = aws_acm_certificate.existing.domain_validation_options[count.index].resource_record_name
    |----------------
    | aws_acm_certificate.existing.domain_validation_options is set of object with 4 elements
    | count.index is 1

This value does not have any indices.

Since the domainValidationOptions attribute changed from a list to a set and sets cannot be indexed in Terraform, the recommendation is to update the configuration to use the more stable resource forEach support instead of count. Note the slight change in the validationRecordFqdns syntax as well.

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
const awsRoute53RecordExisting = new aws.route53Record.Route53Record(
  this,
  "existing",
  {
    allowOverwrite: true,
    name: "${each.value.name}",
    records: ["${each.value.record}"],
    ttl: 60,
    type: "${each.value.type}",
    zoneId: "${data.aws_route53_zone.public_root_domain.zone_id}",
  }
);
/*In most cases loops should be handled in the programming language context and 
not inside of the Terraform context. If you are looping over something external, e.g. a variable or a file input
you should consider using a for loop. If you are looping over something only known to Terraform, e.g. a result of a data source
you need to keep this like it is.*/
awsRoute53RecordExisting.addOverride(
  "for_each",
  "${{\n    for dvo in aws_acm_certificate.existing.domain_validation_options : dvo.domain_name => {\n      name   = dvo.resource_record_name\n      record = dvo.resource_record_value\n      type   = dvo.resource_record_type\n    }\n  }}"
);
const awsAcmCertificateValidationExisting =
  new aws.acmCertificateValidation.AcmCertificateValidation(
    this,
    "existing_1",
    {
      certificateArn: "${aws_acm_certificate.existing.arn}",
      validationRecordFqdns: [
        `\${[for record in ${awsRoute53RecordExisting.fqn} : record.fqdn]}`,
      ],
    }
  );
/*This allows the Terraform resource name to match the original name. You can remove the call if you don't need them to match.*/
awsAcmCertificateValidationExisting.overrideLogicalId("existing");

After the configuration has been updated, a plan should no longer error and may look like the following:

------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create
  - destroy
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # aws_acm_certificate_validation.existing must be replaced
-/+ resource "aws_acm_certificate_validation" "existing" {
        certificate_arn         = "arn:aws:acm:us-east-2:123456789012:certificate/ccbc58e8-061d-4443-9035-d3af0512e863"
      ~ id                      = "2020-07-16 00:01:19 +0000 UTC" -> (known after apply)
      ~ validation_record_fqdns = [
          - "_40b71647a8d88eb82d53fe988e8a3cc1.existing2.example.com",
          - "_812ddf11b781af1eec1643ec58f102d2.existing.example.com",
          - "_8dc56b6e35f699b8754afcdd79e9748d.existing3.example.com",
          - "_d7112da809a40e848207c04399babcec.existing1.example.com",
        ] -> (known after apply) # forces replacement
    }

  # aws_route53_record.existing will be destroyed
  - resource "aws_route53_record" "existing" {
      - fqdn    = "_812ddf11b781af1eec1643ec58f102d2.existing.example.com" -> null
      - id      = "Z123456789012__812ddf11b781af1eec1643ec58f102d2.existing.example.com._CNAME" -> null
      - name    = "_812ddf11b781af1eec1643ec58f102d2.existing.example.com" -> null
      - records = [
          - "_bdeba72164eec216c55a32374bcceafd.jfrzftwwjs.acm-validations.aws.",
        ] -> null
      - ttl     = 60 -> null
      - type    = "CNAME" -> null
      - zone_id = "Z123456789012" -> null
    }

  # aws_route53_record.existing[1] will be destroyed
  - resource "aws_route53_record" "existing" {
      - fqdn    = "_40b71647a8d88eb82d53fe988e8a3cc1.existing2.example.com" -> null
      - id      = "Z123456789012__40b71647a8d88eb82d53fe988e8a3cc1.existing2.example.com._CNAME" -> null
      - name    = "_40b71647a8d88eb82d53fe988e8a3cc1.existing2.example.com" -> null
      - records = [
          - "_638532db1fa6a1b71aaf063c8ea29d52.jfrzftwwjs.acm-validations.aws.",
        ] -> null
      - ttl     = 60 -> null
      - type    = "CNAME" -> null
      - zone_id = "Z123456789012" -> null
    }

  # aws_route53_record.existing[2] will be destroyed
  - resource "aws_route53_record" "existing" {
      - fqdn    = "_d7112da809a40e848207c04399babcec.existing1.example.com" -> null
      - id      = "Z123456789012__d7112da809a40e848207c04399babcec.existing1.example.com._CNAME" -> null
      - name    = "_d7112da809a40e848207c04399babcec.existing1.example.com" -> null
      - records = [
          - "_6e1da5574ab46a6c782ed73438274181.jfrzftwwjs.acm-validations.aws.",
        ] -> null
      - ttl     = 60 -> null
      - type    = "CNAME" -> null
      - zone_id = "Z123456789012" -> null
    }

  # aws_route53_record.existing[3] will be destroyed
  - resource "aws_route53_record" "existing" {
      - fqdn    = "_8dc56b6e35f699b8754afcdd79e9748d.existing3.example.com" -> null
      - id      = "Z123456789012__8dc56b6e35f699b8754afcdd79e9748d.existing3.example.com._CNAME" -> null
      - name    = "_8dc56b6e35f699b8754afcdd79e9748d.existing3.example.com" -> null
      - records = [
          - "_a419f8410d2e0720528a96c3506f3841.jfrzftwwjs.acm-validations.aws.",
        ] -> null
      - ttl     = 60 -> null
      - type    = "CNAME" -> null
      - zone_id = "Z123456789012" -> null
    }

  # aws_route53_record.existing["existing.example.com"] will be created
  + resource "aws_route53_record" "existing" {
      + allow_overwrite = true
      + fqdn            = (known after apply)
      + id              = (known after apply)
      + name            = "_812ddf11b781af1eec1643ec58f102d2.existing.example.com"
      + records         = [
          + "_bdeba72164eec216c55a32374bcceafd.jfrzftwwjs.acm-validations.aws.",
        ]
      + ttl             = 60
      + type            = "CNAME"
      + zone_id         = "Z123456789012"
    }

  # aws_route53_record.existing["existing1.example.com"] will be created
  + resource "aws_route53_record" "existing" {
      + allow_overwrite = true
      + fqdn            = (known after apply)
      + id              = (known after apply)
      + name            = "_d7112da809a40e848207c04399babcec.existing1.example.com"
      + records         = [
          + "_6e1da5574ab46a6c782ed73438274181.jfrzftwwjs.acm-validations.aws.",
        ]
      + ttl             = 60
      + type            = "CNAME"
      + zone_id         = "Z123456789012"
    }

  # aws_route53_record.existing["existing2.example.com"] will be created
  + resource "aws_route53_record" "existing" {
      + allow_overwrite = true
      + fqdn            = (known after apply)
      + id              = (known after apply)
      + name            = "_40b71647a8d88eb82d53fe988e8a3cc1.existing2.example.com"
      + records         = [
          + "_638532db1fa6a1b71aaf063c8ea29d52.jfrzftwwjs.acm-validations.aws.",
        ]
      + ttl             = 60
      + type            = "CNAME"
      + zone_id         = "Z123456789012"
    }

  # aws_route53_record.existing["existing3.example.com"] will be created
  + resource "aws_route53_record" "existing" {
      + allow_overwrite = true
      + fqdn            = (known after apply)
      + id              = (known after apply)
      + name            = "_8dc56b6e35f699b8754afcdd79e9748d.existing3.example.com"
      + records         = [
          + "_a419f8410d2e0720528a96c3506f3841.jfrzftwwjs.acm-validations.aws.",
        ]
      + ttl             = 60
      + type            = "CNAME"
      + zone_id         = "Z123456789012"
    }

Plan: 5 to add, 0 to change, 5 to destroy.

Due to the type of configuration change, Terraform does not know that the previous awsRoute53Record resources (indexed by number in the existing state) and the new resources (indexed by domain names in the updated configuration) are equivalent. Typically in this situation, the terraformStateMv command can be used to reduce the plan to show no changes. This is done by associating the count index (e.g., [1]) with the equivalent domain name index (e.g., ["existing2ExampleCom"]), making one of the four commands to fix the above example: terraformStateMv 'awsRoute53RecordExisting[1]' 'awsRoute53RecordExisting["existing2ExampleCom"]'. We recommend using this terraformStateMv update process where possible to reduce chances of unexpected behaviors or changes in an environment.

If using terraformStateMv to reduce the plan to show no changes, no additional steps are required.

In larger or more complex environments though, this process can be tedius to match the old resource address to the new resource address and run all the necessary terraformStateMv commands. Instead, since the awsRoute53Record resource implements the allowOverwrite =True argument, it is possible to just remove the old awsRoute53Record resources from the Terraform state using the terraformStateRm command. In this case, Terraform will leave the existing records in Route 53 and plan to just overwrite the existing validation records with the same exact (previous) values.

-> This guide is showing the simpler terraformStateRm option below as a potential shortcut in this specific situation, however in most other cases terraformStateMv is required to change from count based resources to forEach based resources and properly match the existing Terraform state to the updated Terraform configuration.

$ terraform state rm aws_route53_record.existing
Removed aws_route53_record.existing[0]
Removed aws_route53_record.existing[1]
Removed aws_route53_record.existing[2]
Removed aws_route53_record.existing[3]
Successfully removed 4 resource instance(s).

Now the Terraform plan will show only the additions of new Route 53 records (which are exactly the same as before the upgrade) and the proposed recreation of the awsAcmCertificateValidation resource. The awsAcmCertificateValidation resource recreation will have no effect as the certificate is already validated and issued.

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # aws_acm_certificate_validation.existing must be replaced
-/+ resource "aws_acm_certificate_validation" "existing" {
        certificate_arn         = "arn:aws:acm:us-east-2:123456789012:certificate/ccbc58e8-061d-4443-9035-d3af0512e863"
      ~ id                      = "2020-07-16 00:01:19 +0000 UTC" -> (known after apply)
      ~ validation_record_fqdns = [
          - "_40b71647a8d88eb82d53fe988e8a3cc1.existing2.example.com",
          - "_812ddf11b781af1eec1643ec58f102d2.existing.example.com",
          - "_8dc56b6e35f699b8754afcdd79e9748d.existing3.example.com",
          - "_d7112da809a40e848207c04399babcec.existing1.example.com",
        ] -> (known after apply) # forces replacement
    }

  # aws_route53_record.existing["existing.example.com"] will be created
  + resource "aws_route53_record" "existing" {
      + allow_overwrite = true
      + fqdn            = (known after apply)
      + id              = (known after apply)
      + name            = "_812ddf11b781af1eec1643ec58f102d2.existing.example.com"
      + records         = [
          + "_bdeba72164eec216c55a32374bcceafd.jfrzftwwjs.acm-validations.aws.",
        ]
      + ttl             = 60
      + type            = "CNAME"
      + zone_id         = "Z123456789012"
    }

  # aws_route53_record.existing["existing1.example.com"] will be created
  + resource "aws_route53_record" "existing" {
      + allow_overwrite = true
      + fqdn            = (known after apply)
      + id              = (known after apply)
      + name            = "_d7112da809a40e848207c04399babcec.existing1.example.com"
      + records         = [
          + "_6e1da5574ab46a6c782ed73438274181.jfrzftwwjs.acm-validations.aws.",
        ]
      + ttl             = 60
      + type            = "CNAME"
      + zone_id         = "Z123456789012"
    }

  # aws_route53_record.existing["existing2.example.com"] will be created
  + resource "aws_route53_record" "existing" {
      + allow_overwrite = true
      + fqdn            = (known after apply)
      + id              = (known after apply)
      + name            = "_40b71647a8d88eb82d53fe988e8a3cc1.existing2.example.com"
      + records         = [
          + "_638532db1fa6a1b71aaf063c8ea29d52.jfrzftwwjs.acm-validations.aws.",
        ]
      + ttl             = 60
      + type            = "CNAME"
      + zone_id         = "Z123456789012"
    }

  # aws_route53_record.existing["existing3.example.com"] will be created
  + resource "aws_route53_record" "existing" {
      + allow_overwrite = true
      + fqdn            = (known after apply)
      + id              = (known after apply)
      + name            = "_8dc56b6e35f699b8754afcdd79e9748d.existing3.example.com"
      + records         = [
          + "_a419f8410d2e0720528a96c3506f3841.jfrzftwwjs.acm-validations.aws.",
        ]
      + ttl             = 60
      + type            = "CNAME"
      + zone_id         = "Z123456789012"
    }

Plan: 5 to add, 0 to change, 1 to destroy.

Once applied, no differences should be shown and no additional steps should be necessary.

Alternatively, if you are referencing a subset of domainValidationOptions, there is another method of upgrading from v2 to v3 without having to move state. Given the scenario below...

data "aws_route53_zone" "public_root_domain" {
  name = var.public_root_domain
}

resource "aws_acm_certificate" "existing" {
  domain_name = "existing.${var.public_root_domain}"
  subject_alternative_names = [
    "existing1.${var.public_root_domain}",
    "existing2.${var.public_root_domain}",
    "existing3.${var.public_root_domain}",
  ]
  validation_method = "DNS"
}

resource "aws_route53_record" "existing_1" {
  allow_overwrite = true
  name            = aws_acm_certificate.existing.domain_validation_options[0].resource_record_name
  records         = [aws_acm_certificate.existing.domain_validation_options[0].resource_record_value]
  ttl             = 60
  type            = aws_acm_certificate.existing.domain_validation_options[0].resource_record_type
  zone_id         = data.aws_route53_zone.public_root_domain.zone_id
}

resource "aws_acm_certificate_validation" "existing_1" {
  certificate_arn         = aws_acm_certificate.existing.arn
  validation_record_fqdns = aws_route53_record.existing_1.fqdn
}

resource "aws_route53_record" "existing_3" {
  allow_overwrite = true
  name            = aws_acm_certificate.existing.domain_validation_options[2].resource_record_name
  records         = [aws_acm_certificate.existing.domain_validation_options[2].resource_record_value]
  ttl             = 60
  type            = aws_acm_certificate.existing.domain_validation_options[2].resource_record_type
  zone_id         = data.aws_route53_zone.public_root_domain.zone_id
}

resource "aws_acm_certificate_validation" "existing_3" {
  certificate_arn         = aws_acm_certificate.existing.arn
  validation_record_fqdns = aws_route53_record.existing_3.fqdn
}

You can perform a conversion of the new domainValidationOptions object into a map, to allow you to perform a lookup by the domain name in place of an index number.

locals {
  existing_domain_validation_options = {
    for dvo in aws_acm_certificate.cloudfront_cert.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }
}

resource "aws_route53_record" "existing_1" {
  allow_overwrite = true
  name            = local.existing_domain_validation_options["existing1.${var.public_root_domain}"].name
  records         = [local.existing_domain_validation_options["existing1.${var.public_root_domain}"].record]
  ttl             = 60
  type            = local.existing_domain_validation_options["existing1.${var.public_root_domain}"].type
  zone_id         = data.aws_route53_zone.public_root_domain.zone_id
}

resource "aws_acm_certificate_validation" "existing_1" {
  certificate_arn         = aws_acm_certificate.existing.arn
  validation_record_fqdns = aws_route53_record.existing_1.fqdn
}

resource "aws_route53_record" "existing_3" {
  allow_overwrite = true
  name            = local.existing_domain_validation_options["existing3.${var.public_root_domain}"].name
  records         = [local.existing_domain_validation_options["existing3.${var.public_root_domain}"].record]
  ttl             = 60
  type            = local.existing_domain_validation_options["existing3.${var.public_root_domain}"].type
  zone_id         = data.aws_route53_zone.public_root_domain.zone_id
}

resource "aws_acm_certificate_validation" "existing_3" {
  certificate_arn         = aws_acm_certificate.existing.arn
  validation_record_fqdns = aws_route53_record.existing_3.fqdn
}

Performing a plan against these resources will not cause any change in state, since underlying resources have not changed.

subject_alternative_names Changed from List to Set

Previously the subjectAlternativeNames argument was stored in the Terraform state as an ordered list while the API returned information in an unordered manner. The attribute is now configured as a set instead of a list. Certain Terraform configuration language features distinguish between these two attribute types such as not being able to index a set (e.g., awsAcmCertificateExampleSubjectAlternativeNames[0] is no longer a valid reference). Depending on the implementation details of a particular configuration using subjectAlternativeNames as a reference, possible solutions include changing references to using for/forEach or using the tolist() function as a temporary workaround to keep the previous behavior until an appropriate configuration (properly using the unordered set) can be determined. Usage questions can be submitted to the community forums.

certificate_body, certificate_chain, and private_key Arguments No Longer Stored as Hash

Previously when the certificateBody, certificateChain, and privateKey arguments were stored in state, they were stored as a hash of the actual value. This prevented Terraform from properly updating the resource when necessary and the hashing has been removed. The Terraform AWS Provider will show an update to these arguments on the first apply after upgrading to version 3.0.0, which is fixing the Terraform state to remove the hash. Since the privateKey attribute is marked as sensitive, the values in the update will not be visible in the Terraform output. If the non-hashed values have not changed, then no update is occurring other than the Terraform state update. If these arguments are the only updates and they all match the hash removal, the apply will occur without submitting API calls.

Resource: aws_api_gateway_method_settings

throttling_burst_limit and throttling_rate_limit Arguments Now Default to -1

Previously when the throttlingBurstLimit or throttlingRateLimit argument was not configured, the resource would enable throttling and set the limit value to the AWS API Gateway default. In addition, as these arguments were marked as computed, Terraform ignored any subsequent changes made to these arguments in the resource. These behaviors have been removed and, by default, the throttlingBurstLimit and throttlingRateLimit arguments will be disabled in the resource with a value of 1.

Resource: aws_autoscaling_group

availability_zones and vpc_zone_identifier Arguments Now Report Plan-Time Conflict

Specifying both the availabilityZones and vpcZoneIdentifier arguments previously led to confusing behavior and errors. Now this issue is reported at plan-time. Use the null value instead of [] (empty list) in conditionals to ensure this validation does not unexpectedly trigger.

Drift detection enabled for loadBalancers and targetGroupArns arguments

If you previously set one of these arguments to an empty list to enable drift detection (e.g., when migrating an ASG from ELB to ALB), this can be updated as follows.

For example, given this previous configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.autoscalingGroup.AutoscalingGroup(this, "example", {
  loadBalancers: [],
  targetGroupArns: ["${aws_lb_target_group.example.arn}"],
});

An updated configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.autoscalingGroup.AutoscalingGroup(this, "example", {
  targetGroupArns: ["${aws_lb_target_group.example.arn}"],
});

If awsAutoscalingAttachment resources reference your ASG configurations, you will need to add the lifecycle configuration block with an ignoreChanges argument to prevent Terraform non-empty plans (i.e., forcing resource update) during the next state refresh.

For example, given this previous configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
const awsAutoscalingGroupExample = new aws.autoscalingGroup.AutoscalingGroup(
  this,
  "example",
  {}
);
const awsAutoscalingAttachmentExample =
  new aws.autoscalingAttachment.AutoscalingAttachment(this, "example_1", {
    autoscalingGroupName: awsAutoscalingGroupExample.id,
    elb: "${aws_elb.example.id}",
  });
/*This allows the Terraform resource name to match the original name. You can remove the call if you don't need them to match.*/
awsAutoscalingAttachmentExample.overrideLogicalId("example");

An updated configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
const awsAutoscalingGroupExample = new aws.autoscalingGroup.AutoscalingGroup(
  this,
  "example",
  {}
);
awsAutoscalingGroupExample.addOverride("lifecycle", [
  {
    ignore_changes: ["${load_balancers}", "${target_group_arns}"],
  },
]);
const awsAutoscalingAttachmentExample =
  new aws.autoscalingAttachment.AutoscalingAttachment(this, "example_1", {
    autoscalingGroupName: awsAutoscalingGroupExample.id,
    elb: "${aws_elb.example.id}",
  });
/*This allows the Terraform resource name to match the original name. You can remove the call if you don't need them to match.*/
awsAutoscalingAttachmentExample.overrideLogicalId("example");

Resource: aws_cloudfront_distribution

active_trusted_signers Attribute Name and Type Change

Previously, the activeTrustedSigners computed attribute was implemented with a Map that did not support accessing its computed items attribute in Terraform 0.12 correctly. To address this, the activeTrustedSigners attribute has been renamed to trustedSigners and is now implemented as a List with a computed items List attribute and computed enabled boolean attribute. The nested items attribute includes computed awsAccountNumber and keyPairIds sub-fields, with the latter implemented as a List. Thus, user configurations referencing the activeTrustedSigners attribute and its sub-fields will need to be changed as follows.

Given these previous references:

aws_cloudfront_distribution.example.active_trusted_signers.enabled
aws_cloudfront_distribution.example.active_trusted_signers.items

Updated references:

aws_cloudfront_distribution.example.trusted_signers[0].enabled
aws_cloudfront_distribution.example.trusted_signers[0].items

Resource: aws_cloudwatch_log_group

Removal of arn Wildcard Suffix

Previously, the resource returned the ARN directly from the API, which included a :* suffix to denote all CloudWatch Log Streams under the CloudWatch Log Group. Most other AWS resources that return ARNs and many other AWS services do not use the :* suffix. The suffix is now automatically removed. For example, the resource previously returned an ARN such as arn:aws:logs:usEast1:123456789012:logGroup:/example:* but will now return arn:aws:logs:usEast1:123456789012:logGroup:/example.

Workarounds, such as using replace() as shown below, should be removed:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
const awsCloudwatchLogGroupExample =
  new aws.cloudwatchLogGroup.CloudwatchLogGroup(this, "example", {
    name: "example",
  });
const awsDatasyncTaskExample = new aws.datasyncTask.DatasyncTask(
  this,
  "example_1",
  {
    cloudwatchLogGroupArn: `\${replace(${awsCloudwatchLogGroupExample.arn}, ":*", "")}`,
  }
);
/*This allows the Terraform resource name to match the original name. You can remove the call if you don't need them to match.*/
awsDatasyncTaskExample.overrideLogicalId("example");

Removing the :* suffix is a breaking change for some configurations. Fix these configurations using string interpolations as demonstrated below. For example, this configuration is now broken:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.dataAwsIamPolicyDocument.DataAwsIamPolicyDocument(
  this,
  "ad-log-policy",
  {
    statement: [
      {
        actions: ["logs:CreateLogStream", "logs:PutLogEvents"],
        effect: "Allow",
        principals: [
          {
            identifiers: ["ds.amazonaws.com"],
            type: "Service",
          },
        ],
        resources: ["${aws_cloudwatch_log_group.example.arn}"],
      },
    ],
  }
);

An updated configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.dataAwsIamPolicyDocument.DataAwsIamPolicyDocument(
  this,
  "ad-log-policy",
  {
    statement: [
      {
        actions: ["logs:CreateLogStream", "logs:PutLogEvents"],
        effect: "Allow",
        principals: [
          {
            identifiers: ["ds.amazonaws.com"],
            type: "Service",
          },
        ],
        resources: ["${aws_cloudwatch_log_group.example.arn}:*"],
      },
    ],
  }
);

Resource: aws_codepipeline

GITHUB_TOKEN environment variable removal

Switch your Terraform configuration to the oAuthToken element in the action configuration map instead.

For example, given this previous configuration:

$ GITHUB_TOKEN=<token> terraform apply
/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.codepipeline.Codepipeline(this, "example", {
  stage: [
    {
      action: [
        {
          category: "Source",
          configuration: [
            {
              branch: "main",
              owner: "lifesum-terraform",
              repo: "example",
            },
          ],
          name: "Source",
          outputArtifacts: ["example"],
          owner: "ThirdParty",
          provider: "GitHub",
          version: "1",
        },
      ],
      name: "Source",
    },
  ],
});

The configuration could be updated as follows:

$ TF_VAR_github_token=<token> terraform apply
import * as cdktf from "cdktf";
/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
/*Terraform Variables are not always the best fit for getting inputs in the context of Terraform CDK.
You can read more about this at https://cdk.tf/variables*/
const githubToken = new cdktf.TerraformVariable(this, "github_token", {});
new aws.codepipeline.Codepipeline(this, "example", {
  stage: [
    {
      action: [
        {
          category: "Source",
          configuration: [
            {
              branch: "main",
              oAuthToken: githubToken.value,
              owner: "lifesum-terraform",
              repo: "example",
            },
          ],
          name: "Source",
          outputArtifacts: ["example"],
          owner: "ThirdParty",
          provider: "GitHub",
          version: "1",
        },
      ],
      name: "Source",
    },
  ],
});

Resource: aws_cognito_user_pool

Removal of admin_create_user_config.unused_account_validity_days Argument

The Cognito API previously deprecated the adminCreateUserConfig configuration block unusedAccountValidityDays argument in preference of the passwordPolicy configuration block temporaryPasswordValidityDays argument. Configurations will need to be updated to use the API supported configuration.

For example, given this previous configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.cognitoUserPool.CognitoUserPool(this, "example", {
  adminCreateUserConfig: {
    unusedAccountValidityDays: 7,
  },
});

An updated configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.cognitoUserPool.CognitoUserPool(this, "example", {
  passwordPolicy: {
    temporaryPasswordValidityDays: 7,
  },
});

Resource: aws_dx_gateway

Removal of Automatic aws_dx_gateway_association Import

Previously when importing the awsDxGateway resource with the terraformImport command, the Terraform AWS Provider would automatically attempt to import an associated awsDxGatewayAssociation resource(s) as well. This automatic resource import has been removed. Use the awsDxGatewayAssociation resource import to import those resources separately.

Resource: aws_dx_gateway_association

vpn_gateway_id Argument Removal

Switch your Terraform configuration to the associatedGatewayId argument instead.

For example, given this previous configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.dxGatewayAssociation.DxGatewayAssociation(this, "example", {
  vpnGatewayId: "${aws_vpn_gateway.example.id}",
});

An updated configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.dxGatewayAssociation.DxGatewayAssociation(this, "example", {
  associatedGatewayId: "${aws_vpn_gateway.example.id}",
});

Resource: aws_dx_gateway_association_proposal

vpn_gateway_id Argument Removal

Switch your Terraform configuration to the associatedGatewayId argument instead.

For example, given this previous configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.dxGatewayAssociationProposal.DxGatewayAssociationProposal(
  this,
  "example",
  {
    vpn_gateway_id: "${aws_vpn_gateway.example.id}",
  }
);

An updated configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.dxGatewayAssociationProposal.DxGatewayAssociationProposal(
  this,
  "example",
  {
    associatedGatewayId: "${aws_vpn_gateway.example.id}",
  }
);

Resource: aws_ebs_volume

iops Argument Apply-Time Validation

Previously when the iops argument was configured with a type other than io1 (either explicitly or omitted, indicating the default type gp2), the Terraform AWS Provider would automatically disregard the value provided to iops as it is only configurable for the io1 volume type per the AWS EC2 API. This behavior has changed such that the Terraform AWS Provider will instead return an error at apply time indicating an iops value is invalid for types other than io1. Exceptions to this are in cases where iops is set to null or 0 such that the Terraform AWS Provider will continue to accept the value regardless of type.

Resource: aws_elastic_transcoder_preset

video Configuration Block max_frame_rate Argument No Longer Uses 30 Default

Previously when the maxFrameRate argument was not configured, the resource would default to 30. This behavior has been removed and allows for auto frame rate presets to automatically set the appropriate value.

Resource: aws_emr_cluster

core_instance_count Argument Removal

Switch your Terraform configuration to the coreInstanceGroup configuration block instead.

For example, given this previous configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.emrCluster.EmrCluster(this, "example", {
  core_instance_count: 2,
});

An updated configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.emrCluster.EmrCluster(this, "example", {
  coreInstanceGroup: {
    instanceCount: 2,
  },
});

core_instance_type Argument Removal

Switch your Terraform configuration to the coreInstanceGroup configuration block instead.

For example, given this previous configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.emrCluster.EmrCluster(this, "example", {
  core_instance_type: "m4.large",
});

An updated configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.emrCluster.EmrCluster(this, "example", {
  coreInstanceGroup: {
    instanceType: "m4.large",
  },
});

instance_group Configuration Block Removal

Switch your Terraform configuration to the masterInstanceGroup and coreInstanceGroup configuration blocks instead. For any task instance groups, use the awsEmrInstanceGroup resource.

For example, given this previous configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.emrCluster.EmrCluster(this, "example", {
  instance_group: [
    {
      instance_role: "MASTER",
      instance_type: "m4.large",
    },
    {
      instance_count: 1,
      instance_role: "CORE",
      instance_type: "c4.large",
    },
    {
      instance_count: 2,
      instance_role: "TASK",
      instance_type: "c4.xlarge",
    },
  ],
});

An updated configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
const awsEmrClusterExample = new aws.emrCluster.EmrCluster(this, "example", {
  coreInstanceGroup: {
    instanceCount: 1,
    instanceType: "c4.large",
  },
  masterInstanceGroup: {
    instanceType: "m4.large",
  },
});
const awsEmrInstanceGroupExample = new aws.emrInstanceGroup.EmrInstanceGroup(
  this,
  "example_1",
  {
    clusterId: awsEmrClusterExample.id,
    instanceCount: 2,
    instanceType: "c4.xlarge",
  }
);
/*This allows the Terraform resource name to match the original name. You can remove the call if you don't need them to match.*/
awsEmrInstanceGroupExample.overrideLogicalId("example");

master_instance_type Argument Removal

Switch your Terraform configuration to the masterInstanceGroup configuration block instead.

For example, given this previous configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.emrCluster.EmrCluster(this, "example", {
  master_instance_type: "m4.large",
});

An updated configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.emrCluster.EmrCluster(this, "example", {
  masterInstanceGroup: {
    instanceType: "m4.large",
  },
});

Resource: aws_glue_job

allocated_capacity Argument Removal

The Glue API has deprecated the allocatedCapacity argument. Switch your Terraform configuration to the maxCapacity argument instead.

For example, given this previous configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.glueJob.GlueJob(this, "example", {
  allocated_capacity: 2,
});

An updated configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.glueJob.GlueJob(this, "example", {
  maxCapacity: 2,
});

Resource: aws_iam_access_key

ses_smtp_password Attribute Removal

In many regions today and in all regions after October 1, 2020, the SES API will only accept version 4 signatures. If referencing the sesSmtpPassword attribute, switch your Terraform configuration to the sesSmtpPasswordV4 attribute instead. Please note that this signature is based on the region of the Terraform AWS Provider. If you need the SES v4 password in multiple regions, it may require using multiple provider instances.

Depending on when the awsIamAccessKey resource was created, it may not have a sesSmtpPasswordV4 attribute for you to use. If this is the case you will need to taint the resource so that it can be recreated with the new value.

Alternatively, you can stage the change by creating a new awsIamAccessKey resource and change any downstream dependencies to use the new sesSmtpPasswordV4 attribute. Once dependents have been updated with the new resource you can remove the old one.

Resource: aws_iam_instance_profile

roles Argument Removal

Switch your Terraform configuration to the role argument instead.

For example, given this previous configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.iamInstanceProfile.IamInstanceProfile(this, "example", {
  roles: ["${aws_iam_role.example.id}"],
});

An updated configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.iamInstanceProfile.IamInstanceProfile(this, "example", {
  role: "${aws_iam_role.example.id}",
});

Resource: aws_iam_server_certificate

certificate_body, certificate_chain, and private_key Arguments No Longer Stored as Hash

Previously when the certificateBody, certificateChain, and privateKey arguments were stored in state, they were stored as a hash of the actual value. This hashing has been removed for new or recreated resources to prevent lifecycle issues.

Resource: aws_instance

ebs_block_device.iops and root_block_device.iops Argument Apply-Time Validations

Previously when the iops argument was configured in either the ebsBlockDevice or rootBlockDevice configuration block, the Terraform AWS Provider would automatically disregard the value provided to iops if the type argument was also configured with a value other than io1 (either explicitly or omitted, indicating the default type gp2) as iops are only configurable for the io1 volume type per the AWS EC2 API. This behavior has changed such that the Terraform AWS Provider will instead return an error at apply time indicating an iops value is invalid for volume types other than io1. Exceptions to this are in cases where iops is set to null or 0 such that the Terraform AWS Provider will continue to accept the value regardless of type.

Resource: aws_lambda_alias

Import No Longer Converts Function Name to ARN

Previously the resource import would always convert the functionName portion of the import identifier into the ARN format. Configurations using the Lambda Function name would show this as an unexpected difference after import. Now this will passthrough the given value on import whether its a Lambda Function name or ARN.

Resource: aws_launch_template

network_interfaces.delete_on_termination Argument type change

The networkInterfacesDeleteOnTermination argument is now of type string, allowing an unspecified value for the argument since the previous bool type only allowed for true/false and defaulted to false when no value was set. Now to enforce deleteOnTermination to false, the string "false" or bare false value must be used.

For example, given this previous configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.launchTemplate.LaunchTemplate(this, "example", {
  networkInterfaces: [
    {
      deleteOnTermination: [null],
    },
  ],
});

An updated configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.launchTemplate.LaunchTemplate(this, "example", {
  networkInterfaces: [
    {
      deleteOnTermination: false,
    },
  ],
});

Resource: aws_lb_listener_rule

condition.field and condition.values Arguments Removal

Switch your Terraform configuration to use the hostHeader or pathPattern configuration block instead.

For example, given this previous configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.lbListenerRule.LbListenerRule(this, "example", {
  condition: [
    {
      field: "path-pattern",
      values: ["/static/*"],
    },
  ],
});

An updated configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.lbListenerRule.LbListenerRule(this, "example", {
  condition: [
    {
      pathPattern: {
        values: ["/static/*"],
      },
    },
  ],
});

Resource: aws_msk_cluster

encryption_info.encryption_in_transit.client_broker Default Updated to Match API

A few weeks after general availability launch and initial release of the awsMskCluster resource, the MSK API default for client broker encryption switched from tlsPlaintext to tls. The attribute default has now been updated to match the more secure API default, however existing Terraform configurations may show a difference if this setting is not configured.

To continue using the old default when it was previously not configured, add or modify this configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.mskCluster.MskCluster(this, "example", {
  encryptionInfo: {
    encryptionInTransit: {
      clientBroker: "TLS_PLAINTEXT",
    },
  },
});

Resource: aws_rds_cluster

scaling_configuration.min_capacity Now Defaults to 1

Previously when the minCapacity argument in a scalingConfiguration block was not configured, the resource would default to 2. This behavior has been updated to align with the AWS RDS Cluster API default of 1.

Resource: aws_route53_resolver_rule

Removal of trailing period in domain_name argument

Previously the resource returned the Resolver Rule Domain Name directly from the API, which included a . suffix. This proves difficult when many other AWS services do not accept this trailing period (e.g., ACM Certificate). This period is now automatically removed. For example, when the attribute would previously return a Resolver Rule Domain Name such as exampleCom, the attribute now will be returned as exampleCom. While the returned value will omit the trailing period, use of configurations with trailing periods will not be interrupted.

Resource: aws_route53_zone

Removal of trailing period in name argument

Previously the resource returned the Hosted Zone Domain Name directly from the API, which included a . suffix. This proves difficult when many other AWS services do not accept this trailing period (e.g., ACM Certificate). This period is now automatically removed. For example, when the attribute would previously return a Hosted Zone Domain Name such as exampleCom, the attribute now will be returned as exampleCom. While the returned value will omit the trailing period, use of configurations with trailing periods will not be interrupted.

Resource: aws_s3_bucket

Removal of Automatic aws_s3_bucket_policy Import

Previously when importing the awsS3Bucket resource with the terraformImport command, the Terraform AWS Provider would automatically attempt to import an associated awsS3BucketPolicy resource as well. This automatic resource import has been removed. Use the awsS3BucketPolicy resource import to import that resource separately.

region Attribute Is Now Read-Only

The region attribute is no longer configurable, but it remains as a read-only attribute. The region of the awsS3Bucket resource is determined by the region of the Terraform AWS Provider, similar to all other resources.

For example, given this previous configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.s3Bucket.S3Bucket(this, "example", {
  region: "us-west-2",
});

An updated configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.s3Bucket.S3Bucket(this, "example", {});

Resource: aws_s3_bucket_metric

filter configuration block Plan-Time Validation Change

The filter configuration block no longer supports the empty block {} and requires at least one of the prefix or tags attributes to be specified.

For example, given this previous configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.s3BucketMetric.S3BucketMetric(this, "example", {
  filter: {},
});

An updated configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.s3BucketMetric.S3BucketMetric(this, "example", {});

Resource: aws_security_group

Removal of Automatic aws_security_group_rule Import

Previously when importing the awsSecurityGroup resource with the terraformImport command, the Terraform AWS Provider would automatically attempt to import an associated awsSecurityGroupRule resource(s) as well. This automatic resource import has been removed. Use the awsSecurityGroupRule resource import to import those resources separately.

Resource: aws_sns_platform_application

platform_credential and platform_principal Arguments No Longer Stored as SHA256 Hash

Previously when the platformCredential and platformPrincipal arguments were stored in state, they were stored as a SHA256 hash of the actual value. This prevented Terraform from properly updating the resource when necessary and the hashing has been removed. The Terraform AWS Provider will show an update to these arguments on the first apply after upgrading to version 3.0.0, which is fixing the Terraform state to remove the hash. Since the attributes are marked as sensitive, the values in the update will not be visible in the Terraform output. If the non-hashed values have not changed, then no update is occurring other than the Terraform state update. If these arguments are the only two updates and they both match the SHA256 removal, the apply will occur without submitting an actual setPlatformApplicationAttributes API call.

Resource: aws_spot_fleet_request

valid_until Argument No Longer Uses 24 Hour Default

Previously when the validUntil argument was not configured, the resource would default to a 24 hour request. This behavior has been removed and allows for non-expiring requests. To recreate the old behavior, the timeOffset resource can potentially be used.

Resource: aws_ssm_maintenance_window_task

logging_info Configuration Block Removal

Switch your Terraform configuration to the taskInvocationParameters configuration block runCommandParameters configuration block outputS3Bucket and outputS3KeyPrefix arguments instead.

For example, given this previous configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.ssmMaintenanceWindowTask.SsmMaintenanceWindowTask(this, "example", {
  logging_info: [
    {
      s3_bucket_key_prefix: "example",
      s3_bucket_name: "${aws_s3_bucket.example.id}",
    },
  ],
});

An updated configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.ssmMaintenanceWindowTask.SsmMaintenanceWindowTask(this, "example", {
  taskInvocationParameters: {
    runCommandParameters: {
      outputS3Bucket: "${aws_s3_bucket.example.id}",
      outputS3KeyPrefix: "example",
    },
  },
});

task_parameters Configuration Block Removal

Switch your Terraform configuration to the taskInvocationParameters configuration block runCommandParameters configuration block parameter configuration blocks instead.

For example, given this previous configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.ssmMaintenanceWindowTask.SsmMaintenanceWindowTask(this, "example", {
  task_parameters: [
    {
      name: "commands",
      values: ["date"],
    },
  ],
});

An updated configuration:

/*Provider bindings are generated by running cdktf get.
See https://cdk.tf/provider-generation for more details.*/
import * as aws from "./.gen/providers/aws";
new aws.ssmMaintenanceWindowTask.SsmMaintenanceWindowTask(this, "example", {
  taskInvocationParameters: {
    runCommandParameters: {
      parameter: [
        {
          name: "commands",
          values: ["date"],
        },
      ],
    },
  },
});