Open9

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.goProvider.newNew() が呼ばれることで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キーを入力してね!」みたいな設定が必要なときにここで処理