Terraform AWS Provider Version 4がリリースされました

2022.02.22

2022/2/11に Terraform AWS Provider Version 4がリリースされました。2/22現在、もうVersion 4.2まででています。実際に触ってみて何が変わったのか確認したいと思います。

aws_s3_bucketの大規模リファクタリング

要は「aws_s3_bucketがデカくなりすぎて大変だから、細かく分けようぜ!」ということです。

御存知の通りS3は非常に多機能です。そしてTerraformではその機能の殆どをaws_s3_bucketのattributeで設定していました。

以下はaws_s3_bucketのコード例です。

resource "aws_s3_bucket" "v3" {
  bucket = local.bucket_name

  acceleration_status = "Enabled"

  acl = "private"

  cors_rule {
    allowed_headers = ["*"]
    allowed_methods = ["PUT", "POST"]
    allowed_origins = ["https://s3-website-test.hashicorp.com"]
    expose_headers  = ["ETag"]
    max_age_seconds = 3000
  }

  # `acl`とコンフリクトするためコメントアウト  
  #   grant {
  #     type        = "Group"
  #     permissions = ["READ_ACP", "WRITE"]
  #     uri         = "http://acs.amazonaws.com/groups/s3/LogDelivery"
  #   }

  lifecycle_rule {
    id      = "log"
    enabled = true
    prefix  = "log/"
    tags = {
      rule      = "log"
      autoclean = "true"
    }
    transition {
      days          = 30
      storage_class = "STANDARD_IA"
    }
    transition {
      days          = 60
      storage_class = "GLACIER"
    }
    expiration {
      days = 90
    }
  }

  logging {
    target_bucket = aws_s3_bucket.log_bucket.id
    target_prefix = "log/"
  }

#   `replication_configuration`とコンフリクトするためコメントアウト 
#   object_lock_configuration {
#     object_lock_enabled = "Enabled"
# 
#     rule {
#       default_retention {
#         mode = "COMPLIANCE"
#         days = 3
#       }
#     }
#   }

  policy = <<-EOF
    {
    "Id": "Policy1446577137248",
    "Statement": [
        {
        "Action": "s3:PutObject",
        "Effect": "Allow",
        "Principal": {
            "AWS": "${data.aws_elb_service_account.current.arn}"
        },
        "Resource": "arn:${data.aws_partition.current.partition}:s3:::${local.bucket_name}/*",
        "Sid": "Stmt1446575236270"
        }
    ],
    "Version": "2012-10-17"
    }
  EOF

  replication_configuration {
    role = aws_iam_role.replication.arn
    rules {
      id     = "foobar"
      status = "Enabled"
      filter {
        tags = {}
      }
      destination {
        bucket        = aws_s3_bucket.destination.arn
        storage_class = "STANDARD"
        replication_time {
          status  = "Enabled"
          minutes = 15
        }
        metrics {
          status  = "Enabled"
          minutes = 15
        }
      }
    }
  }

  request_payer = "Requester"

  server_side_encryption_configuration {
    rule {
      apply_server_side_encryption_by_default {
        kms_master_key_id = aws_kms_key.mykey.arn
        sse_algorithm     = "aws:kms"
      }
    }
  }

  versioning {
    enabled = true
  }

  website {
    index_document = "index.html"
    error_document = "error.html"
  }
}

まあ実際こんなヘンテコな設定することは無いと思いますが、上記で書いたattributeのうち、一番上のbucket以外は今回リリースされたv4ではエラーになります。

v4でのterraform apply結果

% terraform apply
╷
│ Error: Value for unconfigurable attribute
│ 
│   with aws_s3_bucket.v3,
│   on s3.tf line 5, in resource "aws_s3_bucket" "v3":
│    5: resource "aws_s3_bucket" "v3" {
│ 
│ Can't configure a value for "server_side_encryption_configuration": its value will be decided
│ automatically based on the result of applying this configuration.
╵
(以下略)

こんな感じのCan't configure a value for 〜というエラーが各attributeにて発生します。

v4では各Attribute毎にresourceが用意されているので、こちらを使います。

  resource "aws_s3_bucket" "v4" {
    bucket = local.bucket_name
  
-   server_side_encryption_configuration {
-     rule {
-       apply_server_side_encryption_by_default {
-         kms_master_key_id = aws_kms_key.mykey.arn
-         sse_algorithm     = "aws:kms"
-       }
-     }
-   }
  }
  
+ resource "aws_s3_bucket_server_side_encryption_configuration" "v4" {
+   bucket = aws_s3_bucket.v4.id
+ 
+   rule {
+     apply_server_side_encryption_by_default {
+       kms_master_key_id = aws_kms_key.mykey.arn
+       sse_algorithm     = "aws:kms"
+     }
+   }
+ }

前述のエラーメッセージにも記述がありますが、今回エラーになるようになったaws_s3_bucketの各Attributeはリソース作成後、v4でも参照用途としては使用できます。

v3でserver_side_encryption有効化で作成したバケットをv4にアップグレードした直後

% terraform state show aws_s3_bucket.v3
# aws_s3_bucket.v3:
resource "aws_s3_bucket" "v3" {
    (略)
    server_side_encryption_configuration = [
        {
            rule = [
                {
                    apply_server_side_encryption_by_default = [
                        {
                            kms_master_key_id = "arn:aws:kms:ap-northeast-1:012345678901:key/beadbbd7-7add-4608-9cbc-f9dd91c34ecc"
                            sse_algorithm     = "aws:kms"
                        },
                    ]
                    bucket_key_enabled                      = false
                },
            ]
        },
    ]
    (略)
}

ですのでv3からv4にアップグレードする際は、アップグレード後に新しいリソースを書いて、importを行なってください。

% terraform import aws_s3_bucket_server_side_encryption_configuration.sample test20220220
aws_s3_bucket_server_side_encryption_configuration.sample: Importing from ID "test20220220"...
aws_s3_bucket_server_side_encryption_configuration.sample: Import prepared!
  Prepared aws_s3_bucket_server_side_encryption_configuration for import
aws_s3_bucket_server_side_encryption_configuration.sample: Refreshing state... [id=test20220220]

Import successful!

The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.

デフォルトリソースの作成削除が可能に

これまで参照と更新しかできなかったデフォルトリソース(aws_default_vpc,aws_default_subnet) に対して作成と削除もできるようになりました、だそうですがデフォルトリソースをTerraformで扱ったことがなかったのでピンときませんでした。実際にaws_default_vpcを触ってみました。

v3

v3では、デフォルトのVPCが存在している場合、(各リージョンに1つしか作成できないので)新たにデフォルトVPCを作成することはせず、

  • tags
  • enable_dns_support
  • enable_dns_hostnames
  • enable_classiclink

の更新のみ可能です。

resource "aws_default_vpc" "default" {
  tags = {
    Name = "hoge hoge"
  }
}

そして、aws_default_vpcリソースのコードを削除してapplyすると、

- resource "aws_default_vpc" "default" {
-   tags = {
-     Name = "hoge hoge"
-   }
- }

TerraformのStateからはaws_default_vpcは削除されますが、実際のリソースはそのまま残り続けます。(設定したタグも残ります)

また、デフォルトVPCが存在していない場合にこのリソースを使うとError: No default VPC found in this region.というエラーになります。

v4

v4でも一部設定を更新できる、destoryしてもstateから消えるだけでリソース削除はされないは同じです。

違うのは2つ。まずデフォルトVPCが存在していない場合にこのリソースを使ってapplyすると、新たにデフォルトVPCを作成します。作成時にサブネットなどの紐づくリソースも併せて作成されます。

もう一つは、新たにforce_destroyattributeが追加されています。これをtrueにすると…

resource "aws_default_vpc" "default" {
  force_destroy = true
}

この状態でapplyしても特に何も変わりません。が、このコードを削除した時、

- resource "aws_default_vpc" "default" {
-   force_destroy = true
- }

stateから消えるだけでなくリソースも削除されるようになります。なお、事前にデフォルトVPCに紐づくサブネット、インターネットゲートウェイ、デフォルト以外のセキュリティグループなどのリソースを削除しておく必要があります。

Provider設定の改良

Providerブロックのargumentsが色々増えています。ざっと眺めて見ましたが、普段使いそうなのはありませんでした。特殊な要件がある場合に役立つかもしれません。以下に新argumentsの一覧があるのでご確認ください。

複数値を返すData Sourceで値がゼロになってもエラーにならないように

aws_security_groupsのような、同種リソースを複数個配列型にして持つData Sourceがあります。v3では1つもリソースを取得できなかった場合はエラーを返します。v4ではエラーにならなくなりました。

何もヒットしないData Source

data "aws_security_groups" "test" {
  tags = {
    Name = "notexist"
  }
}

v3でのapply結果

% terraform apply
╷
│ Error: Your query returned no results. Please change your search criteria and try again.
│ 
│   with data.aws_security_groups.test,
│   on plural.tf line 1, in data "aws_security_groups" "test":
│    1: data "aws_security_groups" "test" {
│ 
╵

v4でのapply結果

% terraform apply        

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found
no differences, so no changes are needed.

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
% terraform state list
data.aws_security_groups.test
% terraform state show data.aws_security_groups.test
# data.aws_security_groups.test:
data "aws_security_groups" "test" {
    arns    = []
    id      = "ap-northeast-1"
    ids     = []
    tags    = {
        "Name" = "notexist"
    }
    vpc_ids = []
}

これを利用して、該当セキュリティグループがあるときだけリソースを作成する、なければしない(あるいはその逆)みたいなことができますね。

resource "aws_alb" "test" {
  count = (length(data.aws_security_groups.test.ids) > 0) ? 1 : 0

  name               = "test"
  internal           = false
  load_balancer_type = "application"
  subnets            = [for subnet in aws_default_subnet.default: subnet.id]
  security_groups    = data.aws_security_groups.test.ids
}

認証方法の変更

AWS Providerの認証方法は大きく分けて3つあります。そして上から順に優先して使われます。

  1. providerブロック内で指定する
    • profileとかassume_roleといったarugumentを使う方法です。
  2. 環境変数
    • AWS_ACCESS_KEY_IDとかAWS_SECRET_ACCESS_KEYとかAWS_PROFILEとかですね。
  3. クレデンシャル/コンフィグファイル
    • ~/.aws/credentials/.aws/configに書かれている内容を使います。

v3では上記1から設定されているか調べて、その値が無効な場合、次の2の設定を調べます。2も無効な場合は3を調べます。というように「無効だったら次の優先度の設定を調べる」という挙動になっています。

v4では1から設定されているか調べて、「設定されているけど無効だったら即エラーを返す」という挙動に変更になりました。SDKやCLIと挙動を揃える意図だそうです。

空文字指定の非推奨

リソースのattribute値に空文字""を入れるとそのattributeは未指定と解釈されデフォルト値が入るという挙動をするリソースが色々あります。v4からは多くのリソースで空文字を指定するとエラーになるようになりました。代わりにnullを指定するか、そもそもそのattributeを使わないようにしてくださいとのことです。

参考情報