コンテンツにスキップ

独自プロバイダー@Terraform

はじめに

本サイトにつきまして、以下をご認識のほど宜しくお願いいたします。


最小限のプロバイダー実装

Terraform Plugin Framework を使用すると、Go で独自プロバイダーを実装できる。

package provider

import (
    "context"

    "github.com/hashicorp/terraform-plugin-framework/datasource"
    "github.com/hashicorp/terraform-plugin-framework/provider"
    providerschema "github.com/hashicorp/terraform-plugin-framework/provider/schema"
    "github.com/hashicorp/terraform-plugin-framework/resource"
)

type exampleProvider struct{}

func (p *exampleProvider) Metadata(
    _ context.Context,
    _ provider.MetadataRequest,
    resp *provider.MetadataResponse,
) {
    // fooリソースを定義する
    resp.TypeName = "foo"
}

func (p *exampleProvider) Schema(
    _ context.Context,
    _ provider.SchemaRequest,
    resp *provider.SchemaResponse,
) {
    resp.Schema = providerschema.Schema{}
}

func (p *exampleProvider) Configure(
    context.Context,
    provider.ConfigureRequest,
    *provider.ConfigureResponse,
) {
}

func (p *exampleProvider) Resources(context.Context) []func() resource.Resource {
    return []func() resource.Resource{
        func() resource.Resource { return &fooResource{} },
    }
}

func (p *exampleProvider) DataSources(context.Context) []func() datasource.DataSource {
    return []func() datasource.DataSource{
        func() datasource.DataSource { return &barDataSource{} },
    }
}

func New() provider.Provider {
    return &exampleProvider{}
}

最小限のResource

Resource は、Terraform で作成、読み取り、更新、削除する対象を表す。

次のような resource を定義するための Data Source を実装する。

resource "foo_bar" "this" {}
package provider

import (
    "context"

    "github.com/hashicorp/terraform-plugin-framework/resource"
    "github.com/hashicorp/terraform-plugin-framework/resource/schema"
    "github.com/hashicorp/terraform-plugin-framework/types"
)

type fooResource struct{}

type fooResourceModel struct {
    ID types.String `tfsdk:"id"`
}

func (r *fooResource) Metadata(
    _ context.Context,
    req resource.MetadataRequest,
    resp *resource.MetadataResponse,
) {
    // barタイプを定義する
    // fooリソースはプロバイダーのMetadataで定義している
    // resource "foo_bar" になる
    resp.TypeName = req.ProviderTypeName + "_bar"
}

func (r *fooResource) Schema(
    _ context.Context,
    _ resource.SchemaRequest,
    resp *resource.SchemaResponse,
) {
    resp.Schema = schema.Schema{
        Attributes: map[string]schema.Attribute{
            "id": schema.StringAttribute{Computed: true},
        },
    }
}

func (r *fooResource) Create(
    ctx context.Context,
    _ resource.CreateRequest,
    resp *resource.CreateResponse,
) {
    resp.Diagnostics.Append(resp.State.Set(ctx, &fooResourceModel{
        ID: types.StringValue("example-id"),
    })...)
}

func (r *fooResource) Read(
    context.Context,
    resource.ReadRequest,
    *resource.ReadResponse,
) {
}

func (r *fooResource) Update(
    context.Context,
    resource.UpdateRequest,
    *resource.UpdateResponse,
) {
}

func (r *fooResource) Delete(
    ctx context.Context,
    _ resource.DeleteRequest,
    resp *resource.DeleteResponse,
) {
    resp.State.RemoveResource(ctx)
}

最小限のData Source

Data Source は、既存の情報を読み取って State に保存する。

次のような data を定義するための Data Source を実装する。

data "foo_bar" "this" {}
package provider

import (
    "context"

    "github.com/hashicorp/terraform-plugin-framework/datasource"
    "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
    "github.com/hashicorp/terraform-plugin-framework/types"
)

type barDataSource struct{}

type barDataSourceModel struct {
    Message types.String `tfsdk:"message"`
}

func (d *barDataSource) Metadata(
    _ context.Context,
    req datasource.MetadataRequest,
    resp *datasource.MetadataResponse,
) {
    // barタイプを定義する
    // fooリソースはプロバイダーのMetadataで定義している
    // data "foo_bar" になる
    resp.TypeName = req.ProviderTypeName + "_bar"
}

func (d *barDataSource) Schema(
    _ context.Context,
    _ datasource.SchemaRequest,
    resp *datasource.SchemaResponse,
) {
    resp.Schema = schema.Schema{
        Attributes: map[string]schema.Attribute{
            "message": schema.StringAttribute{Computed: true},
        },
    }
}

func (d *barDataSource) Read(
    ctx context.Context,
    _ datasource.ReadRequest,
    resp *datasource.ReadResponse,
) {
    resp.Diagnostics.Append(resp.State.Set(ctx, &barDataSourceModel{
        Message: types.StringValue("Hello, Terraform!"),
    })...)
}

受け入れテスト

Go 標準の testing パッケージと terraform-plugin-testing を組み合わせ、Terraform Provider の受け入れテストを実装する。

受け入れテストは、次のように TF_ACC=1 を設定する必要がある。

TF_ACC=1 go test ./internal/provider/... -v
package provider_test

import (
    "testing"

    "github.com/hashicorp/terraform-plugin-framework/providerserver"
    "github.com/hashicorp/terraform-plugin-go/tfprotov6"
    "github.com/hashicorp/terraform-plugin-testing/helper/resource"

    exampleprovider "example.com/example/internal/provider"
)

var testProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){
    "foo": providerserver.NewProtocol6WithError(exampleprovider.New()),
}

func TestAccProvider(t *testing.T) {
    resource.Test(t, resource.TestCase{
        ProtoV6ProviderFactories: testProviderFactories,
        Steps: []resource.TestStep{
            {
                Config: `
terraform {
  required_providers {
    foo = {
      source = "example.com/example/example"
    }
  }
}

resource "foo_bar" "this" {}

data "foo_bar" "this" {}
`,
                Check: resource.ComposeAggregateTestCheckFunc(
                    resource.TestCheckResourceAttr(
                        "foo_bar.this", "id", "example-id",
                    ),
                    resource.TestCheckResourceAttr(
                        "data.foo_bar.this", "message", "Hello, Terraform!",
                    ),
                ),
            },
        },
    })
}