Terraform0.12時代に、より汎用的なmoduleを作るためのtips3選

![cover.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321850/3887639b-2895-dec3-21d6-6e69a432ce84.jpeg)

## はじめに
こんにちは。Wano株式会社エンジニアのnariと申します。
7月からある新規のプロジェクトでterraform職人デビューしました。
**絶賛IaCにハマり、tfファイルと格闘する毎日を過ごしております。**

moduleといえば、[Terraform module Registry](https://registry.terraform.io/)は実装の参考にはなるけれど、moduleとしてusecaseが狭かったり、外部に依存したくなかったりで、結局ローカルにprivate moduleを作ることが多いと思います。

その際痛感したのが、
**汎用的なmodule作るのって、難しい。。。**

あまり凝りすぎて可読性が落ちたり使いづらくならない範囲で、用途の広いmoduleが欲しい。。
そこで自分がmoduleを作るときに、便利だなと思って使用しているtipsを3つ紹介したいと思います。

## 想定読者
- 最近terraformerになった人
- **Pragmatic Terraform on AWSを読了し、実際のmodule作りに励んでいる方**  
-> **terraformerになりたい方は[「Pragmatic Terraform on AWS」が神本だったので紹介する](https://qiita.com/fukubaka0825/items/53cd168730815a33f51f)をまず読んでください！**
- terraform 0.12.0にupgradeしようとされている方


## TL;DR
- **汎用的なmodule作りには**
    - **optionalなfield -> variableのdefault値 null**
    - **optionalなargument -> dynamic block+三項演算子**
    - **optionalなresource -> count+三項演算子**

## 実行環境
**terraform 0.12.0**

## 汎用的なmoduleの特徴
- requiredが少ない
- **optionalが多く、様々な用途に対応できる**

## 汎用的moduleを作るためのtips3選
### 1.variableのdefault値でnullを使用し、optionalなfieldを設定
#### module example
```main.tf
/* codebuild */
resource "aws_codebuild_project" "default" {
...
  source {
    type      = var.source_type
    location  = var.source_location
    buildspec = var.buildspec
  }
...
}
```
```variable.tf
/* optional source_typeがs3の場合required*/
variable "source_location" {
  default = null
}

```
#### 説明
- valiableのdefault値でnullを使用することで、設定しない限りそのfieldは無いものとして扱われる
- **null値の導入は0.12.0から**
- **ある条件下でのみ設定したいfieldに使用する**(今回で言えばsource_type=s3)

### 2.dynamic blockと三項演算子を使用し、optionalなargumentを設定
#### module example
```main.tf
/* ecs sercie */
resource "aws_ecs_service" "default" {
...
  dynamic load_balancer {
    //is_load_balancer_modeがfalseならこのargument設定されない
    for_each = var.has_load_balancer ? {dummy:"hoge"} : {}
    content {
      target_group_arn = var.target_group_arn
      container_name   = var.container_name
      container_port   = var.container_port
    }
  }
...
}
```
```variable.tf
/* optional*/
variable "has_load_balancer" {
  description = "もしload_balancer argumentを設定したいなら、trueを入れる"
  default     = false
}

/* has_load_balancerがtrueなら requierd　*/
variable "target_group_arn" {
  default = null
}
variable "container_name" {
  default = null
}
variable "container_port" {
  default = null
}
```
#### 説明
- **has_load_balancerのflagを設定し、dynamic blockと三項演算子を使用することで、load_balancerを設定する場合もしない場合も対応可能なecs sercviceのmoduleが作れる**
- **三項演算子**とは？　->　[三項演算子](https://ja.wikipedia.org/wiki/%E6%9D%A1%E4%BB%B6%E6%BC%94%E7%AE%97%E5%AD%90#%E4%B8%89%E9%A0%85%E6%BC%94%E7%AE%97%E5%AD%90)
- **dynamic block**とは？　-> [Expressions](https://www.terraform.io/docs/configuration/expressions.html)
- **dynamic blockの導入は0.12.0から**
- has_load_balancer=falseの場合は、**空のmap**を設定することで、argumentが設定されない
- has_load_balancer=trueの場合は**key valueが１組のmap**を設定することで、argumentが１つ設定される(コードが汚いのでなんとかならないかな、、要検討)

### 3.countと三項演算子を使用し、optionalなresourceを設定
#### module example
```main.tf
resource "aws_iam_user_login_profile" "default" {
  count   = var.has_login_profile ? 1 : 0
  user    = aws_iam_user.default.name
  pgp_key = var.pgp_key
}
```
```variable.tf
/*if you want to login as this user, required*/
variable "has_login_profile" {
  default = false
}

```
#### 説明
- 例えば、コンソールログインできるuserもできないuserも作れるmoduleにしたい、という場合resource丸々がoptionalとして扱いたくなる
- そこで、has_login_profileのようなフラグを用意して、**trueならcount=1でlogin_profileを生成する,falseならcount=0でlogin_profileを生成しないとしてやる**

## 終わりに
0.12.0で追加された話としては、object型が便利だなぁとか色々あると思いますが、今日はこの辺で。
今回紹介したサンプルなど、awsのresource系のmoduleは[こちらのリンク](https://github.com/takafk9/terraform-sample-aws-modules)に掲載してますので、ご参考にしてください。
terraformerとしてスーパービギナーですので至らぬところたくさんあると思いますのでご指摘お待ちしております。
**最近全然記事書けてなかったので、今日明日で溜まってたトピックでいっぱい記事書こうと思います**