» Terraform AWS Provider Version 2 Upgrade Guide

Version 2.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 1.60.0 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.

Upgrade topics:

» Provider Version Configuration

It is recommended to use version constraints when configuring Terraform providers. If you are following that recommendation, update the version constraints in your Terraform configuration and run terraform init to download the new version.

Update to latest 1.X version:

provider "aws" {
  # ... other configuration ...

  version = "~> 1.60"
}

Update to latest 2.X version:

provider "aws" {
  # ... other configuration ...

  version = "~> 2.0"
}

» Provider: Configuration

» skip_requesting_account_id Argument Now Required to Skip Account ID Lookup Errors

If the provider is unable to determine the AWS account ID from a provider assume role configuration or the STS GetCallerIdentity call used to verify the credentials (if skip_credentials_validation = false), it will attempt to lookup the AWS account ID via EC2 metadata, IAM GetUser, IAM ListRoles, and STS GetCallerIdentity. Previously, the provider would silently allow the failure of all the above methods.

The provider will now return an error to ensure operators understand the implications of the missing AWS account ID in the provider.

If necessary, the AWS account ID lookup logic can be skipped via:

provider "aws" {
  # ... other configuration ...

  skip_requesting_account_id = true
}

» Data Source: aws_ami

» owners Argument Now Required

The owners argument is now required. Specifying owner-id or owner-alias under filter does not satisfy this requirement.

» Data Source: aws_ami_ids

» owners Argument Now Required

The owners argument is now required. Specifying owner-id or owner-alias under filter does not satisfy this requirement.

» Data Source: aws_iam_role

» assume_role_policy_document Attribute Removal

Switch your attribute references to the assume_role_policy attribute instead.

» role_id Attribute Removal

Switch your attribute references to the unique_id attribute instead.

» role_name Argument Removal

Switch your Terraform configuration to the name argument instead.

» Data Source: aws_kms_secret

» Data Source Removal and Migrating to aws_kms_secrets Data Source

The implementation of the aws_kms_secret data source, prior to Terraform AWS provider version 2.0.0, used dynamic attribute behavior which is not supported with Terraform 0.12 and beyond (full details available in this GitHub issue).

Terraform configuration migration steps:

  • Change the data source type from aws_kms_secret to aws_kms_secrets
  • Change any attribute reference (e.g. "${data.aws_kms_secret.example.ATTRIBUTE}") from .ATTRIBUTE to .plaintext["ATTRIBUTE"]

As an example, lets take the below sample configuration and migrate it.

# Below example configuration will not be supported in Terraform AWS provider version 2.0.0

data "aws_kms_secret" "example" {
  secret {
    # ... potentially other configuration ...
    name    = "master_password"
    payload = "AQEC..."
  }

  secret {
    # ... potentially other configuration ...
    name    = "master_username"
    payload = "AQEC..."
  }
}

resource "aws_rds_cluster" "example" {
  # ... other configuration ...
  master_password = "${data.aws_kms_secret.example.master_password}"
  master_username = "${data.aws_kms_secret.example.master_username}"
}

Notice that the aws_kms_secret data source previously was taking the two secret configuration block name arguments and generating those as attribute names (master_password and master_username in this case). To remove the incompatible behavior, this updated version of the data source provides the decrypted value of each of those secret configuration block name arguments within a map attribute named plaintext.

Updating the sample configuration from above:

data "aws_kms_secrets" "example" {
  secret {
    # ... potentially other configuration ...
    name    = "master_password"
    payload = "AQEC..."
  }

  secret {
    # ... potentially other configuration ...
    name    = "master_username"
    payload = "AQEC..."
  }
}

resource "aws_rds_cluster" "example" {
  # ... other configuration ...
  master_password = "${data.aws_kms_secrets.example.plaintext["master_password"]}"
  master_username = "${data.aws_kms_secrets.example.plaintext["master_username"]}"
}

» Data Source: aws_lambda_function

» arn and qualified_arn Attribute Behavior Changes

The arn attribute now always returns the unqualified (no :QUALIFIER or :VERSION suffix) Amazon Resource Name (ARN) value and the qualified_arn attribute now always returns the qualified (includes :QUALIFIER or :VERSION suffix) ARN value. Previously by default, the arn attribute included :$LATEST suffix when not setting the optional qualifier argument, which was not compatible with many other resources. To restore the previous default behavior, set the qualifier argument to $LATEST and reference the qualified_arn attribute.

» Data Source: aws_region

» current Argument Removal

Simply remove current = true from your Terraform configuration. The data source defaults to the current provider region if no other filtering is enabled.

» Resource: aws_api_gateway_api_key

» stage_key Argument Removal

Since the API Gateway usage plans feature was launched on August 11, 2016, usage plans are now required to associate an API key with an API stage. To migrate your Terraform configuration, the AWS provider implements support for usage plans with the following resources:

For example, given this previous configuration:

resource "aws_api_gateway_rest_api" "example" {
  name = "example"
}

resource "aws_api_gateway_deployment" "example" {
  rest_api_id = "${aws_api_gateway_rest_api.example.id}"
  stage_name  = "example"
}

resource "aws_api_gateway_api_key" "example" {
  name = "example"

  stage_key {
    rest_api_id = "${aws_api_gateway_rest_api.example.id}"
    stage_name  = "${aws_api_gateway_deployment.example.stage_name}"
  }
}

An updated configuration:

resource "aws_api_gateway_rest_api" "example" {
  name = "example"
}

resource "aws_api_gateway_deployment" "example" {
  rest_api_id = "${aws_api_gateway_rest_api.example.id}"
  stage_name  = "example"
}

resource "aws_api_gateway_api_key" "example" {
  name = "example"
}

resource "aws_api_gateway_usage_plan" "example" {
  name         = "example"

  api_stages {
    api_id = "${aws_api_gateway_rest_api.example.id}"
    stage  = "${aws_api_gateway_deployment.example.stage_name}"
  }
}

resource "aws_api_gateway_usage_plan_key" "example" {
  key_id        = "${aws_api_gateway_api_key.example.id}"
  key_type      = "API_KEY"
  usage_plan_id = "${aws_api_gateway_usage_plan.example.id}"
}

» Resource: aws_api_gateway_integration

» request_parameters_in_json Argument Removal

Switch your Terraform configuration to the request_parameters argument instead.

For example, given this previous configuration:

resource "aws_api_gateway_integration" "example" {
  # ... other configuration ...

  request_parameters_in_json = <<PARAMS
{
    "integration.request.header.X-Authorization": "'static'"
}
PARAMS
}

An updated configuration:

resource "aws_api_gateway_integration" "example" {
  # ... other configuration ...

  request_parameters = {
    "integration.request.header.X-Authorization" = "'static'"
  }
}

» Resource: aws_api_gateway_integration_response

» response_parameters_in_json Argument Removal

Switch your Terraform configuration to the response_parameters argument instead.

For example, given this previous configuration:

resource "aws_api_gateway_integration_response" "example" {
  # ... other configuration ...

  response_parameters_in_json = <<PARAMS
{
    "method.response.header.Content-Type": "integration.response.body.type"
}
PARAMS
}

An updated configuration:

resource "aws_api_gateway_integration_response" "example" {
  # ... other configuration ...

  response_parameters = {
    "method.response.header.Content-Type" = "integration.response.body.type"
  }
}

» Resource: aws_api_gateway_method

» request_parameters_in_json Argument Removal

Switch your Terraform configuration to the request_parameters argument instead.

For example, given this previous configuration:

resource "aws_api_gateway_method" "example" {
  # ... other configuration ...

  request_parameters_in_json = <<PARAMS
{
    "method.request.header.Content-Type": false,
    "method.request.querystring.page": true
}
PARAMS
}

An updated configuration:

resource "aws_api_gateway_method" "example" {
  # ... other configuration ...

  request_parameters = {
    "method.request.header.Content-Type" = false
    "method.request.querystring.page" = true
  }
}

» Resource: aws_api_gateway_method_response

» response_parameters_in_json Argument Removal

Switch your Terraform configuration to the response_parameters argument instead.

For example, given this previous configuration:

resource "aws_api_gateway_method_response" "example" {
  # ... other configuration ...

  response_parameters_in_json = <<PARAMS
{
    "method.response.header.Content-Type": true
}
PARAMS
}

An updated configuration:

resource "aws_api_gateway_method_response" "example" {
  # ... other configuration ...

  response_parameters = {
    "method.response.header.Content-Type" = true
  }
}

» Resource: aws_appautoscaling_policy

» Argument Removals

The following arguments have been moved into a nested argument named step_scaling_policy_configuration:

For example, given this previous configuration:

resource "aws_appautoscaling_policy" "example" {
  # ... other configuration ...

  adjustment_type         = "ChangeInCapacity"
  cooldown                = 60
  metric_aggregation_type = "Maximum"

  step_adjustment {
    metric_interval_upper_bound = 0
    scaling_adjustment          = -1
  }
}

An updated configuration:

resource "aws_appautoscaling_policy" "example" {
  # ... other configuration ...

  step_scaling_policy_configuration {
    adjustment_type         = "ChangeInCapacity"
    cooldown                = 60
    metric_aggregation_type = "Maximum"

    step_adjustment {
      metric_interval_upper_bound = 0
      scaling_adjustment          = -1
    }
  }
}

» Resource: aws_autoscaling_policy

» min_adjustment_step Argument Removal

Switch your Terraform configuration to the min_adjustment_magnitude argument instead.

For example, given this previous configuration:

resource "aws_autoscaling_policy" "example" {
  # ... other configuration ...

  min_adjustment_step = 2
}

An updated configuration:

resource "aws_autoscaling_policy" "example" {
  # ... other configuration ...

  min_adjustment_magnitude = 2
}

» Resource: aws_batch_compute_environment

» ecc_cluster_arn Attribute Removal

Switch your attribute references to the ecs_cluster_arn attribute instead.

» Resource: aws_cloudfront_distribution

» cache_behavior Argument Removal

Switch your Terraform configuration to the ordered_cache_behavior argument instead. It behaves similar to the previous cache_behavior argument, however the ordering of the configurations in Terraform is now reflected in the distribution where previously it was indeterminate.

For example, given this previous configuration:

resource "aws_cloudfront_distribution" "example" {
  # ... other configuration ...

  cache_behavior {
    # ... other configuration ...
  }

  cache_behavior {
    # ... other configuration ...
  }
}

An updated configuration:

resource "aws_cloudfront_distribution" "example" {
  # ... other configuration ...

  ordered_cache_behavior {
    # ... other configuration ...
  }

  ordered_cache_behavior {
    # ... other configuration ...
  }
}

» Resource: aws_cognito_user_pool

» email_verification_subject Argument Now Conflicts With verification_message_template Configuration Block email_subject Argument

Choose one argument or the other. These arguments update the same underlying information in Cognito and the selection is indeterminate if differing values are provided.

» email_verification_message Argument Now Conflicts With verification_message_template Configuration Block email_message Argument

Choose one argument or the other. These arguments update the same underlying information in Cognito and the selection is indeterminate if differing values are provided.

» sms_verification_message Argument Now Conflicts With verification_message_template Configuration Block sms_message Argument

Choose one argument or the other. These arguments update the same underlying information in Cognito and the selection is indeterminate if differing values are provided.

» Resource: aws_dx_lag

» number_of_connections Argument Removal

Default connections have been removed as part of LAG creation. To migrate your Terraform configuration, the AWS provider implements the following resources:

For example, given this previous configuration:

resource "aws_dx_lag" "example" {
  name                  = "example"
  connections_bandwidth = "1Gbps"
  location              = "EqSe2"
  number_of_connections = 1
}

An updated configuration:

resource "aws_dx_connection" "example" {
  name      = "example"
  bandwidth = "1Gbps"
  location  = "EqSe2"
}

resource "aws_dx_lag" "example" {
  name                  = "example"
  connections_bandwidth = "1Gbps"
  location              = "EqSe2"
}

resource "aws_dx_connection_association" "example" {
  connection_id = "${aws_dx_connection.example.id}"
  lag_id        = "${aws_dx_lag.example.id}"
}

» Resource: aws_ecs_service

» placement_strategy Argument Removal

Switch your Terraform configuration to the ordered_placement_strategy argument instead. It behaves similar to the previous placement_strategy argument, however the ordering of the configurations in Terraform is now reflected in the distribution where previously it was indeterminate.

For example, given this previous configuration:

resource "aws_ecs_service" "example" {
  # ... other configuration ...

  placement_strategy {
    # ... other configuration ...
  }

  placement_strategy {
    # ... other configuration ...
  }
}

An updated configuration:

resource "aws_ecs_service" "example" {
  # ... other configuration ...

  ordered_placement_strategy {
    # ... other configuration ...
  }

  ordered_placement_strategy {
    # ... other configuration ...
  }
}

» Resource: aws_efs_file_system

» reference_name Argument Removal

Switch your Terraform configuration to the creation_token argument instead.

For example, given this previous configuration:

resource "aws_efs_file_system" "example" {
  # ... other configuration ...

  reference_name = "example"
}

An updated configuration:

resource "aws_efs_file_system" "example" {
  # ... other configuration ...

  creation_token = "example"
}

» Resource: aws_elasticache_cluster

» availability_zones Argument Removal

Switch your Terraform configuration to the preferred_availability_zones argument instead. The argument is still optional and the API will continue to automatically choose Availability Zones for nodes if not specified. The new argument will also continue to match the APIs required behavior that the length of the list must be the same as num_cache_nodes.

For example, given this previous configuration:

resource "aws_elasticache_cluster" "example" {
  # ... other configuration ...

  availability_zones = ["us-west-2a", "us-west-2b"]
}

An updated configuration:

resource "aws_elasticache_cluster" "example" {
  # ... other configuration ...

  preferred_availability_zones = ["us-west-2a", "us-west-2b"]
}

» Resource: aws_iam_user_login_profile

» Import Now Required For Existing Infrastructure

When attempting to bring existing IAM User Login Profiles under Terraform management, terraform import is now required. See the aws_iam_user_login_profile resource documentation for more information.

» Resource: aws_instance

» network_interface_id Attribute Removal

Switch your attribute references to the primary_network_interface_id attribute instead.

» Resource: aws_lambda_function

» reserved_concurrent_executions Argument Behavior Change

Setting reserved_concurrent_executions to 0 will now disable Lambda Function invocations, causing downtime for the Lambda Function.

Previously reserved_concurrent_executions accepted 0 and below for unreserved concurrency, which means it was not previously possible to disable invocations. The argument now differentiates between a new value for unreserved concurrency (-1) and disabling Lambda invocations (0). If previously configuring this value to 0 for unreserved concurrency, update the configured value to -1 or the resource will disable Lambda Function invocations on update. If previously unconfigured, the argument does not require any changes.

See the Lambda User Guide for more information about concurrency.

» Resource: aws_lambda_layer_version

» arn and layer_arn Attribute Value Swap

Switch your arn attribute references to the layer_arn attribute instead and vice-versa.

» Resource: aws_network_acl

» subnet_id Argument Removal

Switch your Terraform configuration to the subnet_ids argument instead.

For example, given this previous configuration:

resource "aws_network_acl" "example" {
  # ... other configuration ...

  subnet_id = "subnet-12345678"
}

An updated configuration:

resource "aws_network_acl" "example" {
  # ... other configuration ...

  subnet_ids = ["subnet-12345678"]
}

» Resource: aws_redshift_cluster

» Argument Removals

The following arguments have been moved into a nested argument named logging:

For example, given this previous configuration:

resource "aws_redshift_cluster" "example" {
  # ... other configuration ...

  bucket_name    = "example"
  enable_logging = true
  s3_key_prefix  = "example"
}

An updated configuration:

resource "aws_redshift_cluster" "example" {
  # ... other configuration ...

  logging {
    bucket_name    = "example"
    enable         = true
    s3_key_prefix  = "example"
  }
}

» Resource: aws_route_table

» Import Change

Previously, importing this resource resulted in an aws_route resource for each route, in addition to the aws_route_table, in the Terraform state. Support for importing aws_route resources has been added and importing this resource only adds the aws_route_table resource, with in-line routes, to the state.

» Resource: aws_route53_record

» allow_overwrite Default Value Change

The resource now requires existing Route 53 Records to be imported into the Terraform state for management unless the allow_overwrite argument is enabled.

For example, if the www.example.com Route 53 Record in the example.com Route 53 Hosted Zone existed previously and this new Terraform configuration was introduced:

resource "aws_route53_record" "www" {
  # ... other configuration ...
  name = "www.example.com"
}

During resource creation in version 1.X and prior, it would silently perform an UPSERT changeset to the existing Route 53 Record and not report back an error. In version 2.0.0 of the Terraform AWS Provider, the resource now performs a CREATE changeset, which will error for existing Route 53 Records.

The allow_overwrite argument provides a workaround to keep the old behavior, but most existing workflows should be updated to perform a terraform import command like the following instead:

$ terraform import aws_route53_record.www ZONEID_www.example.com_TYPE

More information can be found in the aws_route53_record resource documentation.

» Resource: aws_route53_zone

» vpc_id and vpc_region Argument Removal

Switch your Terraform configuration to vpc configuration block(s) instead.

For example, given this previous configuration:

resource "aws_route53_zone" "example" {
  # ... other configuration ...

  vpc_id = "..."
}

An updated configuration:

resource "aws_route53_zone" "example" {
  # ... other configuration ...

  vpc {
    vpc_id = "..."
  }
}

» Resource: aws_wafregional_byte_match_set

» byte_match_tuple Argument Removal

Switch your Terraform configuration to the byte_match_tuples argument instead.

For example, given this previous configuration:

resource "aws_wafregional_byte_match_set" "example" {
  # ... other configuration ...

  byte_match_tuple {
    # ... other configuration ...
  }

  byte_match_tuple {
    # ... other configuration ...
  }
}

An updated configuration:

resource "aws_wafregional_byte_match_set" "example" {
  # ... other configuration ...

  byte_match_tuples {
    # ... other configuration ...
  }

  byte_match_tuples {
    # ... other configuration ...
  }
}