Terraform AWS Provider v4 にアップグレード
概要
Terraform AWS Providerをversion 4にアップグレードした際の対応方法をまとめました。
version 4ではaws_s3_bucket
リソースで破壊的変更があり、コードを修正する必要があります。
今回は、下記ブログで紹介されていた tfrefactor というツールを利用しました。
このツールはversion 4に準拠したコードを自動で生成でき、思ったより簡単にアップグレードできました。
TerraformのAWS Provider Version 4へのアップグレードに伴うコード改修がおっくうなほうへ 便利なツールありますよ | DevelopersIO
友人と開発・運用しているサービスLGTMeowでアップグレード対応しています。
なお、v3.29.0からv4.5.0に変更しています。
ソースコード
実際にアップデートした際の変更内容はこちらのPRを参考にしてください。
手順は下記にて説明します。
前提
基本的に公式ドキュメントを参考にしています。
Terraform AWS Provider Version 4 Upgrade Guide | Guides | hashicorp/aws | Terraform Registry
ドキュメントにも記載されていますが、version 3.Xからアップグレードすることを前提としています。 version 2を利用中の方はversion 3へのアップグレードを先に行なってください。
事前準備
tfrefactor を利用する場合は、インストールしてください。
手順
Provider Version Configuration の更新
terraform {
required_version = "1.0.3"
required_providers {
- aws = "3.29.0"
+ aws = "4.5.0"
}
}
lockfile の更新
.terraform.lock.hcl
を更新します。作業ディレクトリで、下記のコマンドを実行してください。
terraform init --upgrade
この時点でterraform plan
実行すると下記の通り、aws_s3_bucket
関連のリソースでエラーになしました。 aws_s3_bucket
関連以外のリソースではエラーになりませんでした。
╷
│ Error: Value for unconfigurable attribute
│
│ with module.images.aws_s3_bucket.upload_images_bucket,
│ on ../../../../../modules/aws/images/main.tf line 1, in resource "aws_s3_bucket" "upload_images_bucket":
│ 1: resource "aws_s3_bucket" "upload_images_bucket" {
│
│ Can't configure a value for "lifecycle_rule": its value will be decided automatically based on the result of applying this configuration.
╵
╷
│ Error: Value for unconfigurable attribute
│
│ with module.images.aws_s3_bucket.upload_images_bucket,
│ on ../../../../../modules/aws/images/main.tf line 1, in resource "aws_s3_bucket" "upload_images_bucket":
│ 1: resource "aws_s3_bucket" "upload_images_bucket" {
│
│ Can't configure a value for "versioning": its value will be decided automatically based on the result of applying this configuration.
╵
╷
│ Error: Value for unconfigurable attribute
│
│ with module.images.aws_s3_bucket.upload_images_bucket,
│ on ../../../../../modules/aws/images/main.tf line 3, in resource "aws_s3_bucket" "upload_images_bucket":
│ 3: acl = "private"
│
│ Can't configure a value for "acl": its value will be decided automatically based on the result of applying this configuration.
╵
╷
│ Error: Value for unconfigurable attribute
│
│ with module.images.aws_s3_bucket.cat_images_bucket,
│ on ../../../../../modules/aws/images/main.tf line 23, in resource "aws_s3_bucket" "cat_images_bucket":
│ 23: resource "aws_s3_bucket" "cat_images_bucket" {
│
│ Can't configure a value for "versioning": its value will be decided automatically based on the result of applying this configuration.
╵
╷
│ Error: Value for unconfigurable attribute
│
│ with module.images.aws_s3_bucket.cat_images_bucket,
│ on ../../../../../modules/aws/images/main.tf line 23, in resource "aws_s3_bucket" "cat_images_bucket":
│ 23: resource "aws_s3_bucket" "cat_images_bucket" {
│
│ Can't configure a value for "lifecycle_rule": its value will be decided automatically based on the result of applying this configuration.
╵
╷
│ Error: Value for unconfigurable attribute
│
│ with module.images.aws_s3_bucket.cat_images_bucket,
│ on ../../../../../modules/aws/images/main.tf line 25, in resource "aws_s3_bucket" "cat_images_bucket":
│ 25: acl = "private"
│
│ Can't configure a value for "acl": its value will be decided automatically based on the result of applying this configuration.
╵
╷
│ Error: Value for unconfigurable attribute
│
│ with module.images.aws_s3_bucket.created_lgtm_images_bucket,
│ on ../../../../../modules/aws/images/main.tf line 45, in resource "aws_s3_bucket" "created_lgtm_images_bucket":
│ 45: resource "aws_s3_bucket" "created_lgtm_images_bucket" {
│
│ Can't configure a value for "lifecycle_rule": its value will be decided automatically based on the result of applying this configuration.
╵
╷
│ Error: Value for unconfigurable attribute
│
│ with module.images.aws_s3_bucket.created_lgtm_images_bucket,
│ on ../../../../../modules/aws/images/main.tf line 45, in resource "aws_s3_bucket" "created_lgtm_images_bucket":
│ 45: resource "aws_s3_bucket" "created_lgtm_images_bucket" {
│
│ Can't configure a value for "versioning": its value will be decided automatically based on the result of applying this configuration.
╵
╷
│ Error: Value for unconfigurable attribute
│
│ with module.images.aws_s3_bucket.created_lgtm_images_bucket,
│ on ../../../../../modules/aws/images/main.tf line 47, in resource "aws_s3_bucket" "created_lgtm_images_bucket":
│ 47: acl = "private"
│
│ Can't configure a value for "acl": its value will be decided automatically based on the result of applying this configuration.
╵
╷
│ Error: Value for unconfigurable attribute
│
│ with module.images.aws_s3_bucket.lgtm_images_bucket,
│ on ../../../../../modules/aws/images/main.tf line 67, in resource "aws_s3_bucket" "lgtm_images_bucket":
│ 67: resource "aws_s3_bucket" "lgtm_images_bucket" {
│
│ Can't configure a value for "lifecycle_rule": its value will be decided automatically based on the result of applying this configuration.
╵
╷
│ Error: Value for unconfigurable attribute
│
│ with module.images.aws_s3_bucket.lgtm_images_bucket,
│ on ../../../../../modules/aws/images/main.tf line 67, in resource "aws_s3_bucket" "lgtm_images_bucket":
│ 67: resource "aws_s3_bucket" "lgtm_images_bucket" {
│
│ Can't configure a value for "versioning": its value will be decided automatically based on the result of applying this configuration.
╵
╷
│ Error: Value for unconfigurable attribute
│
│ with module.images.aws_s3_bucket.lgtm_images_bucket,
│ on ../../../../../modules/aws/images/main.tf line 69, in resource "aws_s3_bucket" "lgtm_images_bucket":
│ 69: acl = "private"
│
│ Can't configure a value for "acl": its value will be decided automatically based on the result of applying this configuration.
╵
╷
│ Error: Value for unconfigurable attribute
│
│ with module.images.aws_s3_bucket.lgtm_images_access_logs,
│ on ../../../../../modules/aws/images/main.tf line 121, in resource "aws_s3_bucket" "lgtm_images_access_logs":
│ 121: resource "aws_s3_bucket" "lgtm_images_access_logs" {
│
│ Can't configure a value for "lifecycle_rule": its value will be decided automatically based on the result of applying this configuration.
╵
version 4 に準拠したコードに変更
tfrefactorを利用してversion 4準拠のコードに書き換えます。tfrefactorを利用しない場合は、ドキュメントを参考に修正してください。
下記のコマンドを実行すると、main_migrated.tf
というファイルが生成されます。実際に生成されたコードはこちらです。
tfrefactor resource aws_s3_bucket main.tf -c
-c
を指定することで、変更の対象となったリソースの一覧がCSVに出力されます。
aws_s3_bucket_acl.upload_images_bucket_acl,aws_s3_bucket.upload_images_bucket
aws_s3_bucket_lifecycle_configuration.upload_images_bucket_lifecycle_configuration,aws_s3_bucket.upload_images_bucket
aws_s3_bucket_versioning.upload_images_bucket_versioning,aws_s3_bucket.upload_images_bucket
aws_s3_bucket_acl.cat_images_bucket_acl,aws_s3_bucket.cat_images_bucket
aws_s3_bucket_lifecycle_configuration.cat_images_bucket_lifecycle_configuration,aws_s3_bucket.cat_images_bucket
aws_s3_bucket_versioning.cat_images_bucket_versioning,aws_s3_bucket.cat_images_bucket
aws_s3_bucket_acl.created_lgtm_images_bucket_acl,aws_s3_bucket.created_lgtm_images_bucket
aws_s3_bucket_lifecycle_configuration.created_lgtm_images_bucket_lifecycle_configuration,aws_s3_bucket.created_lgtm_images_bucket
aws_s3_bucket_versioning.created_lgtm_images_bucket_versioning,aws_s3_bucket.created_lgtm_images_bucket
aws_s3_bucket_acl.lgtm_images_bucket_acl,aws_s3_bucket.lgtm_images_bucket
aws_s3_bucket_lifecycle_configuration.lgtm_images_bucket_lifecycle_configuration,aws_s3_bucket.lgtm_images_bucket
aws_s3_bucket_versioning.lgtm_images_bucket_versioning,aws_s3_bucket.lgtm_images_bucket
aws_s3_bucket_lifecycle_configuration.lgtm_images_access_logs_lifecycle_configuration,aws_s3_bucket.lgtm_images_access_logs
生成されたコードを利用して terraform plan
を実行すると先ほどとは異なるエラーが発生しました。
╷
│ Error: Missing required argument
│
│ on ../../../../../modules/aws/images/main_migrated.tf line 151, in resource "aws_s3_bucket_lifecycle_configuration" "upload_images_bucket_lifecycle_configuration":
│ 151: rule {
│
│ The argument "id" is required, but no definition was found.
╵
╷
│ Error: Missing required argument
│
│ on ../../../../../modules/aws/images/main_migrated.tf line 177, in resource "aws_s3_bucket_lifecycle_configuration" "cat_images_bucket_lifecycle_configuration":
│ 177: rule {
│
│ The argument "id" is required, but no definition was found.
╵
╷
│ Error: Missing required argument
│
│ on ../../../../../modules/aws/images/main_migrated.tf line 202, in resource "aws_s3_bucket_lifecycle_configuration" "created_lgtm_images_bucket_lifecycle_configuration":
│ 202: rule {
│
│ The argument "id" is required, but no definition was found.
╵
╷
│ Error: Missing required argument
│
│ on ../../../../../modules/aws/images/main_migrated.tf line 228, in resource "aws_s3_bucket_lifecycle_configuration" "lgtm_images_bucket_lifecycle_configuration":
│ 228: rule {
│
│ The argument "id" is required, but no definition was found.
╵
╷
│ Error: Missing required argument
│
│ on ../../../../../modules/aws/images/main_migrated.tf line 248, in resource "aws_s3_bucket_lifecycle_configuration" "lgtm_images_access_logs_lifecycle_configuration":
│ 248: rule {
│
│ The argument "id" is required, but no definition was found.
╵
aws_s3_bucket_lifecycle_configuration
の rule
にはidが必須ですが、指定されていないのでエラーになっています。
idを指定してもう一度 terraform plan
を実行するとエラーが解消しました。
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# module.images.aws_s3_bucket_acl.cat_images_bucket_acl will be created
+ resource "aws_s3_bucket_acl" "cat_images_bucket_acl" {
+ acl = "private"
+ bucket = "stg-lgtmeow-cat-images"
+ id = (known after apply)
+ access_control_policy {
+ grant {
+ permission = (known after apply)
+ grantee {
+ display_name = (known after apply)
+ email_address = (known after apply)
+ id = (known after apply)
+ type = (known after apply)
+ uri = (known after apply)
}
}
+ owner {
+ display_name = (known after apply)
+ id = (known after apply)
}
}
}
# module.images.aws_s3_bucket_acl.created_lgtm_images_bucket_acl will be created
+ resource "aws_s3_bucket_acl" "created_lgtm_images_bucket_acl" {
+ acl = "private"
+ bucket = "stg-lgtmeow-created-lgtm-images"
+ id = (known after apply)
+ access_control_policy {
+ grant {
+ permission = (known after apply)
+ grantee {
+ display_name = (known after apply)
+ email_address = (known after apply)
+ id = (known after apply)
+ type = (known after apply)
+ uri = (known after apply)
}
}
+ owner {
+ display_name = (known after apply)
+ id = (known after apply)
}
}
}
# module.images.aws_s3_bucket_acl.lgtm_images_bucket_acl will be created
+ resource "aws_s3_bucket_acl" "lgtm_images_bucket_acl" {
+ acl = "private"
+ bucket = "stg-lgtmeow-images"
+ id = (known after apply)
+ access_control_policy {
+ grant {
+ permission = (known after apply)
+ grantee {
+ display_name = (known after apply)
+ email_address = (known after apply)
+ id = (known after apply)
+ type = (known after apply)
+ uri = (known after apply)
}
}
+ owner {
+ display_name = (known after apply)
+ id = (known after apply)
}
}
}
# module.images.aws_s3_bucket_acl.upload_images_bucket_acl will be created
+ resource "aws_s3_bucket_acl" "upload_images_bucket_acl" {
+ acl = "private"
+ bucket = "stg-lgtmeow-upload-images"
+ id = (known after apply)
+ access_control_policy {
+ grant {
+ permission = (known after apply)
+ grantee {
+ display_name = (known after apply)
+ email_address = (known after apply)
+ id = (known after apply)
+ type = (known after apply)
+ uri = (known after apply)
}
}
+ owner {
+ display_name = (known after apply)
+ id = (known after apply)
}
}
}
# module.images.aws_s3_bucket_lifecycle_configuration.cat_images_bucket_lifecycle_configuration will be created
+ resource "aws_s3_bucket_lifecycle_configuration" "cat_images_bucket_lifecycle_configuration" {
+ bucket = "stg-lgtmeow-cat-images"
+ id = (known after apply)
+ rule {
+ id = "purge-noncurrent-version"
+ status = "Enabled"
+ abort_incomplete_multipart_upload {
+ days_after_initiation = 7
}
+ noncurrent_version_expiration {
+ noncurrent_days = 30
}
}
}
# module.images.aws_s3_bucket_lifecycle_configuration.created_lgtm_images_bucket_lifecycle_configuration will be created
+ resource "aws_s3_bucket_lifecycle_configuration" "created_lgtm_images_bucket_lifecycle_configuration" {
+ bucket = "stg-lgtmeow-created-lgtm-images"
+ id = (known after apply)
+ rule {
+ id = "purge-old-version"
+ status = "Enabled"
+ abort_incomplete_multipart_upload {
+ days_after_initiation = 1
}
+ expiration {
+ days = 10
+ expired_object_delete_marker = (known after apply)
}
}
}
# module.images.aws_s3_bucket_lifecycle_configuration.lgtm_images_access_logs_lifecycle_configuration will be created
+ resource "aws_s3_bucket_lifecycle_configuration" "lgtm_images_access_logs_lifecycle_configuration" {
+ bucket = "stg-lgtmeow-images-logs"
+ id = (known after apply)
+ rule {
+ id = "abort-incomplete-multipart-upload"
+ status = "Enabled"
+ abort_incomplete_multipart_upload {
+ days_after_initiation = 7
}
}
}
# module.images.aws_s3_bucket_lifecycle_configuration.lgtm_images_bucket_lifecycle_configuration will be created
+ resource "aws_s3_bucket_lifecycle_configuration" "lgtm_images_bucket_lifecycle_configuration" {
+ bucket = "stg-lgtmeow-images"
+ id = (known after apply)
+ rule {
+ id = "purge-noncurrent-version"
+ status = "Enabled"
+ abort_incomplete_multipart_upload {
+ days_after_initiation = 7
}
+ noncurrent_version_expiration {
+ noncurrent_days = 30
}
}
}
# module.images.aws_s3_bucket_lifecycle_configuration.upload_images_bucket_lifecycle_configuration will be created
+ resource "aws_s3_bucket_lifecycle_configuration" "upload_images_bucket_lifecycle_configuration" {
+ bucket = "stg-lgtmeow-upload-images"
+ id = (known after apply)
+ rule {
+ id = "purge-old-object"
+ status = "Enabled"
+ abort_incomplete_multipart_upload {
+ days_after_initiation = 1
}
+ expiration {
+ days = 10
+ expired_object_delete_marker = (known after apply)
}
}
}
# module.images.aws_s3_bucket_versioning.cat_images_bucket_versioning will be created
+ resource "aws_s3_bucket_versioning" "cat_images_bucket_versioning" {
+ bucket = "stg-lgtmeow-cat-images"
+ id = (known after apply)
+ versioning_configuration {
+ mfa_delete = (known after apply)
+ status = "Enabled"
}
}
# module.images.aws_s3_bucket_versioning.created_lgtm_images_bucket_versioning will be created
+ resource "aws_s3_bucket_versioning" "created_lgtm_images_bucket_versioning" {
+ bucket = "stg-lgtmeow-created-lgtm-images"
+ id = (known after apply)
+ versioning_configuration {
+ mfa_delete = (known after apply)
+ status = "Suspended"
}
}
# module.images.aws_s3_bucket_versioning.lgtm_images_bucket_versioning will be created
+ resource "aws_s3_bucket_versioning" "lgtm_images_bucket_versioning" {
+ bucket = "stg-lgtmeow-images"
+ id = (known after apply)
+ versioning_configuration {
+ mfa_delete = (known after apply)
+ status = "Enabled"
}
}
# module.images.aws_s3_bucket_versioning.upload_images_bucket_versioning will be created
+ resource "aws_s3_bucket_versioning" "upload_images_bucket_versioning" {
+ bucket = "stg-lgtmeow-upload-images"
+ id = (known after apply)
+ versioning_configuration {
+ mfa_delete = (known after apply)
+ status = "Suspended"
}
}
Plan: 13 to add, 0 to change, 0 to destroy.
terraform import を実行
公式ドキュメントに下記の通り記載されているため terraform import
を実行します。
While it is not strictly necessary to import new aws_s3_bucket_* resources where the updated configuration matches the configuration used in previous versions of the AWS provider, skipping this step will lead to a diff in the first plan after a configuration change indicating that any new aws_s3_bucket_* resources will be created, making it more difficult to determine whether the appropriate actions will be taken.
下記のようなスクリプトを用意して実行します。
terraform import module.images.aws_s3_bucket_acl.upload_images_bucket_acl stg-lgtmeow-upload-images,private
terraform import module.images.aws_s3_bucket_lifecycle_configuration.upload_images_bucket_lifecycle_configuration stg-lgtmeow-upload-images
terraform import module.images.aws_s3_bucket_versioning.upload_images_bucket_versioning stg-lgtmeow-upload-images
terraform import module.images.aws_s3_bucket_acl.cat_images_bucket_acl stg-lgtmeow-cat-images,private
terraform import module.images.aws_s3_bucket_lifecycle_configuration.cat_images_bucket_lifecycle_configuration stg-lgtmeow-cat-images
terraform import module.images.aws_s3_bucket_versioning.cat_images_bucket_versioning stg-lgtmeow-cat-images
terraform import module.images.aws_s3_bucket_acl.created_lgtm_images_bucket_acl stg-lgtmeow-created-lgtm-images,private
terraform import module.images.aws_s3_bucket_lifecycle_configuration.created_lgtm_images_bucket_lifecycle_configuration stg-lgtmeow-created-lgtm-images
terraform import module.images.aws_s3_bucket_versioning.created_lgtm_images_bucket_versioning stg-lgtmeow-created-lgtm-images
terraform import module.images.aws_s3_bucket_acl.lgtm_images_bucket_acl stg-lgtmeow-images,private
terraform import module.images.aws_s3_bucket_lifecycle_configuration.lgtm_images_bucket_lifecycle_configuration stg-lgtmeow-images
terraform import module.images.aws_s3_bucket_versioning.lgtm_images_bucket_versioning stg-lgtmeow-images
terraform import module.images.aws_s3_bucket_lifecycle_configuration.lgtm_images_access_logs_lifecycle_configuration stg-lgtmeow-images-logs
importが成功したのでterraform plan
を実行すると、下記の差分が発生しました。
Terraform will perform the following actions:
# module.images.aws_s3_bucket_lifecycle_configuration.cat_images_bucket_lifecycle_configuration will be updated in-place
~ resource "aws_s3_bucket_lifecycle_configuration" "cat_images_bucket_lifecycle_configuration" {
id = "stg-lgtmeow-cat-images"
# (1 unchanged attribute hidden)
~ rule {
~ id = "tf-s3-lifecycle-20210418134531800400000005" -> "purge-noncurrent-version"
# (1 unchanged attribute hidden)
- filter {
}
# (2 unchanged blocks hidden)
}
}
# module.images.aws_s3_bucket_lifecycle_configuration.created_lgtm_images_bucket_lifecycle_configuration will be updated in-place
~ resource "aws_s3_bucket_lifecycle_configuration" "created_lgtm_images_bucket_lifecycle_configuration" {
id = "stg-lgtmeow-created-lgtm-images"
# (1 unchanged attribute hidden)
~ rule {
~ id = "tf-s3-lifecycle-20210418134531254200000003" -> "purge-old-version"
# (1 unchanged attribute hidden)
- filter {
}
# (2 unchanged blocks hidden)
}
}
# module.images.aws_s3_bucket_lifecycle_configuration.lgtm_images_access_logs_lifecycle_configuration will be updated in-place
~ resource "aws_s3_bucket_lifecycle_configuration" "lgtm_images_access_logs_lifecycle_configuration" {
id = "stg-lgtmeow-images-logs"
# (1 unchanged attribute hidden)
~ rule {
~ id = "tf-s3-lifecycle-20210418134531415700000004" -> "abort-incomplete-multipart-upload"
# (1 unchanged attribute hidden)
- filter {
}
# (1 unchanged block hidden)
}
}
# module.images.aws_s3_bucket_lifecycle_configuration.lgtm_images_bucket_lifecycle_configuration will be updated in-place
~ resource "aws_s3_bucket_lifecycle_configuration" "lgtm_images_bucket_lifecycle_configuration" {
id = "stg-lgtmeow-images"
# (1 unchanged attribute hidden)
~ rule {
~ id = "tf-s3-lifecycle-20210418134531206000000002" -> "purge-noncurrent-version"
# (1 unchanged attribute hidden)
- filter {
}
# (2 unchanged blocks hidden)
}
}
# module.images.aws_s3_bucket_lifecycle_configuration.upload_images_bucket_lifecycle_configuration will be updated in-place
~ resource "aws_s3_bucket_lifecycle_configuration" "upload_images_bucket_lifecycle_configuration" {
id = "stg-lgtmeow-upload-images"
# (1 unchanged attribute hidden)
~ rule {
~ id = "tf-s3-lifecycle-20210418134531195400000001" -> "purge-old-object"
# (1 unchanged attribute hidden)
- filter {
}
# (2 unchanged blocks hidden)
}
}
Plan: 0 to add, 5 to change, 0 to destroy.
これはaws_s3_bucket_lifecycle_configuration
の rule
にはidを追加した影響です。
変更しても問題ない内容のため、terraform apply
を実行して完了です。
補足
もし間違えてterraform import
してしまった場合はstate rm
を実行することで取り消しできます。
terraform state rm module.images.aws_s3_bucket_acl.upload_images_bucket_acl
参考:Command: state rm | Terraform by HashiCorp
Discussion