Terraform プロバイダーを配布する自前のプライベートリポジトリーの構築
概要
- Terraform プロバイダーを公開するプライベートレジストリーを構築する手順例を記載しました。
- プライベートレジストリーは、静的コンテンツを提供できる Web サーバーがあれば構築することができます。
- ただし、Web サーバーについていくつかの条件を満たす必要があります。
プライベートレジストリー
- Terraform でプロバイダーを配布する HTTPS サーバーのことをレジストリーと呼びます。自前のプロバイダーを作った場合、レジストリーで公開することで、プロバイダーを色んな場所で使えるようになります。
- 公式で提供される Terraform Registry を使うのが簡単ですが、登録したプロバイダーは全世界に公開されるため、社内でのみ使用したいプロバイダーの運用などには不都合があります。
-
HCP Terraform (旧名 Terraform Cloud) であればプライベートレジストリーが利用可能ですが、以下の課題があります:
- 利用に際してはユーザー登録や、商用利用には有償プランが必要になる。
- アクセスに認証が必要になる。IP アドレス制限で社内からだけ利用可能にする、ということはできない。
- プライベートレジストリーを自前で構築するには、 Provider Registry Protocol に従ったフォーマットでプロバイダーの情報を提供する Web サーバーを用意します。
- Provider Registry Protocol はファイルのフォーマットやファイルの内容について定義したもので、動的なリクエスト・レスポンスを必要とはしていないため、 静的なコンテンツを提供する Web サーバーがあれば構築可能です。
- ただしファイルの配置場所についての制限があるため、Web サイトにある程度の条件が求められます (後述)。
- 例えば以下のような方法で構築できると考えられます (試してはいない):
- GitHub Pages
- AWS S3 による Web サイト
- Google Cloud Storage (GCS) による Web サイト
プライベートレジストリー構築のための要件
Web サイトの要件
プライベートレジストリーを既存の Web サイトで構築する場合、以下の要件を満たす必要があります:
- サイトのルートディレクトリーの
/.well-known/terraform.json
にファイルを設置できること。- terraform は最初に Remote Service Discovery という手順で、固定でこのファイルを見に行くため、このファイルの管理権限を持っていないとプライベートレジストリーとしては使用できません。
- ユーザーごとのホームディレクトリーが提供されるタイプの共用 Web サーバーだと、サイトのルートディレクトリーのファイルを自由に編集できないので、この要件を満たすのが難しくなります。
- HTTPS で動作していること。
-
Remote Service Discovery Protocol Reference#Discovery Process で記載の通り、
https:
スキーマを必要とするため、 HTTPS で動作している Web サーバーである必要があります。 - この要件のため、ローカルでの検証はちょっと面倒です。
-
Remote Service Discovery Protocol Reference#Discovery Process で記載の通り、
- HTTPS の証明書が
Subject Alternative Name
(SAN) を使って作成されていること。- 一般的な SSL 証明書ではこの要件は満たされていますが、自己署名証明書を使ってサイトを構築する場合は注意が必要です。
- ただ SAN で作られていない証明書を使った Web サイトは、 Chrome などのブラウザーで怒られるので、フツーにサイトを構築すれば SAN を使った証明書になっていると思ってよいはずです。けれどもほんとうのフツーは一体なんだろう。
- レスポンスの Content-Type を
application/json
にできること。- プライベートレジストリーは、
https://.../versions
やhttps://.../amd64
といった URL でアクセスされますが、 この時の Content-Type がapplication/json
である必要があります。 - これは意外と難しいのですが、例えば以下のような設定をすることで解決します:
-
index.json
をインデックスファイルとして提供できるようにして、versions/index.json
といったファイルを置く。-
.htaccess
で設定できるサイトの場合、以下のような設定になります:DirectoryIndex index.json
-
- Content-Type を強制的に
application/json
にする設定を行う。- これによって
versions
といった拡張子なしのファイルもapplication/json
として返すようになります。
- これによって
- Web サーバーでアクセスされたときに
.json
を補完してしてファイルを返すようにする (URL のリライト処理)。
-
- プライベートレジストリーは、
その他の要件
- GnuPG による署名を行うので、GnuPG で鍵ペアを作っておく必要があります。
- SHA256 チェックサムを使用します。このため
sha256sum
などが必要です。
デモ
プライベートレジストリーを構築するデモを以下のリポジトリーに作りました:
-
example.com/example/hello
という独自プロバイダーをプライベートレジストリーで配布します。 - あくまでローカルでの検証として提供しているので、 docker compose を使用して
example.com
というサイトを偽装して Terraform に参照させます。 - 構築したサイトで提供するファイルは registry/ 以下から参照できます。
- サイトのファイルを構築するためのスクリプトを build-registry.sh として作成したので、構築方法はこのスクリプトを見ればわかります。
プライベートレジストリーの構築手順
Terraform がプロバイダーのアクセスを行う手順を追いつつ、プライベートレジストリーの構築手順について説明します。
Terraform の公式レジストリーの動作と対比できるよう、 hashicorp/aws
での動作を見るための curl コマンドも併記しています。
プロバイダーアドレスを決める
リファレンス: Provider Registry Protocol Reference#Provider Addresses
プロバイダーのフルネーム (プロバイダーアドレス) を決めておく必要があります。
プロバイダーアドレスは以下のフォーマットになります:
(ドメイン名)/(ネームスペース)/(タイプ)
例えば AWS プロバイダー であれば、 registry.terraform.io/hashicorp/aws
になります。
特記事項として、ドメイン名が registry.terraform.io の場合は省略して良いので、通常、 AWS プロバイダーは hashicorp/aws
として参照します。
今回は example.com/example/hello
にしました。
なお一度配布したプロバイダーを別のプライベートレジストリーで配布し直す場合はドメイン名を変更することになりますが、その場合にバイナリーの再ビルドが必要になるのかは未検証です (特に、 terraform-plugin-framework だとプロバイダーアドレスをコードにいれることになる)。
ただ、たぶん、ダウンロード処理に使われるだけで、プロバイダーの処理自体ではドメイン名は考慮されていないように思うので、バイナリーの再ビルドは不要な気がしています。
プロバイダーのバージョンとプラットフォームを決める
リファレンス: Publish providers#Creating a GitHub Release
プライベートレジストリーに設置する際に、このプロバイダーをなんというバージョンで提供するかの情報が必要となります。
バージョンはセマンティックバージョニングに従って X.Y.Z と 3 つの数値で設定します。
バージョンはプロバイダー自体には含まれない情報なので、プロバイダーのバイナリーを作った後で勝手に決めればいい情報となります (実際にはそれなりに計画的にバージョンを考えながら開発したり、 go 言語のバージョニングのポリシーに従ったりすると思うけれど、わかりやすさのための極端な説明)。
プライベートレジストリーの文脈では、バージョンの表記が以下の 2 パターンあるので注意が必要です。
※これに準じなかった場合に動作不良が起きるのかは未検証です。
- v がつかないバージョン:
1.0.0
など。プライベートレジストリー上のバージョン一覧ファイルや、パッケージファイルを設置するパスで使用するバージョンではこの表記を使う。 - v がついたバージョン:
v1.0.0
など。プロバイダーのバイナリーや zip ファイルではこの表記を使う。
また、プロバイダーを利用できるプラットフォーム (OS と CPU アーキテクチャー) を決定します。
多くの場合、プロバイダーは go 言語で実装しているため、 GOOS
と GOARCH
を指定することで簡単にいろんなプラットフォーム向けのバイナリーをビルドできます。
今回は、バージョンを 1.0.0
(v1.0.0
)、 OS は linux
固定、アーキテクチャーは amd64
と arm64
として構築していきます。
Discover Protocol 用のファイルを設置する
リファレンス:
hashicorp/aws
での例:
curl https://registry.terraform.io/.well-known/terraform.json
terraform は最初に、 https://(プロバイダーアドレスのドメイン名)/.well-known/terraform.json
にアクセスに行きます。
例えば以下のようなファイルを置きます:
{
"providers.v1": "/providers/"
}
ここで別のホストを指定することもでき、プロバイダーアドレスで指定したホストとは別のホストにプロバイダーの本体を置くこともできます。
注意が必要な点として、 Content-Type が application/json
である必要があります。これは以降で設定するすべての JSON ファイルで同様です。
バージョン一覧ファイルを置く
リファレンス: Provider Registry Protocol Reference#List Available Versions
hashicorp/aws
での例:
curl https://registry.terraform.io/v1/providers/hashicorp/aws/versions
/providers/example/hello/versions
に、プロバイダーのバージョン一覧を記載したファイルを設置します。
-
/providers/
の部分は前の手順で設置した Discovery Protocol 用のファイルで指定したパスです。 -
example/hello
はプロバイダーアドレスから決定します。
例えば以下のようなファイルになります:
{
"versions": [
{
"version": "1.0.0",
"platforms": [
{"os": "linux", "arch": "amd64"},
{"os": "linux", "arch": "arm64"}
]
}
]
}
terraform はこのファイルと、 required_providers
に記載されたバージョン制約、実行している OS とアーキテクチャーから、使用するプロバイダーのバージョン、OS、アーキテクチャーを決定します。
このファイルは以降で作成するプロバイダーパッケージ情報を収集して自動構築することもできると思います。
プロバイダーパッケージ情報を置く
リファレンス:
- Provider Registry Protocol Reference#Find a Provider Package
- Publish providers#Manually Preparing a Release
hashicorp/aws
での例:
curl https://registry.terraform.io/v1/providers/hashicorp/aws/5.93.0/download/linux/amd64
curl https://releases.hashicorp.com/terraform-provider-aws/5.93.0/terraform-provider-aws_5.93.0_SHA256SUMS
curl https://releases.hashicorp.com/terraform-provider-aws/5.93.0/terraform-provider-aws_5.93.0_SHA256SUMS.72D7468F.sig
curl -O https://releases.hashicorp.com/terraform-provider-aws/5.93.0/terraform-provider-aws_5.93.0_linux_amd64.zip
/providers/example/hello/1.0.0/download/linux/amd64
に、プロバイダーのバージョン一覧を記載したファイルを設置します。
-
/providers/
の部分は前の手順で設置した Discovery Protocol 用のファイルで指定したパスです。 -
example/hello
はプロバイダーアドレスから決定します。 -
1.0.0
,linux/amd64
の部分は、前の手順で設置したバージョン一覧ファイルから terraform が決定したバージョン、OS、アーキテクチャーが使用されます。
例えば以下のようなファイルになります:
{
"protocols": [
"6.0"
],
"os": "linux",
"arch": "amd64",
"filename": "terraform-provider-hello_v1.0.0_linux_amd64.zip",
"download_url": "terraform-provider-hello_v1.0.0_linux_amd64.zip",
"shasums_url": "terraform-provider-hello_v1.0.0_linux_amd64_SHA256SUMS",
"shasums_signature_url": "terraform-provider-hello_v1.0.0_linux_amd64_SHA256SUMS.sig",
"shasum": "f57f6a379c014dcf3c522964aef8fe38e1b1afb1f781256539c4baa10e77ed0f",
"signing_keys": {
"gpg_public_keys": [
{
"key_id": "4794E56AC17DADB3",
"ascii_armor": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmDMEZ/HX3RYJKwYBBAHaRw8BAQdAwtgb5Yc+Eyg5AurKOIzt0dMcXUX3tsV7Xilw\nU/QgRpu0IlByb3ZpZGVyIERlbW8gPGlrZWRhbUBleGFtcGxlLmNvbT6IkwQTFgoA\nOxYhBIl8EvH+RE0YUDVCgUeU5WrBfa2zBQJn8dfdAhsDBQsJCAcCAiICBhUKCQgL\nAgQWAgMBAh4HAheAAAoJEEeU5WrBfa2ziNEA/A3kcQhN4rhkWDkz+r9aCzFQove0\nqNfOpE/HrOdVG8p9AP9LYwjqe4EMFecCahi3tk5MzlLg/mcgSewutDuYwMLcDA==\n=mcPP\n-----END PGP PUBLIC KEY BLOCK-----\n"
}
]
}
}
このファイルに記述する情報を作成するのがプライベートレジストリー構築の山場になります。
プロバイダー本体とzipファイルの構築
filename
と download_url
で使用する zip ファイルを構築します。
ここで使用しているファイル名は、公式ドキュメントの Publish providers#Manually Preparing a Release に記載されたものに準じています。他のファイル名でも動作する可能性がありますが、公式ドキュメントの命名に従っておくのが安全でしょう。
-
プロバイダーのバイナリーを
terraform-provider-{NAME}_v{VERSION}
という名称で作成します。CGO_ENABLED=0 go build -o terraform-provider-hello_v1.0.0 main.go
-
バイナリーを
terraform-provider-{NAME}_{VERSION}_{OS}_{ARCH}.zip
という名前で zip にする。zip terraform-provider-hello_v1.0.0_linux_arm64.zip terraform-provider-hello_v1.0.0
- 他にライセンスファイルなどを同梱しても良いです。
最終的に使用するのは zip ファイルだけなので、バイナリーファイルは削除してよいです。
SHA256 チェックサムファイルの作成
shasums_url
と shasum
で使用するために、 zip の SHA256 チェックサムを計算します。
shasums_url
には sha256sum
コマンドが出力する、以下のフォーマットのファイルを設置します:
f57f6a379c014dcf3c522964aef8fe38e1b1afb1f781256539c4baa10e77ed0f terraform-provider-hello_v1.0.0_linux_amd64.zip
そして shasum
には上記のチェックサムの部分、f57f6a379c014dcf3c522964aef8fe38e1b1afb1f781256539c4baa10e77ed0f
を設定します。
このため shasums_url
と shasum
は冗長な関係にあります。
なお、 shasums_url
は複数のプロバイダーパッケージで共有できます。このため、全プラットフォームのパッケージの SHA256 チェックサムを 1 つのファイルにまとめるなどのつくりにしてもよいでしょう。
今回は簡単のため、パッケージ毎に別のファイルで用意します。
例えば以下のようにして shasums_url
に使用するファイルを出力します:
sha256sum terraform-provider-hello_v1.0.0_linux_arm64.zip \
> terraform-provider-hello_v1.0.0_linux_arm64_SHA256SUMS
SHA256 チェックサムファイルの署名の作成
shasums_signature_url
で使用する、 shasums_url
のファイルの GnuPG 署名ファイルを作成します。
GnuPG の鍵ペアは事前に作ってあるものとします。
以下のように detach-sig
コマンドを使用して署名ファイルを作成します:
gpg --output terraform-provider-hello_v1.0.0_linux_arm64_SHA256SUMS.sig \
--detach-sig \
terraform-provider-hello_v1.0.0_linux_arm64_SHA256SUMS
署名に使った鍵の ID の取得と公開鍵の出力
signing_keys.gpg_public_keys[].key_id
に署名に使った鍵の ID を、
signing_keys.gpg_public_keys[].ascii_armor
に署名に使った鍵の公開鍵を記載します。
鍵の ID は以下のようにして取得します:
$ gpg --list-secret-keys --keyid-format=long
[keyboxd]
---------
sec ed25519/4794E56AC17DADB3 2025-04-06 [SC]
897C12F1FE444D18503542814794E56AC17DADB3
uid [ultimate] Provider Demo <ikedam@example.com>
4794E56AC17DADB3
が鍵の ID です。
また、公開鍵を以下のようにして出力します:
$ gpg --armor --export 4794E56AC17DADB3
-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEZ/HX3RYJKwYBBAHaRw8BAQdAwtgb5Yc+Eyg5AurKOIzt0dMcXUX3tsV7Xilw
U/QgRpu0IlByb3ZpZGVyIERlbW8gPGlrZWRhbUBleGFtcGxlLmNvbT6IkwQTFgoA
OxYhBIl8EvH+RE0YUDVCgUeU5WrBfa2zBQJn8dfdAhsDBQsJCAcCAiICBhUKCQgL
AgQWAgMBAh4HAheAAAoJEEeU5WrBfa2ziNEA/A3kcQhN4rhkWDkz+r9aCzFQove0
qNfOpE/HrOdVG8p9AP9LYwjqe4EMFecCahi3tk5MzlLg/mcgSewutDuYwMLcDA==
=mcPP
-----END PGP PUBLIC KEY BLOCK-----
もちろん、 GnuPG の鍵は基本的に変更しないものなので、ここの出力は一度行ったものをずっと再利用できます。
パッケージ情報の JSON の構築
JSON を作成します:
{
"protocols": [
"6.0"
],
"os": "linux",
"arch": "amd64",
"filename": "terraform-provider-hello_v1.0.0_linux_amd64.zip",
"download_url": "terraform-provider-hello_v1.0.0_linux_amd64.zip",
"shasums_url": "terraform-provider-hello_v1.0.0_linux_amd64_SHA256SUMS",
"shasums_signature_url": "terraform-provider-hello_v1.0.0_linux_amd64_SHA256SUMS.sig",
"shasum": "f57f6a379c014dcf3c522964aef8fe38e1b1afb1f781256539c4baa10e77ed0f",
"signing_keys": {
"gpg_public_keys": [
{
"key_id": "4794E56AC17DADB3",
"ascii_armor": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmDMEZ/HX3RYJKwYBBAHaRw8BAQdAwtgb5Yc+Eyg5AurKOIzt0dMcXUX3tsV7Xilw\nU/QgRpu0IlByb3ZpZGVyIERlbW8gPGlrZWRhbUBleGFtcGxlLmNvbT6IkwQTFgoA\nOxYhBIl8EvH+RE0YUDVCgUeU5WrBfa2zBQJn8dfdAhsDBQsJCAcCAiICBhUKCQgL\nAgQWAgMBAh4HAheAAAoJEEeU5WrBfa2ziNEA/A3kcQhN4rhkWDkz+r9aCzFQove0\nqNfOpE/HrOdVG8p9AP9LYwjqe4EMFecCahi3tk5MzlLg/mcgSewutDuYwMLcDA==\n=mcPP\n-----END PGP PUBLIC KEY BLOCK-----\n"
}
]
}
}
-
protocols
- このプロバイダーがサポートする terraform の GRPC プロトコルのバージョンのリストを記載します。
- ここについての明確な資料が見当たらず、どういうフォーマットで指定するのか (X.X なのか、 X.X.X なのかとか)、マイナーバージョン以下のバージョンがどういうものがあるのか、プロバイダーのプロトコルバージョンをどうやって確認すればいいのかが不明です。
- ただ、 Terraform plugin protocol を見る限り、以下の規則で設定すれば良さそうです:
- terraform-plugin-framework で作成している: 6.0
- terraform-plugin-framework は 5.0 にも対応しているようなのですが、その切替がどのように行われるのかが不明です。
- terraform-plugin-sdk/v2 で作成している: 5.0
- terraform-plugin-framework で作成している: 6.0
-
os
,arch
- このパッケージのプラットフォームを記載します。
-
filename
- zip ファイルのファイル名を記載します。
-
download_url
- zip ファイルをダウンロードするための URL を記載します。相対パスでの指定も可能なので、ファイルをこの JSON ファイルと同じディレクトリー内において、ファイル名だけ指定するのが簡単でしょう。
-
shasums_url
- sha256sum で作成したファイルをダウンロードするための URL を記載します。相対パスでの指定も可能なので、ファイルをこの JSON ファイルと同じディレクトリー内において、ファイル名だけ指定するのが簡単でしょう。
-
shasums_signature_url
-
gpg --detatch-sig
で作成したファイルをダウンロードするための URL を記載します。相対パスでの指定も可能なので以下略。
-
-
shasum
- sha256sum で作成したチェックサムの値を記載します。
-
signing_keys.gpg_public_keys[0].key_id
- 署名に使用した GnuPG 鍵の ID を記載します。
- 実際にはこの情報は
ascii_armor
にも含まれているはずなので、単に冗長に情報を含めているものと思われます。
-
signing_keys.gpg_public_keys[0].ascii_armor
- 署名に使用した GnuPG 鍵の公開鍵を記載します。
完成
ここまでの手順で作ったファイルを Web サイトに配置し、Terraform テンプレートで以下のように required_providers
で指定を行えば、自作プロバイダーをレジストリーからダウンロードする形で Terraform で利用できるようになります。
terraform {
required_providers {
hello = {
source = "example.com/example/hello"
version = "~> 1.0.0"
}
}
}
terraform init
などの処理でエラーが出る場合は、 TF_LOG=debug
を設定して詳細ログを出力して動作を確認するとよいでしょう。
Discussion