🦁

Terraform AWS Provider v4へのアップデート

2022/03/12に公開

TerraformのAWSProviderv4がリリースされました。
v4へのアップグレードと、アップグレードに伴う破壊的変更に対応したので手順をまとめます。
公式のアップグレードガイドはこちらです。

破壊的変更について

aws_s3_bucketリソースまわりが大きく変わっています。
aws_s3_bucketのattributeとして指定していたところ(ライフサイクルルールとかバージョニングとか)が独立したリソースとして扱われるようになりました。

手順

サンプルとして以下のtfファイルを用意してapplyしておきます。
ちなみにaws_s3_bucketのattributeを指定しないとランダムな名前でS3バケットが作成されます。

provider.tf
provider "aws" {
  region = "ap-northeast-1"
}
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.74.3"

    }
  }
}
main.tf
resource "aws_s3_bucket" "normal" {
}

resource "aws_s3_bucket" "server_side_encryption" {
  server_side_encryption_configuration {
    rule {
      apply_server_side_encryption_by_default {
        sse_algorithm = "AES256"
      }
    }
  }
}

resource "aws_s3_bucket" "lifecycle" {
  lifecycle_rule {
    enabled = true
    expiration {
      days = "90"
    }
  }
}

resource "aws_s3_bucket" "acl" {
  acl = "private"
}

providerのアップグレード

まずtfファイル内で指定するaws providerのバージョンを4系に変更します。
2022年3月12日時点で最新が4.5.0なので、4.5.0以上の4系で指定しておきます。

provider.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
+     version = "~> 4.5.0"
-     version = "~> 3.74.3"

    }
  }
}

修正後、terraform init --upgradeコマンドを実行します。

% terraform init --upgrade

Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 4.5.0"...
- Installing hashicorp/aws v4.5.0...
- Installed hashicorp/aws v4.5.0 (signed by HashiCorp)

Terraform has made some changes to the provider dependency selections recorded
in the .terraform.lock.hcl file. Review those changes and commit them to your
version control system if they represent changes you intended to make.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

試しにterraform plan

この状態でterraform planするとv4の記法に則してないので構文エラーになります。

% terraform plan          
╷
│ Error: Value for unconfigurable attribute
│ 
│   with aws_s3_bucket.server_side_encryption,
│   on main.tf line 4, in resource "aws_s3_bucket" "server_side_encryption":4: resource "aws_s3_bucket" "server_side_encryption" {
│ 
│ Can't configure a value for "server_side_encryption_configuration": its value will be decided automatically based on the result of applying this configuration.
╵
╷
│ Error: Value for unconfigurable attribute
│ 
│   with aws_s3_bucket.lifecycle,
│   on main.tf line 14, in resource "aws_s3_bucket" "lifecycle":
│   14: resource "aws_s3_bucket" "lifecycle" {
│ 
│ 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 aws_s3_bucket.acl,
│   on main.tf line 24, in resource "aws_s3_bucket" "acl":24:   acl = "private"
│ 
│ Can't configure a value for "acl": its value will be decided automatically based on the result of applying this configuration.

コードの修正

tfrefactorというツールを使うと既存コードをv4準拠の形に直してくれます。

インストール

wget https://github.com/anGie44/ohmyhcl/releases/download/v0.1.0/tfrefactor_0.1.0_linux_amd64.zip
unzip tfrefactor_0.1.0_linux_amd64.zip
sudo chmod 755 tfrefactor
sudo mv tfrefactor /usr/local/bin

% tfrefactor --help   
Usage: tfrefactor [--version] [--help] <command> [<args>]

Available commands are:
    resource    Refactor resource arguments to individual resources

tfrefactor resource aws_s3_bucket main.tfするとmain_migrated.tfというファイルが生成されます。
aws_s3_bucket内のattributeが消えて、新しいリソースが追加されています。

main_migrated.tf
resource "aws_s3_bucket" "normal" {
}

resource "aws_s3_bucket" "server_side_encryption" {
}

resource "aws_s3_bucket" "lifecycle" {
}

resource "aws_s3_bucket" "acl" {
}

resource "aws_s3_bucket_server_side_encryption_configuration" "server_side_encryption_server_side_encryption_configuration" {
  bucket = aws_s3_bucket.server_side_encryption.id
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

resource "aws_s3_bucket_lifecycle_configuration" "lifecycle_lifecycle_configuration" {
  bucket = aws_s3_bucket.lifecycle.id
  rule {
    status = "Enabled"
    expiration {
      days = "90"
    }
  }
}

resource "aws_s3_bucket_acl" "acl_acl" {
  bucket = aws_s3_bucket.acl.id
  acl    = "private"
}

再びterraform plan

元のファイル(main.tf)が残ったままだとリソース名が重複してしまいエラーになるのでリネームしてからterraform planします。

% mv main.tf main.tf_bk
% terraform plan
╷
│ Error: Missing required argument
│ 
│   on main_migrated.tf line 27, in resource "aws_s3_bucket_lifecycle_configuration" "lifecycle_lifecycle_configuration":27:   rule {
│ 
│ The argument "id" is required, but no definition was found.

idが足りずエラーになります。。
idというのはルールの一意な識別子で画面だと以下の赤枠の個所にあたります。

ひとまず一意であればなんでも良いので適当に追加します。

main_migrated.tf
resource "aws_s3_bucket_lifecycle_configuration" "lifecycle_lifecycle_configuration" {
  bucket = aws_s3_bucket.lifecycle.id
  rule {
+   id     = "v4test"
    status = "Enabled"
    expiration {
      days = "90"
    }
  }
}

三度terraform plan

三度目の正直で通りました。新しく3リソースが作成されます。

% tf plan          
aws_s3_bucket.normal: Refreshing state... [id=terraform-20220312121026484500000004]
aws_s3_bucket.acl: Refreshing state... [id=terraform-20220312121026483400000003]
aws_s3_bucket.server_side_encryption: Refreshing state... [id=terraform-20220312121026475200000001]
aws_s3_bucket.lifecycle: Refreshing state... [id=terraform-20220312121026481600000002]

Note: Objects have changed outside of Terraform

Terraform detected the following changes made outside of Terraform since the last "terraform apply":

  # aws_s3_bucket.acl has changed
  ~ resource "aws_s3_bucket" "acl" {
        id                                   = "terraform-20220312121026483400000003"
      + object_lock_enabled                  = false
      + tags                                 = {}
        # (18 unchanged attributes hidden)
    }

  # aws_s3_bucket.lifecycle has changed
  ~ resource "aws_s3_bucket" "lifecycle" {
        id                                   = "terraform-20220312121026481600000002"
      ~ lifecycle_rule                       = [
          ~ {
              ~ tags                                   = null -> {}
                # (8 unchanged elements hidden)
            },
        ]
      + object_lock_enabled                  = false
      + tags                                 = {}
        # (17 unchanged attributes hidden)
    }

  # aws_s3_bucket.normal has changed
  ~ resource "aws_s3_bucket" "normal" {
        id                                   = "terraform-20220312121026484500000004"
      + object_lock_enabled                  = false
      + tags                                 = {}
        # (18 unchanged attributes hidden)
    }

  # aws_s3_bucket.server_side_encryption has changed
  ~ resource "aws_s3_bucket" "server_side_encryption" {
        id                                   = "terraform-20220312121026475200000001"
      + object_lock_enabled                  = false
      + tags                                 = {}
        # (18 unchanged attributes hidden)
    }


Unless you have made equivalent changes to your configuration, or ignored the relevant attributes using ignore_changes, the following plan may include actions to undo or respond to these changes.

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

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:

  # aws_s3_bucket_acl.acl_acl will be created
  + resource "aws_s3_bucket_acl" "acl_acl" {
      + acl    = "private"
      + bucket = "terraform-20220312121026483400000003"
      + 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)
            }
        }
    }

  # aws_s3_bucket_lifecycle_configuration.lifecycle_lifecycle_configuration will be created
  + resource "aws_s3_bucket_lifecycle_configuration" "lifecycle_lifecycle_configuration" {
      + bucket = "terraform-20220312121026481600000002"
      + id     = (known after apply)

      + rule {
          + id     = "v4test"
          + status = "Enabled"

          + expiration {
              + days                         = 90
              + expired_object_delete_marker = (known after apply)
            }
        }
    }

  # aws_s3_bucket_server_side_encryption_configuration.server_side_encryption_server_side_encryption_configuration will be created
  + resource "aws_s3_bucket_server_side_encryption_configuration" "server_side_encryption_server_side_encryption_configuration" {
      + bucket = "terraform-20220312121026475200000001"
      + id     = (known after apply)

      + rule {
          + apply_server_side_encryption_by_default {
              + sse_algorithm = "AES256"
            }
        }
    }

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

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.

terraform apply

applyも問題なく通りました。これでアップグレード完了です。

% terraform apply
aws_s3_bucket.server_side_encryption: Refreshing state... [id=terraform-20220312121026475200000001]
aws_s3_bucket.normal: Refreshing state... [id=terraform-20220312121026484500000004]
aws_s3_bucket.acl: Refreshing state... [id=terraform-20220312121026483400000003]
aws_s3_bucket.lifecycle: Refreshing state... [id=terraform-20220312121026481600000002]

Note: Objects have changed outside of Terraform

Terraform detected the following changes made outside of Terraform since the last "terraform apply":

  # aws_s3_bucket.acl has changed
  ~ resource "aws_s3_bucket" "acl" {
        id                                   = "terraform-20220312121026483400000003"
      + object_lock_enabled                  = false
      + tags                                 = {}
        # (18 unchanged attributes hidden)
    }

  # aws_s3_bucket.lifecycle has changed
  ~ resource "aws_s3_bucket" "lifecycle" {
        id                                   = "terraform-20220312121026481600000002"
      ~ lifecycle_rule                       = [
          ~ {
              ~ tags                                   = null -> {}
                # (8 unchanged elements hidden)
            },
        ]
      + object_lock_enabled                  = false
      + tags                                 = {}
        # (17 unchanged attributes hidden)
    }

  # aws_s3_bucket.normal has changed
  ~ resource "aws_s3_bucket" "normal" {
        id                                   = "terraform-20220312121026484500000004"
      + object_lock_enabled                  = false
      + tags                                 = {}
        # (18 unchanged attributes hidden)
    }

  # aws_s3_bucket.server_side_encryption has changed
  ~ resource "aws_s3_bucket" "server_side_encryption" {
        id                                   = "terraform-20220312121026475200000001"
      + object_lock_enabled                  = false
      + tags                                 = {}
        # (18 unchanged attributes hidden)
    }


Unless you have made equivalent changes to your configuration, or ignored the relevant attributes using ignore_changes, the following plan may include actions to undo or respond to these changes.

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

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:

  # aws_s3_bucket_acl.acl_acl will be created
  + resource "aws_s3_bucket_acl" "acl_acl" {
      + acl    = "private"
      + bucket = "terraform-20220312121026483400000003"
      + 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)
            }
        }
    }

  # aws_s3_bucket_lifecycle_configuration.lifecycle_lifecycle_configuration will be created
  + resource "aws_s3_bucket_lifecycle_configuration" "lifecycle_lifecycle_configuration" {
      + bucket = "terraform-20220312121026481600000002"
      + id     = (known after apply)

      + rule {
          + id     = "v4test"
          + status = "Enabled"

          + expiration {
              + days                         = 90
              + expired_object_delete_marker = (known after apply)
            }
        }
    }

  # aws_s3_bucket_server_side_encryption_configuration.server_side_encryption_server_side_encryption_configuration will be created
  + resource "aws_s3_bucket_server_side_encryption_configuration" "server_side_encryption_server_side_encryption_configuration" {
      + bucket = "terraform-20220312121026475200000001"
      + id     = (known after apply)

      + rule {
          + apply_server_side_encryption_by_default {
              + sse_algorithm = "AES256"
            }
        }
    }

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

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_s3_bucket_server_side_encryption_configuration.server_side_encryption_server_side_encryption_configuration: Creating...
aws_s3_bucket_acl.acl_acl: Creating...
aws_s3_bucket_lifecycle_configuration.lifecycle_lifecycle_configuration: Creating...
aws_s3_bucket_acl.acl_acl: Creation complete after 0s [id=terraform-20220312121026483400000003,private]
aws_s3_bucket_server_side_encryption_configuration.server_side_encryption_server_side_encryption_configuration: Creation complete after 0s [id=terraform-20220312121026475200000001]
aws_s3_bucket_lifecycle_configuration.lifecycle_lifecycle_configuration: Still creating... [10s elapsed]
aws_s3_bucket_lifecycle_configuration.lifecycle_lifecycle_configuration: Still creating... [20s elapsed]
aws_s3_bucket_lifecycle_configuration.lifecycle_lifecycle_configuration: Still creating... [30s elapsed]
aws_s3_bucket_lifecycle_configuration.lifecycle_lifecycle_configuration: Creation complete after 31s [id=terraform-20220312121026481600000002]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

ライフサイクルルール名(id)も変わってました。

Discussion