terraform state fileのマージ

# state fileをローカルファイルに書き出す

source側

```
terraform state pull > source.tfstate
```

destination側

```
terraform state pull > destination.tfstate
```

# 新しいdirectoryにtfstateファイルを移動

```
mkdir merge-state
mv /path/to/source.tfstate merge-state
mv /path/to/destination.tfstate merge-state
cd merge-state
```

# resourcesのリストファイルを作成

```
terraform state list -state=destination.tfstate > destination-resources.txt
terraform state list -state=source.tfstate > source-resources.txt
```

中身はこんな感じ↓

```
data.aws_ecr_lifecycle_policy_document.foo_policy
data.aws_iam_policy_document.foo_policy
aws_ecr_lifecycle_policy.foo_policy
aws_ecr_repository.foo
aws_route53_record.about
...
```

一応、SourceとDestinationのResourceの数も数えておきます

```
wc -l source-resources.txt 
     107 source-resources.txt
```

```
wc -l destination-resources.txt 
      22 destination-resources.txt
```

# state を移動

:::note warn

1. stateには `module.aaa.aws_s3_bucket["aaa"]`のようにdoublequotesが入っている可能性があるので、escapeする必要あり。
1. また、以下のコマンドだと、 `xargs: command line cannot be assembled, too long` というエラーが出たりするのでlineごとにloopで回した (xargsあまり使いこなせていないので :smiley_cat: )

    ```
    cat source-resources.txt | xargs -I {} bash -c 'terraform state mv -state=source.tfstate -state-out=destination.tfstate "{}" "source_module.{}"'
    ```
:::


```
while IFS= read -r resource; do
    terraform state mv -state=source.tfstate -state-out=destination.tfstate "$resource" "$resource"
done < source-resources.txt
```

```
terraform state list -state=source.tfstate | wc -l
       0
```

```
terraform state list -state=destination.tfstate | wc -l
     129
```

107 (source resources before) + 22 (destination resources before) = 0 (source resources after) + 129 (destination resources after) で一致しています :tada: 

# コードの移動

対象となるsourceのterraformコードをDestination側に移します

# destination 側で確認

destinationのterraform codeのあるdirectoryに移動して `destination-after.tfstate` に変更後のstate fileを移動します。

```
cp /path/merge-state/destination.tfstate destination-after.tfstate
```

backendの設定をremoteからlocalに変えます。

```diff
-  backend "s3" {
-    bucket = "your-terraform"
-    key    = "aws/naka/terraform.tfstate"
-  }
+  backend "local" {
+    path = "destination-after.tfstate"
+  }
```

remote stateはいじらずに、initしたいので `-reconfigure` Optionをつけてinitします。

```
terraform init -reconfigure
```

planをしてみます。

```
terraform plan
```

No changeになればcodeとstateが一致しています。

```
No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.
```

No changes :tada: これで、 source から destinationへの移行が完了しました!

:::note warn
これで一致しない場合は何がおかしいのかを確認して、もう一度merge-stateに戻ってstateのマージに間違いがないかやり直します。もし状況がわからなくなってしまった場合には、backendの変更をrevertして `terraform init -reconfigure`すれば、始める前の状態に戻ることができます。
:::


# stateをremoteに戻す

無事に、stateのマージができたので、最後にlocal fileにあるstateをremoteに反映します。

今はlocalのbackendになっているので、destination側のbackendを元々のremote backendに戻します。

```diff
+  backend "s3" {
+    bucket = "your-terraform"
+    key    = "aws/naka/terraform.tfstate"
+  }
-  backend "local" {
-    path = "destination-after.tfstate"
-  }
```

`terraform init` すると以下のように聞かれます。

```
terraform init
Initializing the backend...
Initializing modules...
╷
│ Error: Backend configuration changed
│ 
│ A change in the backend configuration has been detected, which may require migrating existing state.
│ 
│ If you wish to attempt automatic migration of the state, use "terraform init -migrate-state".
│ If you wish to store the current configuration with no changes to the state, use "terraform init -reconfigure".
```

automatic migration of the stateをしたいので、`-migrate-state` をつけて実行します。

```
terraform init -migrate-state
```

そうすると以下のように local -> s3 (remote) にコピーするかどうかが聞かれます。

```
terraform init -migrate-state
Initializing the backend...
Terraform detected that the backend type changed from "local" to "s3".

Do you want to copy existing state to the new backend?
  Pre-existing state was found while migrating the previous "local" backend to the
  newly configured "s3" backend. An existing non-empty state already exists in
  the new backend. The two states have been saved to temporary files that will be
  removed after responding to this query.
  
  Previous (type "local"): /var/folders/20/jnrp85t51hd23cjk51_btywh0000gp/T/terraform4131371062/1-local.tfstate
  New      (type "s3"): /var/folders/20/jnrp85t51hd23cjk51_btywh0000gp/T/terraform4131371062/2-s3.tfstate
  
  Do you want to overwrite the state in the new backend with the previous state?
  Enter "yes" to copy and "no" to start with the existing state in the newly
  configured "s3" backend.

  Enter a value:
```

`/var/folders/20/jnrp85t51hd23cjk51_btywh0000gp/T/terraform4131371062/1-local.tfstate` 一応中身を軽く確認して (行数やversionをチェックする程度) `yes` と答えます。

```
Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.
Initializing modules...
Initializing provider plugins...

...

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
```

そうするとlocal stateをs3(remote)のbackendにコピーしてくれます。

最後にこの状態で`terraform plan`を実行して `No changes` がでればOKです :tada: :tada: 完全に完了です。

```
No changes. Your infrastructure matches the configuration.
```

あとは、Source側のコードとCIを削除して、PushしてCI上でもNo changesとなるのをみて、Mergeをして終わりです。


# Ref

- https://support.hashicorp.com/hc/en-us/articles/4418624552339-How-to-Merge-State-Files
- https://developer.hashicorp.com/terraform/cli/commands/state/mv