【Terraform】for_eachでAmazon DynamoDBの項目をまとめて管理する

## 概要

- DynamoDBにてアプリケーションで使用する設定値を管理したい
- DynamoDB TableはTerraformで管理
- DynamoDB Tableに追加する項目（ここでは設定値）をTerraformで管理
    - Terraformの`for_each`ファンクションを利用して、複数項目を追加

## 動機

以下のjsonファイルの内容でDynamoDBに登録される項目が存在する。 
登録するデータはマスターデータであるため、基本的に変更しない。 
可能であれば、Terraformで管理したい。（[Resource: aws_dynamodb_table_item](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/dynamodb_table_item)）  


```json:sample.json
{
    "device_code": {
        "S": "3"
    },
    "parameter": {
        "M": {
            "input_key": {
                "S": "path/to/directory"
            },
            "output_location": {
                "S": "path/to/directory"
            },
            "instance_type": {
                "S": "ml.m5.2xlarge"
            }
        }
    }
}
```


## 対応方法

`for_each`を活用することで、少ない定義で複数の項目を管理することが可能となる。  

```terraform:dynamodb.tf
resource "aws_dynamodb_table" "device_config" {
  name                   = "sample_table"
  billing_mode           = "PROVISIONED"
  hash_key               = "device_code"

  attribute {
    name = "device_code"
    type = "S"
  }
}

locals {
  json_data = file("./config.json")         # jsonファイルの読み込み
  config    = jsondecode(local.json_data)   # デコード
}

resource "aws_dynamodb_table_item" "device_config" {
  for_each = local.config                   # for_eachにより、要素ごとに分割

  table_name = aws_dynamodb_table.device_config.name
  hash_key   = aws_dynamodb_table.device_config.hash_key

  item = jsonencode(each.value)             # 各要素のバリューをエンコードしitemに挿入
}

```

例として以下のような`config.json`にすることにより、画像のようにまとめて項目を追加することができる。  

```json:config.json
{
    "device_3": {
        "device_code": {
            "S": "3"
        },
        "parameter": {
            "M": {
                "input_key": {
                    "S": "path/to/directory/3"
                },
                "output_location": {
                    "S": "path/to/directory/3"
                },
                "instance_type": {
                    "S": "ml.m5.2xlarge"
                }
            }
        }
    },
    "device_4": {
        "device_code": {
            "S": "4"
        },
        "parameter": {
            "M": {
                "input_key": {
                    "S": "path/to/directory/4"
                },
                "output_location": {
                    "S": "path/to/directory/4"
                },
                "instance_type": {
                    "S": "ml.m5.2xlarge"
                }
            }
        }
    },
    "device_5": {
        "device_code": {
            "S": "5"
        },
        "parameter": {
            "M": {
                "input_key": {
                    "S": "path/to/directory/5"
                },
                "output_location": {
                    "S": "path/to/directory/5"
                },
                "instance_type": {
                    "S": "ml.m5.2xlarge"
                }
            }
        }
    }
}
```

![20201211220029.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484840/44cf317c-70af-4732-6b47-f0d320a709ef.png)


## 項目を変更する際

Terraformで項目を管理できることにより、マスターに変更が加わった場合でもコードで変更点などを把握することが可能となる。  
仮に上記例のdevice4を削除した場合、Terraformを実行すると以下のように出力され、`apply`するとその項目のみが削除される。  

```bash
$ terraform apply

aws_dynamodb_table.device_config: Refreshing state... [id=sample_table]
aws_dynamodb_table_item.device_config["device_5"]: Refreshing state... [id=sample_table|device_code||5|]
aws_dynamodb_table_item.device_config["device_3"]: Refreshing state... [id=sample_table|device_code||3|]
aws_dynamodb_table_item.device_config["device_4"]: Refreshing state... [id=sample_table|device_code||4|]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # aws_dynamodb_table_item.device_config["device_4"] will be destroyed
  - resource "aws_dynamodb_table_item" "device_config" {
      - hash_key   = "device_code" -> null
      - id         = "sample_table|device_code||4|" -> null
      - item       = jsonencode(
            {
              - device_code = {
                  - S = "4"
                }
              - parameter   = {
                  - M = {
                      - input_key       = {
                          - S = "path/to/directory/4"
                        }
                      - instance_type   = {
                          - S = "ml.m5.2xlarge"
                        }
                      - output_location = {
                          - S = "path/to/directory/4"
                        }
                    }
                }
            }
        ) -> null
      - table_name = "sample_table" -> null
    }

Plan: 0 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_dynamodb_table_item.device_config["device_4"]: Destroying... [id=sample_table|device_code||4|]
aws_dynamodb_table_item.device_config["device_4"]: Destruction complete after 0s

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

![20201211220728.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484840/cbd3976c-50fc-2cb3-b3ca-ac5ca2ddaf18.png)


## まとめ

- `for_each`を利用することによりDynamoDBの項目管理が容易に実現できた
- IaCすごい

## 参考

- https://www.terraform.io/docs/configuration/meta-arguments/for_each.html