#28.三項演算子って便利なんだなぁと思った話

# はじめに
Terraformの学習中に、公開されているGitのソースコードを見ていたのですが、
その中で三項演算子を使用して、条件分岐しているものがあり、「へぇ〜便利だなぁ」と思ったものがあったので記事にしました。


# 見てたコード
実際に見て便利だなと思ったコードがこちらになります。

■ 参考リンク

https://github.com/aws-ia/terraform-aws-rds-aurora/blob/main/main.tf

164.169行目 抜粋
```
　engine = var.engine
  port = var.port == "" ? var.engine == "aurora-postgresql" ? "5432" : "3306" : var.port
```
Auroraは、MySQLやPostgreSQLのデータベースエンジンを選択して利用できます。
もちろん、エンジンの種類によって設定内容が異なるため、それぞれに合わせて個別に修正を加える必要があります。
しかし、それを毎回手作業で行ったり、モジュール化して切り分けるのは、構成が冗長になってしまう可能性があります。
このコードでは、変数engineに`aurora-postgresql`もしくは`aurora-mysql`を指定することで、それに応じたポート番号を変数portに自動で設定しています。
具体的には、変数portに値が格納されていない場合は、変数engineの値に応じて`aurora-postgresql`であれば5432、`aurora-mysql`であれば3306を使用し、既にportに値が指定されている場合はそのままの値を使用する構成になっています。

参考リンクのソースコードを見てわかる通り、他にもAuroraクラスタのリストア時やセカンダリ構成の際に、DBユーザー名を新規作成するのではなく、既存のものを再利用するといったような使い方もされています。（170行目）


■ モジュールについての参考リンク

https://dev.classmethod.jp/articles/investigation-of-serverless-and-terraform-module-structure/



Terraformはif文による条件分岐をサポートしていないため、条件分岐の際は本記事の三項演算子を使用するか、その他の方法で条件分岐させるということになります。
三項演算子は、便利な反面ネストが深く複雑な記述のものには適していないとされています。その点、IaCのコードは基本的にシンプルな記法なので相性が良いと思われます。

■ 参考リンク

https://dexall.co.jp/articles/?p=2324#i-1




https://qiita.com/smicle/items/7d3b9881834dc0142fb7


# おまけ
実際にterraformで実行してみた。

- aurora.tf
```
#テスト用
resource "aws_rds_cluster" "aurora_cluster_test"{
  cluster_identifier        = var.identifier 
  engine                    = var.engine
  engine_version            = var.engine == "aurora-postgresql" ? var.engine_version_pg : var.engine_version_mysql 
  port                      = var.port == "" ? var.engine == "aurora-postgresql" ? "5432" : "3306" : var.port
  master_username           = var.username
  master_password           = var.password
  db_subnet_group_name      = var.rds-subnet
  skip_final_snapshot       = true
}
```
※テストのため上記の設定としています。



- terraform.tfvars（engine = aurora-postgresql）

```
username                  = "nayanaya"
password                  = "password"
identifier                = "aurora-cluster"
engine                    = "aurora-postgresql"
engine_version_mysql      = "8.0.mysql_aurora.3.08.2"
engine_version_pg         = "17.4"
database_name             = "naya-db"
port                      = ""

```
```
$ terraform plan
aws_rds_cluster.aurora_cluster_test: Refreshing state... [id=aurora-cluster-test01]

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_rds_cluster.aurora_cluster_test will be created
  + resource "aws_rds_cluster" "aurora_cluster_test" {
      + cluster_identifier                    = "aurora-cluster-test01"
      + database_name                         = (known after apply)
      + db_subnet_group_name                  = "rds-subnet-group"
      + engine                                = "aurora-postgresql"
      + engine_version                        = "17.4"
      + master_password                       = (sensitive value)
      + master_username                       = "nayanaya"
      + port                                  = 5432
      + skip_final_snapshot                   = true
    }

Plan: 1 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
aws_rds_cluster.aurora_cluster_test: Refreshing state... [id=aurora-cluster-test01]

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_rds_cluster.aurora_cluster_test will be created
  + resource "aws_rds_cluster" "aurora_cluster_test" {
      + cluster_identifier                    = "aurora-cluster-test01"
      + database_name                         = (known after apply)
      + engine                                = "aurora-postgresql"
      + engine_version                        = "17.4"
      + master_password                       = (sensitive value)
      + master_username                       = "nayanaya"
      + port                                  = 5432
      + skip_final_snapshot                   = true
    }

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_rds_cluster.aurora_cluster_test: Creating...
aws_rds_cluster.aurora_cluster_test: Still creating... [13s elapsed]
aws_rds_cluster.aurora_cluster_test: Still creating... [23s elapsed]
aws_rds_cluster.aurora_cluster_test: Still creating... [33s elapsed]
aws_rds_cluster.aurora_cluster_test: Still creating... [45s elapsed]
aws_rds_cluster.aurora_cluster_test: Creation complete after 46s [id=aurora-cluster-test01]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
```

- terraform.tfvars（engine = aurora-mysql）

```
username                  = "nayanaya"
password                  = "password"
identifier                = "aurora-cluster-test02"
engine                    = "aurora-mysql"
engine_version_mysql      = "8.0.mysql_aurora.3.08.2"
engine_version_pg         = "17.4"
database_name             = "naya-db"
port                      = ""
rds-subnet                = "rds-subnet-group" #すでに作成済み
```
※`identifier(識別子）`を変更しないと先ほど作成した`aurora-postgresql`との変更差分となります。
  また、`terraform state list`コマンドで先ほどのリソース名が表示される場合、削除してから再構築されてしまいます。（.tfstateで参照しているリソースが同じため）
```
$ terraform plan                                                                                                                                  
aws_rds_cluster.aurora_cluster_test: Refreshing state... [id=aurora-cluster-test01]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # aws_rds_cluster.aurora_cluster_test must be replaced
-/+ resource "aws_rds_cluster" "aurora_cluster_test" {
      ~ cluster_identifier                    = "aurora-cluster-test01" -> "aurora-cluster-test02" # forces replacement
      + database_name                         = (known after apply)
      ~ engine                                = "aurora-postgresql" -> "aurora-mysql" # forces replacement
      ~ engine_version                        = "17.4" -> "8.0.mysql_aurora.3.08.2"
      ~ port                                  = 5432 -> 3306

Plan: 1 to add, 0 to change, 1 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
aws_rds_cluster.aurora_cluster_test: Refreshing state... [id=aurora-cluster-test01]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # aws_rds_cluster.aurora_cluster_test must be replaced
      ~ cluster_identifier                    = "aurora-cluster-test01" -> "aurora-cluster-test02" # forces replacement
      + database_name                         = (known after apply)
      ~ engine                                = "aurora-postgresql" -> "aurora-mysql" # forces replacement
      ~ engine_version                        = "17.4" -> "8.0.mysql_aurora.3.08.2"
      ~ port                                  = 5432 -> 3306
    }

Plan: 1 to add, 0 to change, 1 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_rds_cluster.aurora_cluster_test: Destroying... [id=aurora-cluster-test01]
╷
│ Error: deleting RDS Cluster (aurora-cluster-test01): operation error RDS: DeleteDBCluster, https response error StatusCode: 403, RequestID: 20871707-7e21-4024-824f-5fae72fd3488, api error AccessDenied: User: arn:aws:iam::アカウントID:user/naya is not authorized to perform: rds:DeleteDBCluster on resource: arn:aws:rds:ap-northeast-1:アカウントID:cluster:aurora-cluster-test01 because no identity-based policy allows the rds:DeleteDBCluster action
│
│
╵
```
IaMポリシーを追加していなかったので削除されずに済みましたね....
以下のコマンドを使用してから再度applyします。
```
$ terraform state list
aws_rds_cluster.aurora_cluster_test

$ terraform state rm aws_rds_cluster.aurora_cluster_test
Removed aws_rds_cluster.aurora_cluster_test
Successfully removed 1 resource instance(s).
```
`.tfstate`ファイルで参照しているリソースが先ほどapplyしたリソースとなっているため、削除を伴う更新処理が実行されそうになったようです。`.tfstate`ファイルを削除してから`terraform init`コマンドを実行するか、`terraform state`から削除します。今回は`terraform state`で削除しました。
```
$ terraform apply

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_rds_cluster.aurora_cluster_test will be created
  + resource "aws_rds_cluster" "aurora_cluster_test" {
      + cluster_identifier                    = "aurora-cluster-test02"
      + database_name                         = (known after apply)
      + db_subnet_group_name                  = "rds-subnet-group"
      + engine                                = "aurora-mysql"
      + engine_version                        = "8.0.mysql_aurora.3.08.2"
      + master_password                       = (sensitive value)
      + master_username                       = "nayanaya"
      + port                                  = 3306
      + skip_final_snapshot                   = true
    }

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_rds_cluster.aurora_cluster_test: Creating...
aws_rds_cluster.aurora_cluster_test: Still creating... [10s elapsed]
aws_rds_cluster.aurora_cluster_test: Still creating... [20s elapsed]
aws_rds_cluster.aurora_cluster_test: Still creating... [32s elapsed]
aws_rds_cluster.aurora_cluster_test: Still creating... [42s elapsed]
aws_rds_cluster.aurora_cluster_test: Creation complete after 44s [id=aurora-cluster-test02]

```

削除後に再度applyすると、同じリソース名であっても別のクラスターが新しく作成されました。

また、変数を変更するだけで他の構成もデータベースエンジンに応じて自動で切り替えられる点は非常に便利ですね👏

- バージョン指定の挙動について
postgresqlとmysqlでは、バージョン指定の書き方が異なります。気になったので、Terraformの公式ドキュメントを確認してみました。
使用例には`8.0.mysql_aurora.3.08.2`本記事で使用したものと似た形式のものが紹介されており、説明にも以下のように記載があります

■ 参考ドキュメント

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster

以下抜粋
>engine_version- (オプション) データベースエンジンのバージョン。この引数を更新すると、システム停止が発生します。この値を確認するには、設定済みのエンジンのAurora MySQLおよびAurora Postgresのドキュメントを参照するか、aws rds describe-db-engine-versionsを実行してください。例えば、Aurora MySQL 2の場合、この引数の値は5.7.mysql_aurora.2.03.2です。APIでサポートされている場合、この値には部分的なバージョンを含めることができます。

明確に「この形式で書かないといけない」というのは読み取れなかったので実際に確認してみました。

- terraform.tfvars（engine = aurora-mysql）

```
username                  = "nayanaya"
password                  = "password"
identifier                = "aurora-cluster"
engine                    = "aurora-mysql"
engine_version_mysql      = "3.08.2" #検証対象
engine_version_pg         = "17.4"
database_name             = "naya-db"
port                      = ""
rds-subnet                = "rds-subnet-group" #すでに作成済み
```

実行した結果となります👇
```
╷
│ Error: creating RDS Cluster (aurora-cluster-test01): operation error RDS: CreateDBCluster, https response error StatusCode: 400, RequestID: 2e97299f-4ebb-45ee-9bc6-7317dc1acdef, api error InvalidParameterCombination: 
Cannot find version 3.08.2 for aurora-mysql
│
│   with aws_rds_cluster.aurora_cluster_test,
│   on aurora.tf line 2, in resource "aws_rds_cluster" "aurora_cluster_test":
│    2: resource "aws_rds_cluster" "aurora_cluster_test"{
│
╵
```
何となく、予想はできていましたがエラーが出ましたね。

----
あと、一応コンソール画面も以下に張り付けておきます。


- コンソール画面

![image - 2025-05-04T010143.895.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904736/15806b1e-14ad-46d1-b18b-649fdaaacf4d.png)

- aurora-cluster-test01(PostgreSQL)

![image - 2025-05-04T010218.332.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904736/3632f049-4b99-485c-abb1-89b5784451e8.png)

- aurora-cluster-test02(MySQL)

![image - 2025-05-04T010212.051.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904736/43064681-89a0-4ff0-89f0-c8ad130d29dc.png)



# まとめ
これまでコードに触れる機会が少なかった自分にとっては、少しの工夫でこれほど柔軟に設定を切り替えられるという点がとても面白いなと思いました。
もちろん、コードの書き方やベストプラクティスには個人差がありますし、「これが正解」というものは一概には言えないのですが、今回のような三項演算子による切り替えなどのやり方を知れたのは良い学びになりました。