Terraform 自作プロバイダを作成してみる

美ねこ(TerraformとGo言語共にスーパー初心者)がTerraformの自作プロバイダを作成

ディレクトリ構成
.
├── Makefile
├── go.mod
├── go.sum
├── main.go
├── main.tf
└── provider
├── provider.go
└── resource_hello.go

go.mod
module example.com/hello-provider
go 1.20
require github.com/hashicorp/terraform-plugin-framework v1.0.0
require (
github.com/fatih/color v1.13.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/hashicorp/go-hclog v1.2.1 // indirect
github.com/hashicorp/go-plugin v1.4.6 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/terraform-plugin-go v0.14.2 // indirect
github.com/hashicorp/terraform-plugin-log v0.7.0 // indirect
github.com/hashicorp/terraform-registry-address v0.1.0 // indirect
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/oklog/run v1.0.0 // indirect
github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect
github.com/vmihailenco/tagparser v0.1.1 // indirect
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
golang.org/x/text v0.4.0 // indirect
google.golang.org/appengine v1.6.5 // indirect
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
google.golang.org/grpc v1.51.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
)
解説
go.modとはモジュール管理ファイルのこと
module の定義
このプロジェクトのモジュール名を定義している部分
example.com の部分はダミーのドメイン名だけど、通常は GitHub や組織のドメイン名を使用する
module example.com/hello-provider
Go のバージョン
go 1.20
主要な依存関係
Terraform の公式プロバイダフレームワークを使用
自作プロバイダを作成する際に必要
require github.com/hashicorp/terraform-plugin-framework v1.0.0
indirect な依存関係
indirectとは、このプロジェクトが直接必要としているわけではなく主要なライブラリが必要としている必要としているもの
require (
github.com/fatih/color v1.13.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/hashicorp/go-hclog v1.2.1 // indirect
github.com/hashicorp/go-plugin v1.4.6 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/terraform-plugin-go v0.14.2 // indirect
github.com/hashicorp/terraform-plugin-log v0.7.0 // indirect
github.com/hashicorp/terraform-registry-address v0.1.0 // indirect
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/oklog/run v1.0.0 // indirect
github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect
github.com/vmihailenco/tagparser v0.1.1 // indirect
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
golang.org/x/text v0.4.0 // indirect
google.golang.org/appengine v1.6.5 // indirect
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
google.golang.org/grpc v1.51.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
)

手順
1
ルートディレクトリで下記実行するとgo.modが作成される
この時点でgoのバージョンは指定されていない
go mod init example.com/hello-provider(モジュール名)
2
主要なフレームワークを追加する
go get github.com/hashicorp/terraform-plugin-framework@v1.0.0
すると下記がgo.modに追加
require github.com/hashicorp/terraform-plugin-framework v1.0.0
3
下記を叩くことで依存関係を整理
↓
使われていない依存関係を削除
必要な間接依存関係(indirect として)を追加
go.mod に、現在の Go バージョンが追加されます(例:go 1.20)
go.sum ファイルが生成され、依存関係のハッシュ情報が記録されます
go mod tidy

main.go
package main
import (
"context"
"example.com/hello-provider/provider"
"github.com/hashicorp/terraform-plugin-framework/providerserver"
)
func main() {
providerserver.Serve(
context.Background(),
provider.New,
providerserver.ServeOpts{
Address: "registry.terraform.io/torcheees/hello",
},
)
}

解説
パッケージ宣言
package main
mainパッケージですよという宣言
エントリーポイント(プログラムがスタートする地点)のこと
インポート
import (
"context"
"example.com/hello-provider/provider"
"github.com/hashicorp/terraform-plugin-framework/providerserver"
)
context
Go の標準ライブラリ。コンテキスト管理用(キャンセルやタイムアウト制御)
"example.com/hello-provider/provider"
自作プロバイダーの実装(provider パッケージ)(これはプロジェクトに自分で作成したパッケージ)
"github.com/hashicorp/terraform-plugin-framework/providerserver"
自分で作ったプロバイダーをそのまま実行できるわけではない ので、Terraform とカスタムプロバイダーが通信できるように、providerserver を使ってサーバーを起動する
providerserver.Serve()
を使用することでTerraform に 「このプロバイダーを使ってね!」と伝えることができる
terraform-plugin-frameworkの一部(サブパッケージだからgo.modには記述なし)

main関数
func main() {
providerserver.Serve(
context.Background(),
provider.New,
providerserver.ServeOpts{
Address: "registry.terraform.io/torcheees/hello",
},
)
}
用途
「Terraform プラグインを起動する」ための関数
providerserver.Serve()
Terraform がこのプロバイダーを認識できるようにするためのもの
「Terraform と話せるサーバーを起動する!」 みたいな感じ
context.Background()
「プログラムの環境を用意する!」 みたいなもの
ここでは 「特に特別な設定はないよ!」という感じ
provider.New()
これは 「プロバイダーの本体を作る」 ための関数
プロバイダーの中身(リソースやデータソースなど)を定義した provider.go の New() を呼び出している
Address: "registry.terraform.io/torcheees/hello"
Terraform が「このプロバイダーはここにあるよ!」と知るための情報
"registry.terraform.io/torcheees/hello" というアドレスは、Terraform がこのプロバイダーを探すときの 住所 みたいなもの

provider.go
package provider
import (
"context"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/resource"
)
func New() provider.Provider {
return &helloProvider{}
}
type helloProvider struct{}
func (p *helloProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) {
resp.TypeName = "hello_provider"
resp.Version = "0.1.0"
}
func (p *helloProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
}
func (p *helloProvider) Resources(ctx context.Context) []func() resource.Resource {
return []func() resource.Resource{
NewHelloResource,
}
}
func (p *helloProvider) DataSources(ctx context.Context) []func() datasource.DataSource {
return []func() datasource.DataSource{}
}
func (p *helloProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
}

解説
「プロバイダーの本体」を作る役割をしているファイル
New() 関数:プロバイダーを作る関数
func New() provider.Provider {
return &helloProvider{}
}
新しいhello_provider
を作成してTerraformに渡している
main.go
の Provider.new
で New()
が呼ばれることでTerraformがこのプロバイダを使用できるようになる
helloProvider とは?
type helloProvider struct{}
プロバイダの入れ物を作っている
このプロバイダの中に、情報を入れていくイメージ
Metadata() 関数:プロバイダーの名前とバージョンを設定
func (p *helloProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) {
resp.TypeName = "hello_provider"
resp.Version = "0.1.0"
}
Terraform に「このプロバイダーの名前は hello_provider で、バージョンは 0.1.0 だよ!」と教えている
これがないと、Terraform は 「このプロバイダーが何なのかわからない!」 となる
Schema() 関数:プロバイダーの設定(今回は空)
func (p *helloProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
}
プロバイダーに特別な設定を追加するための関数(今回は何もなし)
「APIキー」や「接続情報」を設定したいときにここに書く
Resources() 関数:プロバイダーが提供するリソース
func (p *helloProvider) Resources(ctx context.Context) []func() resource.Resource {
return []func() resource.Resource{
NewHelloResource, // helloリソースを登録
}
}
このプロバイダーには hello というリソースがあるよ!と教えている
DataSources() 関数:プロバイダーが提供するデータソース
func (p *helloProvider) DataSources(ctx context.Context) []func() datasource.DataSource {
return []func() datasource.DataSource{}
}
データソースを登録する場所(今回は空っぽ)!
データソースとは
Terraform のリソースとは違って、「データを取得するだけ」のもの
例えば aws_ami は AWS の AMI(仮想マシンイメージ)の情報を取得するデータソース
Configure() 関数:プロバイダーの設定(今回は空)
func (p *helloProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
}
プロバイダーを設定する場所(今回は何もなし)
例えば「APIキーを入力してね!」みたいな設定が必要なときにここで処理