
Terraform 1.11がGAになりました
Terraformのversion 1.11が2025/2/27にGA(一般提供開始)になりました。新機能3つを紹介します。
write-only attributes が追加された
write-only attributesは、1つ前のバージョンであるv1.10で追加されたEphemeral Valuesの一機能です。v1.10の段階で「v1.11でwrite-only attributesを追加する」と告知されており、今回リリースされたv1.11で追加されました。
Ephemeral Valuesについてご存じない方は一度以下をご確認ください。
Ephemeral Valuesの中のEphemeral resourcesは、いわばData SourceのEphemeral版でした。今回のwrite-only attributeは、resourceのattributeのEphemeral版です。つまりリソース作成時に値を指定できますが、stateファイルには残りません。
コード例
AWSのParameter Storeを使います。 value_wo
がwrite-only attributeです。使用する際はvalue_wo_version
も併せて指定する必要があります。
resource "aws_ssm_parameter" "foo" {
name = "foo"
type = "SecureString"
value_wo = "bar"
value_wo_version = 1
}
こちらのコードでapplyした後に、
State ファイルの中身を確認する terraform state pull
コマンドを実行してみます。
value_wo
attribute値はnullになっていますね。
{
"version": 4,
"terraform_version": "1.11.0",
"serial": 3,
"lineage": "5d9ed96c-b8bf-a028-24d6-5aaaea655cf0",
"outputs": {},
"resources": [
{
"mode": "managed",
"type": "aws_ssm_parameter",
"name": "foo",
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"allowed_pattern": "",
"arn": "arn:aws:ssm:ap-northeast-1:123456789012:parameter/foo",
"data_type": "text",
"description": "",
"has_value_wo": true,
"id": "foo",
"insecure_value": null,
"key_id": "alias/aws/ssm",
"name": "foo",
"overwrite": null,
"tags": {},
"tags_all": {},
"tier": "Standard",
"type": "SecureString",
"value": "",
"value_wo": null,
"value_wo_version": 1,
"version": 1
},
"sensitive_attributes": [
[
{
"type": "get_attr",
"value": "value_wo"
}
],
[
{
"type": "get_attr",
"value": "value"
}
]
],
"private": "bnVsbA=="
}
]
}
],
"check_results": null
}
apply後に、以下のようにvalue指定値を変えたとします。
resource "aws_ssm_parameter" "foo" {
name = "foo"
type = "SecureString"
- value_wo = "bar"
+ value_wo = "modified"
value_wo_version = 1
}
この状態で再度applyしても変更は発生しません。
% terraform apply
aws_ssm_parameter.foo: Refreshing state... [id=foo]
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.
変更を適用するには value_wo_version
を変更する必要があります。value_wo_version
値はStateファイル内にあるので、Stateファイル内の値とコード内の値を比べて、異なっている場合のみ現在のコードで指定されている value_wo
値でvalue_wo
を更新する、という風になっています。
resource "aws_ssm_parameter" "foo" {
name = "foo"
type = "SecureString"
value_wo = "modified"
- value_wo_version = 1
+ value_wo_version = 2
}
value_wo_versionは今回の様に 1→2と順にインクリメントする必要は無いです。例えば 1000を指定しても更新は走ります。とにかく現在の値と違う値であれば良いようです。とはいえ1ずつインクリメントさせる、更新日をYYYYMMDD形式(例:20250302)にするなどの方がわかりやすいかと思います。
以下のように plan結果に value_wo
が更新されるということは記載されません。個人的にはちょっと気持ち悪いので記載して欲しいですね。
# aws_ssm_parameter.foo will be updated in-place
~ resource "aws_ssm_parameter" "foo" {
~ has_value_wo = true -> (known after apply)
id = "foo"
name = "foo"
tags = {}
~ value_wo_version = 1 -> 2
# (11 unchanged attributes hidden)
}
コード改良
resource "aws_ssm_parameter" "foo" {
name = "foo"
type = "SecureString"
value_wo = "bar"
value_wo_version = 1
}
ここまで例として使ってきた上記コードですが、write-only attributeを使うことによってStateファイルにはパラメーター値は残りませんが、コード内には記載されているのでGitに記録されてしまうのがイマイチですよね。実際に利用する際には以下のように ephemeral inputを使ってコマンド実行時に外部から値を指定するなどするのが良いと思います。
variable "foo_value" {
type = string
ephemeral = true
default = ""
}
resource "aws_ssm_parameter" "foo" {
name = "foo"
type = "SecureString"
value_wo = var.foo_value
value_wo_version = 1
}
初回は -var
オプションでパラメーター値を指定します。
% terraform apply -var 'foo_value=bar'
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_ssm_parameter.foo will be created
+ resource "aws_ssm_parameter" "foo" {
+ arn = (known after apply)
+ data_type = (known after apply)
+ has_value_wo = (known after apply)
+ id = (known after apply)
+ insecure_value = (known after apply)
+ key_id = (known after apply)
+ name = "foo"
+ tags_all = (known after apply)
+ tier = (known after apply)
+ type = "SecureString"
+ value = (sensitive value)
+ value_wo = (write-only attribute)
+ value_wo_version = 1
+ version = (known after apply)
}
Plan: 1 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_ssm_parameter.foo: Creating...
aws_ssm_parameter.foo: Creation complete after 0s [id=foo]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
以降は、パラメーター値を更新したいときは value_wo_version
を更新のうえ再度applyコマンドに-var
オプションを追加してパラメーター値を指定、
更新したくないときは単純に apply
すれば良いです。
AWS providerのwrite-only attributes 一覧
write-only attributeの名前は接尾辞として _wo
が付きます。
aws_ssm_parameter
のvalue_wo
aws_secretsmanager_secret_version
のsecret_string_wo
aws_db_instance
のpassword_wo
aws_rds_cluster
のmaster_password_wo
aws_redshift_cluster
のmaster_password_wo
aws_redshiftserverless_namespace
のadmin_user_password_wo
aws_docdb_cluster
のmaster_password_wo
※ 漏れがあったらゴメンナサイ
aws_db_instance
のドキュメントに 「it will be stored in the state file」の記述
気になった点: 前述のaws_db_instance
のpassword_wo
のドキュメントのpassword_wo
欄に、「it will be stored in the state file」との記載があります。
気になったので実際にインスタンス作成して terraform state pull
でStateファイルの中身を確認してみましたが、password_wo
は記録されていませんでした。ざっとこの「it will be stored in the state file」記述について調べてみたのですが特に情報が見つかりませんでした。何かご存知の方は教えていただけないでしょうか…
{
"mode": "managed",
"type": "aws_db_instance",
"name": "default",
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
"instances": [
{
(略)
"password": null,
"password_wo": null,
"password_wo_version": 1,
(略)
},
"sensitive_attributes": [
[
{
"type": "get_attr",
"value": "password_wo"
}
],
[
{
"type": "get_attr",
"value": "password"
}
]
],
(略)
}
]
}
S3 Native State Locking がGAに。 DynamoDBでのロックは deprecatedに。
1つ前の v1.10より S3 Native State Locking機能が experimental featureとして利用可能でした。 こちらが今回GAになっています。
この機能は、S3バックエンド上のStateファイルが破損しないように保護するものです。複数人が同時に terraform apply
コマンドを実行するなどしてStateファイルを同時更新しようとする際にファイル破損が起こり得ます。そこで、Stateファイルを更新中はファイルにロックをかけて、他のユーザーが更新を行えないようにする仕組みを提供します。(昔使っていた VSS(Visual SourceSafe)というバージョン管理ツールにこういう機能あったな…) このロック機能をS3だけで実現できるようになったのがS3 Native State Lockingです。
従来は同様のロック機能をDynamoDBを用いて実現していました。が、こちらはv1.10時点で将来deprecated化されることが告知されており、今回v1.11にて正式にdeprecatedとなりました。具体的には S3 backendブロックでのDynamoDBロック関連のattributesがdeprecatedとなっています。将来的には利用不可になりますのでS3 Native State Lockingへ移行しましょう。
コード例
S3 Native State Lockingのコード例です。最後のuse_lockfile = true
のみが本機能利用に必要なattributeです。とても簡単ですね。
terraform {
required_version = "= 1.11.0"
backend "s3" {
bucket = "(バケット名)"
key = "s3-native-state-locking-test.tfstate"
region = "ap-northeast-1"
use_lockfile = true
}
}
ロック例
terraform apply
中に${stateファイルオブジェクトキー名}.tflock
というファイル名のロックファイルが作成され、コマンド終了後に削除されました。
S3 Native State Lockingを使う場合、各種terraformコマンドを実施するIAMエンティティにこのロックファイルオブジェクトの更新権限を付与する必要があることに注意です。以下はIAM Policy例です。最後のブロックでロックファイルに関する権限を付与しています。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::mybucket",
"Condition": {
"StringEquals": {
"s3:prefix": "mybucket/path/to/my/key"
}
}
},
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": [
"arn:aws:s3:::mybucket/path/to/my/key"
]
},
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
"Resource": [
"arn:aws:s3:::mybucket/path/to/my/key.tflock"
]
}
]
}
※ S3 Bucket Permissions | Backend Type: s3 | Terraform | HashiCorp Developer より引用
ちなみに terraform plan
でもロックがかかります。plan時に使うIAMエンティティにReadOnly権限のみを付与していた場合、ロックファイル作成に失敗してエラーになります。 terraform plan -lock=false
で回避できます。
terraform testコマンドで JUnit XML フォーマットでテストレポートを作成できるようになった
以下のチュートリアルのテストコードのTerraformバージョンを 1.11.0に上げ、-junit-xml
オプションを使ってJUnit XML フォーマットのテストレポートを作成してみました。
テストコードは以下のようになっています。
# Call the setup module to create a random bucket prefix
run "setup_tests" {
module {
source = "./tests/setup"
}
}
# Apply run block to create the bucket
run "create_bucket" {
variables {
bucket_name = "${run.setup_tests.bucket_prefix}-aws-s3-website-test"
}
# Check that the bucket name is correct
assert {
condition = aws_s3_bucket.s3_bucket.bucket == "${run.setup_tests.bucket_prefix}-aws-s3-website-test"
error_message = "Invalid bucket name"
}
# Check index.html hash matches
assert {
condition = aws_s3_object.index.etag == filemd5("./www/index.html")
error_message = "Invalid eTag for index.html"
}
# Check error.html hash matches
assert {
condition = aws_s3_object.error.etag == filemd5("./www/error.html")
error_message = "Invalid eTag for error.html"
}
}
コマンド実行します。
% terraform test -junit-xml=testreport.xml
tests/website.tftest.hcl... in progress
run "setup_tests"... pass
run "create_bucket"... pass
tests/website.tftest.hcl... tearing down
tests/website.tftest.hcl... pass
Success! 2 passed, 0 failed.
以下のテストレポートが作成されました。
<?xml version="1.0" encoding="UTF-8"?><testsuites>
<testsuite name="tests/website.tftest.hcl" tests="2" skipped="0" failures="0" errors="0">
<testcase name="setup_tests" classname="tests/website.tftest.hcl" time="0.31946" timestamp="2025-03-03T02:30:38Z"></testcase>
<testcase name="create_bucket" classname="tests/website.tftest.hcl" time="14.114209" timestamp="2025-03-03T02:30:39Z"></testcase>
</testsuite>
</testsuites>
以下のようにしてテストをわざと失敗させてみます。
# Check that the bucket name is correct
assert {
+ condition = aws_s3_bucket.s3_bucket.bucket == "error"
- condition = aws_s3_bucket.s3_bucket.bucket == "${run.setup_tests.bucket_prefix}-aws-s3-website-test"
error_message = "Invalid bucket name"
}
テスト実行結果です。
% terraform test -junit-xml=testreport.xml
tests/website.tftest.hcl... in progress
run "setup_tests"... pass
run "create_bucket"... fail
╷
│ Error: Test assertion failed
│
│ on tests/website.tftest.hcl line 16, in run "create_bucket":
│ 16: condition = aws_s3_bucket.s3_bucket.bucket == "error"
│ ├────────────────
│ │ aws_s3_bucket.s3_bucket.bucket is "daily-manually-sharp-catfish-aws-s3-website-test"
│
│ Invalid bucket name
╵
tests/website.tftest.hcl... tearing down
tests/website.tftest.hcl... fail
Failure! 1 passed, 1 failed.
テストレポートにも詳細なエラー内容が記載されました。
<?xml version="1.0" encoding="UTF-8"?><testsuites>
<testsuite name="tests/website.tftest.hcl" tests="2" skipped="0" failures="1" errors="0">
<testcase name="setup_tests" classname="tests/website.tftest.hcl" time="0.233125" timestamp="2025-03-03T02:38:21Z"></testcase>
<testcase name="create_bucket" classname="tests/website.tftest.hcl" time="10.936528" timestamp="2025-03-03T02:38:21Z">
<failure message="1 of 3 assertions failed: Invalid bucket name"><![CDATA[
Error: Test assertion failed
on tests/website.tftest.hcl line 16, in run "create_bucket":
16: condition = aws_s3_bucket.s3_bucket.bucket == "error"
├────────────────
│ aws_s3_bucket.s3_bucket.bucket is "daily-manually-sharp-catfish-aws-s3-website-test"
Invalid bucket name
]]></failure>
</testcase>
</testsuite>
</testsuites>
この機能により、CI/CDツールとのスムーズな連携、JUnit XML フォーマットに対応している他システムやフレームワークのテスト結果との統合・集約などが期待できます。
感想
write-only attributesは今後使えるところはどんどん使っていきたいです。機密情報がStateファイルに載っていて嬉しいことは特に無いので。
S3 Native State Lockingも良いですね!私はこれまでもあったDynamoDBによるロック機能はDynamoDBテーブルの用意が面倒であまり利用していませんでした。がこの機能だと追加で用意する必要のあるリソースが無いので、手間無しにより安全にTerraformを利用できるようになるので、積極的に使っていきたいと思います。
JUnit XML フォーマットでテストレポートを作成できる件については、現時点ではあまり使うつもりは無いです。現状 terraform test
自体も活用できていないので。ですがすでにJUnit XML フォーマットを活用されている現場ではメリットが大きいのではないでしょうか。