tfstate の手動編集 (s3 backend 編)

`terraform apply` の状態管理は tfstate ファイルで行われ、こちらにサーバーリソースに対して行った変更内容と結果が state ファイルとして保存されます。tfstate の状態と tf ファイルの内容が異なる状態で  `terraform plan` を実施すると terraform コマンドが状態の差分を表示してくれます。

基本的に tfstate ファイルは手動で変更することは無いですが、既存のサーバーリソースの設定内容を後付けで terraform 管理下に置こうとした時などに、どうしても手動で変更せざるを得ないケースもあります。

（例： IAM Group、Policy などの情報は、tfstate に保存されていない内容はすべて新規扱いになる。既存のリソースと同じ設定を tf ファイルに書いた状態で terraform apply をすると 409 エラーが発生してしまう）

このドキュメントでは、tfstate の手動編集を行う手順について書きます。


# tfstate format - general (version 4)
概論として、tfstate がどのようなデータフォーマットなのか、簡単に書きます。

https://www.terraform.io/docs/state/index.html#format

tfstate は JSON 形式で表現されています。

公式ドキュメントにはあまり記述がないですが… 0.12.x でのフォーマット(version 4)は以下のようになっています。

terraform のバージョンによって tfstate のバージョンも異なり、それらにより微妙に記述すべき内容は異なります。

```
{
  "version": 4,
  "terraform_version": "0.12.18",
  "serial": 679,
  "lineage": "617d8489-6973-bac0-2750-3457992f99db",
  "outputs": {
    "security_groups": {
      "value": {
        "sada": [
          "sg-xxxxxxxx",
          "sg-xxxxxxxx"
        ],
      },
      "type": [
        "object",
        {
          "sada": [
            "tuple",
            [
              "string",
              "string"
            ]
          ]
        }
      ]
    }
  },
  "resources": [
    {
      "mode": "managed",
      "type": "aws_security_group",
      "name": "sada",
      "provider": "provider.aws",
      "instances": [
        {
          "schema_version": 1,
          "attributes": {
            "arn": "arn:aws:ec2:ap-northeast-1:xxxxxx.....",
(snip.)
        }
      ]
    }
  ]
}
```

`state` は tfstate ファイル自体のフォーマット番号、`terraform_version` は動作させている terraform のバージョン、`serial` は tfstate の変更回数（terraform apply するごとにインクリメントされていく）、`lineage` は terraform init した際の払い出される UUID です。

`outputs` には terraform apply した結果の output の結果が、そして `resources` には実際に設定を適用したリソースの状態が保存されます。

その他、どのようなデータ定義になっているかについては、go で書かれたソースコードを読むのが手っ取り早いと思います。

<https://github.com/hashicorp/terraform/blob/master/states/statefile/version4.go>

手で tfstate を編集する際は、基本的にこの resources の内容の追記、変更、削除を行うことになります。

# tfstate format - resources (version 4)
resources のフォーマットは以下のようになっており、個々のリソースの配列として表現されています。

```
  "resources": [
    {
      "mode": "managed",
      "type": "aws_security_group",
      "name": "sada",
      "provider": "provider.aws",
      "instances": [
        {
          "schema_version": 1,
          "attributes": {
            "arn": "arn:aws:ec2:ap-northeast-1:xxxxxx.....",
(snip.)
        }
      ]
    }
  ]
```

`mode` は基本 managed (サーバーリソース)か data、`type` は対象リソースの名前、`name` はリソースに付与した名前、`provider` は対象となるリソースプロバイダー(aws, azurerm 等)です。

そして、`instances.attributes` の中の内容が個々のリソースの設定情報を表現するため、基本的にはここの編集を行います。

たとえば `aws_security_group` の場合、attributes に設定すべき項目は以下のようなものになります。

- arn
- id
- name
- vpc-id
- ingress
- egress
- etc…

このドキュメントでは、各リソースの attribute に何を設定すべきかは詳述しません。

以下では、S3 を backend に指定している際の tfstate 編集作業の手順を記載します。


# S3 backend の tfstate の編集手順
S3上のファイルを直接書き換える方法も無いわけではないと思いますが、ダイレクトに変えすぎるとリスクがあります。ということで、ここでは以下のような手順を記載します。


## 1.backend を一時的に local に切り替える
```
terraform {
#  backend "s3" {
#    bucket                  = "sada-terraform-state"
#    key                     = "tfstate.json"
#    region                  = "ap-northeast-1"
(snip.)
#  }
  backend "local" {
    path = "terraform.tfstate"
  }
}
```

## 2.terraform init
backend を切り替えた後に `terraform init` コマンドを実行すると、切り替え先の backend に現在の tfstate をコピーするか否かを尋ねるメッセージが表示されます。

```
Terraform detected that the backend type changed from "s3" to "local".
Do you want to copy existing state to the new backend?
(snip.)
```

こちらに対して yes とタイプすると、切り替え先の backend に tfstate ファイルがコピーされます。

ここでは local に backend を切り替えましたので、`terraform.tfstate` にファイルが保存されます。

## 3.tfstate 、tf ファイルの変更
ローカルに保存された tfstate に変更を加えます。

変更する手段として、 `terraform import` を使った手順を以下に記載します。

### 3.1.terraform import
既存のリソースの情報を取得して tfstate に反映するコマンドです。

<https://www.terraform.io/docs/import/usage.html>

このコマンドを実行して、最新のリソース状況を tfstate に取り込む事ができます。

```
terraform import -state=terraform.tfstate aws_security_group.sada sg-xxxxxxxx
```

必ずしも最新のリソース状況の取り込みだけが tfstate を手動で編集する際の動機では無いと思いますが、リソース状況の取得の際は terraform import を使うと便利です。

### 3.2.terraform state show
tfstate に何が設定されているかは、 `terraform state show` コマンドを使用することで確認できます。

こちらを使って、tfstate が意図した状態になっているかを確認します。

```
terraform state show -state=terraform.tfstate aws_security_group.sada
# aws_security_group.sada:
resource "aws_security_group" "sada" {
    arn                    = "arn:aws:ec2:ap-northeast-1:xxxxxx....."
(snip.)

}
```

### 3.3.tfファイルの修正、作成、削除
上記等をふまえ、tf ファイルに、設定したい情報を定義します。

## 4.terraform plan 、applyで意図した差分状態になるかを確認
一通り変更を加えたら、 `terraform plan` を実行し、意図した変更内容が反映されているかを確認します。

反映が必要な場合は、 `terraform apply` を実行します。

## 5.backend を S3 に戻す
問題がなければ、再度 backend を s3 に戻します。

```
terraform {
  backend "s3" {
    bucket                  = "sada-terraform-state"
    key                     = "tfstate.json"
    region                  = "ap-northeast-1"
(snip.)
  }
#  backend "local" {
#    path = "terraform.tfstate"
#  }
}
```

## 6.terraform init
S3 に backend を戻した上で `terraform init` を実行すると、S3 にローカルの tfstate ファイルをコピーするかどうか尋ねられますので、yes を入力します。

```
Terraform detected that the backend type changed from "local" to "s3".
Do you want to copy existing state to the new backend?
(snip.)
```

## 7.terraform plan
念の為、backend を S3 に戻した後も意図した差分になっているか、 `terraform plan` で確認します。

 

これで、一通りの作業は完了です。