🛠️

Terraform AWS Provider v4 にアップグレード

2022/03/18に公開

概要

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に出力されます。

main_new_resources.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_configurationrule には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.

下記のようなスクリプトを用意して実行します。

s3_import.sh
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_configurationrule には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