Terraform v1.0.0 で開発が終了した terraform-bundle の代替手段

## 背景

Terraform では v0.10 からプラグインベースのアーキテクチャが導入 [^1] [^2] されて、`terraform init` を実行した際に外部レジストリから Provider をインストールする仕様になりました。この仕様により、組織や地域のファイアウォールにより外部レジストリにアクセスできない環境では Terraform を利用できない課題が発生しました。この課題を解決するために Terraform CLI とは独立して開発されたのが terraform-bundle [^3] です。

Terraform CLI の方でも上記の課題を解決するために、v0.13 でローカルのファイルシステムに配置されたファイルなどから Provider をインストールする機能 [^4] や、terraform-bundle と似た機能を持つ `terraform providers mirror` コマンド [^5] が提供されました。これにより、Terraform CLI だけで当初の課題を解決できるようになったので terraform-bundle の開発が終了 [^6] となり v1.0.0 からリリースに含まれなくなりました。

[terraform-bundle のドキュメント](https://github.com/hashicorp/terraform/tree/43f698dc9db36b722f3d60a7d6aca9b757c2e621/tools/terraform-bundle)では、過去のリリースを参照すれば terraform-bundle を使用できるという記載がありますが、開発が終了したので非推奨という理解で問題ないかと思います。一方で「Terraform Cloud のセルフホスト型ディストリビューションである Terraform Enterprise を使用している方は、terraform-bundle を使用して独自の Terraform パッケージを構築することができます。」という記載もあったので、Terraform Enterprise 利用者にとっては現在も変わらず有用なツールという位置づけのなのだろうという印象を受けました。詳細な情報は [Installing a Bundle in Terraform Enterprise](https://github.com/hashicorp/terraform/blob/v0.15/tools/terraform-bundle/README.md#installing-a-bundle-in-terraform-enterprise) を参照してください。

## terraform-bundle から代替手段への切り替え

ここでは terraform-bundle で生成できる bundle archive と同等のものを `terraform providers mirror` コマンドで作成する方法を紹介していきます。

### terraform providers mirror コマンド

`terraform providers mirror` コマンドは、現在の設定から Terraform の実行に必要な Provider をローカルのファイルシステムにダウンロードするためのコマンドです。さらに、実行時に作成されるディレクトリ構造は `<HOSTNAME>/<NAMESPACE>/<TYPE>/terraform-provider-<TYPE>_<VERSION>_<TARGET>.zip` のようになっていて、Provider のインストール方法として指定できる [filesystem_mirror 方式](https://qiita.com/ryysud/items/a38bcc856069801878e9#filesystem_mirror) ですぐに利用できるものになっています。

```bash
$ terraform --version
Terraform v1.0.2
on darwin_amd64

$ terraform providers mirror --help

Usage: terraform [global options] providers mirror [options] <target-dir>

  Populates a local directory with copies of the provider plugins needed for
  the current configuration, so that the directory can be used either directly
  as a filesystem mirror or as the basis for a network mirror and thus obtain
  those providers without access to their origin registries in future.

  The mirror directory will contain JSON index files that can be published
  along with the mirrored packages on a static HTTP file server to produce
  a network mirror. Those index files will be ignored if the directory is
  used instead as a local filesystem mirror.

Options:

  -platform=os_arch  Choose which target platform to build a mirror for.
                     By default Terraform will obtain plugin packages
                     suitable for the platform where you run this command.
                     Use this flag multiple times to include packages for
                     multiple target systems.

                     Target names consist of an operating system and a CPU
                     architecture. For example, "linux_amd64" selects the
                     Linux operating system running on an AMD64 or x86_64
                     CPU. Each provider is available only for a limited
                     set of target platforms.
```

### bundle archive と同等のものを作成する

ここでは `terraform providers mirror` コマンドを使用して、以下のような Terraform CLI v1.0.2 と AWS Provider v3.49.0 がパッケージングされた bundle archive と同等のものを作成していきます。

```bash
$ unzip terraform_1.0.2-bundle2021071506_darwin_amd64.zip

$ tree .
.
├── plugins
│   └── registry.terraform.io
│       └── hashicorp
│           └── aws
│               └── 3.49.0
│                   └── darwin_amd64
│                       └── terraform-provider-aws_v3.49.0_x5
└── terraform

$ ./terraform --version
Terraform v1.0.2
on darwin_amd64
```

AWS Provider v3.49.0 をインストール必須とした main.tf ファイルを用意します。

```hcl:main.tf
terraform {
  required_version = "= 1.0.2"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "= 3.49.0"
    }
  }
}
```

Provider をダウンロードするディレクトリを指定して `terraform providers mirror` コマンドを実行します。デフォルトだとコマンドを実行したホストの OS と CPU アーキテクチャで実行可能な Provider をダウンロードする仕様なので、要件に応じて `-platform=linux_amd64` のようなオプションを付与してください。

```bash
$ terraform --version
Terraform v1.0.2
on darwin_amd64

$ terraform providers mirror plugins
- Mirroring hashicorp/aws...
  - Selected v3.49.0 to meet constraints 3.49.0
  - Downloading package for darwin_amd64...
  - Package authenticated: signed by HashiCorp
```

main.tf に設定したとおりに Provider がダウンロードされました。

```bash
$ tree plugins
plugins
└── registry.terraform.io
    └── hashicorp
        └── aws
            ├── 3.49.0.json
            ├── index.json
            └── terraform-provider-aws_3.49.0_darwin_amd64.zip

3 directories, 3 files
```

`terraform-provider-aws_3.49.0_darwin_amd64.zip` は Provider バイナリを ZIP 圧縮したものです。

```bash
$ zipinfo plugins/registry.terraform.io/hashicorp/aws/terraform-provider-aws_3.49.0_darwin_amd64.zip
Archive:  plugins/registry.terraform.io/hashicorp/aws/terraform-provider-aws_3.49.0_darwin_amd64.zip
Zip file size: 43084802 bytes, number of entries: 1
-rwxr-xr-x  3.0 unx 213013632 bx defN 21-Jul-09 06:37 terraform-provider-aws_v3.49.0_x5
1 file, 213013632 bytes uncompressed, 43084586 bytes compressed:  79.8%
```

Provider と一緒に生成された JSON ファイルは [Provider Network Mirror Protocol](https://www.terraform.io/docs/internals/provider-network-mirror-protocol.html) を実装する際に役立つもので、今回の記事とは関係ないので詳細な説明は割愛します。

```bash
$ cat plugins/registry.terraform.io/hashicorp/aws/3.49.0.json
{
  "archives": {
    "darwin_amd64": {
      "hashes": [
        "h1:klBcGkbCZhDo95eRUz1c0Sj1J97RNfSceZLzbwaFVaQ="
      ],
      "url": "terraform-provider-aws_3.49.0_darwin_amd64.zip"
    }
  }
}

$ cat plugins/registry.terraform.io/hashicorp/aws/index.json
{
  "versions": {
    "3.49.0": {}
  }
}
```

https://www.terraform.io/downloads.html で記載されている URL から Terraform CLI をダウンロードします。今回はバージョンに加えて OS と CPU アーキテクチャを固定していますが、今回紹介している方法を流用する場合は要件に応じて変更してください。

```bash
$ wget https://releases.hashicorp.com/terraform/1.0.2/terraform_1.0.2_darwin_amd64.zip
$ unzip terraform_1.0.2_darwin_amd64.zip

$ ./terraform --version
Terraform v1.0.2
on darwin_amd64
```

Terraform CLI と Provider がダウンロードできたので、それらを ZIP ファイルにまとめます。

```bash
$ ls -l
total 157992
-rw-r--r--  1 ryysud  staff   149B Jul 15 15:28 main.tf
drwxr-xr-x  3 ryysud  staff    96B Jul 15 15:56 plugins
-rwxr-xr-x  1 ryysud  staff    77M Jul  8 02:46 terraform

$ zip -r bundle_archive.zip terraform plugins
  adding: terraform (deflated 59%)
  adding: plugins/ (stored 0%)
  adding: plugins/registry.terraform.io/ (stored 0%)
  adding: plugins/registry.terraform.io/hashicorp/ (stored 0%)
  adding: plugins/registry.terraform.io/hashicorp/aws/ (stored 0%)
  adding: plugins/registry.terraform.io/hashicorp/aws/terraform-provider-aws_3.49.0_darwin_amd64.zip (stored 0%)
  adding: plugins/registry.terraform.io/hashicorp/aws/3.49.0.json (deflated 24%)
  adding: plugins/registry.terraform.io/hashicorp/aws/index.json (deflated 10%)

$ ls -l bundle_archive.zip
-rw-r--r--  1 ryysud  staff    73M Jul 15 16:00 bundle_archive.zip
```

bundle archive と同等のものが作成できたので、差分を確認していきます。

```bash
# bundle archive
$ zipinfo terraform_1.0.2-bundle2021071506_darwin_amd64.zip
Archive:  terraform_1.0.2-bundle2021071506_darwin_amd64.zip
Zip file size: 77860750 bytes, number of entries: 2
-rwxr-xr-x  2.0 unx 213013632 bX defN 21-Jul-15 15:24 plugins/registry.terraform.io/hashicorp/aws/3.49.0/darwin_amd64/terraform-provider-aws_v3.49.0_x5
-rwxr-xr-x  2.0 unx 80886784 bX defN 21-Jul-15 15:24 terraform
2 files, 293900416 bytes uncompressed, 77860296 bytes compressed:  73.5%

# 今回作成したもの
$ zipinfo bundle_archive.zip
Archive:  bundle_archive.zip
Zip file size: 76586882 bytes, number of entries: 8
-rwxr-xr-x  3.0 unx 80886784 bx defN 21-Jul-08 02:46 terraform
drwxr-xr-x  3.0 unx        0 bx stor 21-Jul-15 15:56 plugins/
drwxr-xr-x  3.0 unx        0 bx stor 21-Jul-15 15:30 plugins/registry.terraform.io/
drwxr-xr-x  3.0 unx        0 bx stor 21-Jul-15 15:30 plugins/registry.terraform.io/hashicorp/
drwxr-xr-x  3.0 unx        0 bx stor 21-Jul-15 15:30 plugins/registry.terraform.io/hashicorp/aws/
-rw-r--r--  3.0 unx 43084802 bx stor 21-Jul-15 15:30 plugins/registry.terraform.io/hashicorp/aws/terraform-provider-aws_3.49.0_darwin_amd64.zip
-rw-r--r--  3.0 unx      198 tx defN 21-Jul-15 15:30 plugins/registry.terraform.io/hashicorp/aws/3.49.0.json
-rw-r--r--  3.0 unx       40 tx defN 21-Jul-15 15:30 plugins/registry.terraform.io/hashicorp/aws/index.json
```

ディレクトリ構造が異なっているため同じように機能しないよう見受けられますが、bundle archive の方は Provider バイナリを配置しているため `<HOSTNAME>/<NAMESPACE>/<TYPE>/<VERSION>/<TARGET>` というディレクトリ構造で、`terraform providers mirror` コマンドは Provider バイナリを ZIP 圧縮して配置しているので `<HOSTNAME>/<NAMESPACE>/<TYPE>/terraform-provider-<TYPE>_<VERSION>_<TARGET>.zip` というディレクトリ構造という、Provider のインストール方法における [filesystem_mirror 方式](https://qiita.com/ryysud/items/a38bcc856069801878e9#filesystem_mirror) の仕様に準拠させているだけなので、機能性にはなんら違いはありません。

以下からも `terraform providers mirror` コマンドで作成したものが機能することがわかります。

```bash
$ unzip bundle_archive.zip
Archive:  bundle_archive.zip
  inflating: terraform
   creating: plugins/
   creating: plugins/registry.terraform.io/
   creating: plugins/registry.terraform.io/hashicorp/
   creating: plugins/registry.terraform.io/hashicorp/aws/
 extracting: plugins/registry.terraform.io/hashicorp/aws/terraform-provider-aws_3.49.0_darwin_amd64.zip
  inflating: plugins/registry.terraform.io/hashicorp/aws/3.49.0.json
  inflating: plugins/registry.terraform.io/hashicorp/aws/index.json

$ ls -l
total 322544
-rw-r--r--  1 ryysud  staff    73M Jul 15 16:00 bundle_archive.zip
-rw-r--r--  1 ryysud  staff   149B Jul 15 15:28 main.tf
drwxr-xr-x  3 ryysud  staff    96B Jul 15 15:56 plugins
-rwxr-xr-x  1 ryysud  staff    77M Jul  8 02:46 terraform

$ ./terraform --version
Terraform v1.0.2
on darwin_amd64

$ ./terraform init -plugin-dir plugins

Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/aws versions matching "3.49.0"...
- Installing hashicorp/aws v3.49.0...
- Installed hashicorp/aws v3.49.0 (unauthenticated)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

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.
```

## まとめ

今回は Terraform v1.0.0 で開発が終了した terraform-bundle の代替手段を紹介しました。
ニッチな内容ですが参考になれば幸いです。

## 参考資料

- https://github.com/hashicorp/terraform/tree/43f698dc9db36b722f3d60a7d6aca9b757c2e621/tools/terraform-bundle
  - 2021-07-14 時点の main ブランチ

[^1]: https://github.com/hashicorp/terraform/blob/v0.11/CHANGELOG.md#0100-august-2-2017
[^2]: https://www.terraform.io/upgrade-guides/0-10.html
[^3]: 詳細は [terraform-bundle で Terraform の実行に必要なファイルをパッケージングする](https://qiita.com/ryysud/items/7d368641a62965d68df8) を参照してください。
[^4]: 詳細は [Terraform における Provider インストール処理を理解する（2021年2月版）](https://qiita.com/ryysud/items/a38bcc856069801878e9) を参照してください。
[^5]: https://www.terraform.io/docs/cli/commands/providers/mirror.html
[^6]: [hashicorp/terraform#29069](https://github.com/hashicorp/terraform/issues/29069#issuecomment-871679330) にあるとおり Terraform Enterprise 利用者に影響する緊急の Bugfix を除いて今後はメンテナンスが実施されることはない。