[terraform] VariableとLocal Valueの使い所

これは「[「はじめに」の Advent Calendar 2021](https://adventar.org/calendars/7037)」1日目の記事です。


## TL;DR
- 基本的に変数はLocal Valueで作る
- Variableはモジュールの引数として定義し、モジュールを再利用する場合に使う
- 1ディレクトリで複数のStateやワークスペースを切り替える際は、Variableで環境名を設定、Local Valueを通してresourceから利用する。

## Variableとは

公式ドキュメント: https://www.terraform.io/docs/language/values/variables.html

`× Variable`
`○ Input Variable`

- Local Valueがまだなかった時、内部の変数と外から注入される引数としての両方が責務だった。
が、内部の変数はLocal Valueを使うようになった。
- Input Variableはその名の通り、入力用の変数として内部で利用しないことを公式でも推奨している。
- Input Variableは実行時にファイル・環境変数・オプション・対話的入力など、様々な注入方法を利用して- 外部から値を設定・上書きできる。
- Terraform CloudだとVariableごとにWorkspaceに値を設定していく必要がある。

### Input Variableの問題点

- 他のVariableを参照して新しいVariableを定義できない。
- 関数を利用できない。
- モジュールではなくワークスペースの作成に利用する場合、渡された値により作成されるリソースが変わるため、思った通りの値にならない。

## Local Valueとは

公式ドキュメント: https://www.terraform.io/docs/language/values/locals.html

- スコープがモジュール内に閉じた変数。
- 他のLocal　Valueを参照してあたらしいLocal Valueを定義できる。
- 関数や式展開を利用できる。
- 外部から値を設定・上書きできない。

### 使い方のおさらい
- 変数定義

```terraform
locals {
  name = "Foo"
  resource_count = 1
  # 配列
  cidr_list = ["10.1.0.0/16", "10.2.0.0/16"]
  # Map
}
# locals内に定義した変数名が衝突しなければ何度でも定義可能
locals {
  key_values = {
    key = "Bar"
    value = "Baz"
  }
}
```

- 関数・他のLocal Valueの利用

```terraform
locals {
  name = "foo"
  another_name = upper(local.name)
  count = 1
}

locals {
  # Localのスコープはモジュール全体なので、他のLocalブロックから参照可能
  another_count = count + 1
}
```

- リソースからは `local.${名前}`で利用

```terraform
locals {
  name_prefix = "foo-"
}

resource "aws_instance" "foo" {
  ...
  tags {
    Name = "${local.name_prefix}-web"
  }
}
```

## Local ValueとInput Variableの強みを両方活かす方法

- Local Valueは外部から値を設定できない。
- とはいえ、環境ごとに変数セットをごっそり切り替えたい場合もある。
- Input Variableで多くの変数を切り替えると、変数間の依存や関数が使えないため、重複記述が多くなる。
- Input VariableとLocal Valueを組み合わせて利用することでお互いのメリットを活かしデメリットを解消する。

### 具体的には・・・

ワークスペースの場合以下のように、環境名のみをVariableで渡し、環境ごとの変数セットをLocal Valueを定義して利用する。

```terraform
variable "env" {}

locals {
  _env = {
    devlopment = {
      name_prefix = "dev"
      service = "web"
      cidr = "10.1.0.0/16"
      ec2_web_name = "${local.name_prefix}-${local.service}"
      domain = "dev.example.com"
    },
    production = {
      name_prefix = "prod"
      service = "web"
      cidr = "10.2.0.0/16"
      ec2_web_name = "${local.name_prefix}-${local.service}"
      domain = "prod.example.com"
    },
  }
  env = local._env[var.env]
}

resource "aws_ec2_instance" "web" {
  ...
  tags {
    Name = "local.env.ec2_web_name
  }
}
```

- resourceからは、`local.env.*` を利用することで `env` ごとの値を参照できる。
- envに不正な値が設定され予期せぬリソースが作成されるのを抑制できる。上記なら `env=stage` を入力されても参照先がないのでエラーになる。
- 1ソースコードで複数のワークスペースや複数Stateを作る場合には、上記の方法は重宝する。
- モジュールの場合でも基本的な考え方は一緒で、例えばEC2の作成をモジュール化する場合、SecurityGroupのルール、InstanceTypeのパターンなどをパターン化して、 `A` `B` `C` セットのどれを作るか、みたいな形で選ばせたいときは、不正な値の抑制ができる。
- 例えば、基本セットとして `Publicに公開するAPI` か `Private向けのAPI` かでSecurityGroupの内容を変えるとか。

```terraform
variable "accessibility" {}

locals {
  _acc = {
    public = {
    },
    private = {
    }
  }
  acc = local._acc
}
...(略)
```

- また、受け取る値は　`Input Variable` で定義するしかないが、`resource` はすべてLocal Valueを参照することで、関数が必要だったり変数同士の文字列結合や計算する必要がある場合も見通しよく記述できる。

## なぜ書いたのか

- ２０２１年になっても「変数はVariablesで定義しよう」みたいな記事やブログが検索上位に散見されるので、１人でも多くの人にLocal Valueの利用を促進させるため。たとえ似たような記事が多くとも誰かの役に立つはず・・・