provider-actions について
Plugin Framework を使って Terraform Provider Actions を実装するための開発ガイドです。Provider の設定、スキーマ、呼び出し、進捗、エラー処理、テスト、ライフサイクル連動操作の文書化を扱います。
主な機能
- 01ProviderWithActions の設定
- 02Action スキーマの定義と検証
- 03呼び出しと進捗報告の実装
- 04タイムアウトとエラーへの対応
- 05ライフサイクル連動 Action のテスト
- 06Action ドキュメントの作成
活用シーン
- 01Provider に最初の Action を追加
- 02Terraform のライフサイクルイベントで起動する API 操作の実装
- 03Action の結果と失敗ケースのテスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ hashicorp/ agent-skills --skill provider-actionsインストール後、Claude Code で/provider-actionsを入力して読み込まれたか確認します。
詳細
Terraform プロバイダーアクションの実装ガイド
概要
Terraform Actions を使うと、Terraform のライフサイクル中に命令型の操作を実行できます。アクションは、ライフサイクルの特定のイベント(作成・更新・破棄の前後)でプロバイダーの操作を実行できる実験的な機能です。
参考資料:
最初のアクションのセットアップ
これまでアクションを使用したことのないプロバイダーに最初のアクションを追加する場合、次のような一度限りの足場づくりが必要です。
ProviderWithActionsを実装する —[]func() action.Actionを返すActions()メソッドをプロバイダーに追加します。ConfigureでActionDataを設定する — プロバイダーのConfigureメソッドでは、既存のResourceData、DataSourceData、EphemeralResourceDataの割り当てに加えて、resp.ActionData = vを設定する必要があります。ActionWithConfigure基底型を作成する — プロバイダーで埋め込み基底型(例:ResourceWithConfigure)を使用している場合は、action.ConfigureRequest/action.ConfigureResponseを実装する同等のActionWithConfigure型を作成します。- アクションスキーマのヘルパーのバリエーション — プロバイダーでヘルパー関数を使って共通のスキーマ属性(例:
namespace)を注入している場合、action/schemaとresource/schemaでは型が異なるため、アクションスキーマ用のバリエーションが必要です。
ファイル構成
ほとんどのプロバイダーでは、プロバイダーパッケージ内でアクションをリソースと並べて管理します。
internal/provider/
├── <action_name>_action.go # Action implementation
└── <action_name>_action_test.go # Action tests
(複数のサービスを扱う大規模なプロバイダーでは、代わりに internal/service/<service>/ パッケージを使用します。対象リポジトリの構成に従ってください。)
ドキュメントは、ほかの生成済みドキュメントとともに配置します。
docs/actions/
└── <action_name>.md # User-facing documentation
(古い大規模プロバイダーの中には、website/docs/actions/<name>.html.markdown に手書きのドキュメントを置くものもあります。リポジトリの方式に合わせてください。)
アクションスキーマの定義
アクションでは、標準的なスキーマパターンで Terraform Plugin Framework を使用します。
func (a *actionType) Schema(ctx context.Context, req action.SchemaRequest, resp *action.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
// Required configuration parameters
"resource_id": schema.StringAttribute{
Required: true,
Description: "ID of the resource to operate on",
},
// Optional parameters with defaults
"timeout": schema.Int64Attribute{
Optional: true,
Description: "Operation timeout in seconds",
Default: int64default.StaticInt64(1800),
Computed: true,
},
},
}
}
よくあるスキーマの問題
特にスキーマ定義に注意してください — 最初の草案の後によく見られる問題は次のとおりです。
-
型の不一致
- モデル構造体では
types.String/types.Int64を使用し、スキーマではgithub.com/hashicorp/terraform-plugin-framework/typesのtypes.StringTypeを使用します。他のパッケージの型を混在させないでください。 - 大規模なプロバイダーの中には、独自の型パッケージを重ねて使用しているものもあります(例:terraform-provider-aws の内部
fwtypes)。そのようなリポジトリ内では、標準の型ではなく、リポジトリの規約に一貫して従ってください。
- モデル構造体では
-
リスト/マップの要素型
// WRONG - missing ElementType "items": schema.ListAttribute{ Optional: true, } // CORRECT "items": schema.ListAttribute{ Optional: true, ElementType: types.StringType, } -
計算済みとオプション
- デフォルト値のある属性には、
Optional: trueとComputed: trueの両方を指定する必要があります。 - デフォルト値がない限り、アクション入力を
Computedとして指定しないでください。
- デフォルト値のある属性には、
-
バリデーターのインポート
// Ensure proper imports "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" -
リージョン/プロバイダー属性(AWS などのマルチリージョンプロバイダー)
- プロバイダーに共通のリージョン処理がある場合は、それを使用してください。
- アクションスキーマでプロバイダーレベルの設定を手動で再定義しないでください。
-
ネストされた属性
- 複雑な構造には、適切なネストされたオブジェクト型を使用してください。
- ネストされた型が適切に定義されていることを確認してください。
スキーマ検証チェックリスト
提出前に、次の項目を確認してください。
- すべての属性に説明がある
- リスト/マップ属性に
ElementTypeが定義されている - バリデーターが正しくインポートされ、適用されている
- モデル構造体で適切なフレームワーク型が使用されている
- デフォルト値のあるオプション属性に
Computedが指定されている - 型エラーなしでコードをコンパイルできる
- 型の不一致を検出するため、
go buildを実行する
アクションの Invoke メソッド
Invoke メソッドにアクションのロジックを記述します。
func (a *actionType) Invoke(ctx context.Context, req action.InvokeRequest, resp *action.InvokeResponse) {
var data actionModel
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// a.client was stored by Configure (from req.ProviderData), the same
// pattern resources use.
resp.SendProgress(action.InvokeProgressEvent{Message: "Starting operation..."})
// Implement action logic with error handling
// Use context for timeout management
// Poll for completion if async operation
resp.SendProgress(action.InvokeProgressEvent{Message: "Operation completed"})
}
主な実装要件
1. 進捗報告
- リアルタイムの更新には
resp.SendProgress(action.InvokeProgressEvent{...})を使用します。 - 長時間かかる処理中は、意味のある進捗メッセージを表示します。
- 重要な節目で進捗を更新します。
- 長時間かかる処理では経過時間を含めます。
2. タイムアウト管理
- 設定可能なタイムアウトパラメーターを必ず含めます(デフォルト:1800s)
- API 呼び出しには
context.WithTimeout()を使用します - タイムアウトエラーを適切に処理します
- タイムアウトの範囲を検証します(通常は60~7200秒)
3. エラー処理
resp.Diagnostics.AddError()で診断情報を追加します- コンテキストを含む明確なエラーメッセージを提示します
- 関連する場合は API エラーの詳細を含めます
- プロバイダーのエラータイプを、ユーザーにわかりやすいメッセージに対応付けます
- 想定されるすべてのエラーケースを文書化します
エラー処理の例:
// Handle specific errors
var notFound *types.ResourceNotFoundException
if errors.As(err, ¬Found) {
resp.Diagnostics.AddError(
"Resource Not Found",
fmt.Sprintf("Resource %s was not found", resourceID),
)
return
}
// Generic error handling
resp.Diagnostics.AddError(
"Operation Failed",
fmt.Sprintf("Could not complete operation for %s: %s", resourceID, err),
)
4. プロバイダー SDK の統合
- Configure 時に保存した API クライアント(
a.client)を使用します。リソースおよびデータソースと共有されます - 一覧表示操作ではページネーションを処理します
- 一時的な障害に対する再試行ロジックを実装します
- 適切なエラータイプを使用します
5. パラメーターの検証
- フレームワークのバリデーターを使用して入力を検証します
- 操作の前にリソースの存在を検証します
- 競合するパラメーターがないか確認します
- プロバイダーの命名要件に照らして検証します
6. ポーリングと待機
完了まで待機する必要がある操作では、コンテキストの期限内にティッカーを使ってポーリングし、進行状況を随時報告します。(または、リソースが使用するものと同じ待機プリミティブである
github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry の retry.StateChangeConf を使用します。)
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
// Poll fast, report slow: progress events cross the plugin protocol, so
// throttle them instead of emitting one per poll.
start := time.Now()
var lastProgress time.Time
for {
res, err := findResource(ctx, a.client, id)
if err != nil {
resp.Diagnostics.AddError("Error polling operation", fmt.Sprintf("checking status of %s: %s", id, err))
return
}
switch res.Status {
case "AVAILABLE", "COMPLETED":
resp.SendProgress(action.InvokeProgressEvent{Message: "Operation completed"})
return
case "CREATING", "PENDING":
if time.Since(lastProgress) >= 30*time.Second {
lastProgress = time.Now()
resp.SendProgress(action.InvokeProgressEvent{
Message: fmt.Sprintf("Status: %s, Elapsed: %v", res.Status, time.Since(start).Round(time.Second)),
})
}
default:
resp.Diagnostics.AddError("Operation Failed", fmt.Sprintf("%s entered unexpected status %q", id, res.Status))
return
}
select {
case <-ctx.Done():
resp.Diagnostics.AddError("Operation Timed Out", fmt.Sprintf("%s did not complete within %v", id, timeout))
return
case <-ticker.C:
}
}
一般的なアクションパターン
バッチ操作
- 設定可能なバッチ単位で項目を処理します
- バッチごとに進行状況を報告します
- 部分的な失敗を適切に処理します
- プレフィックス/フィルターパラメーターをサポートします
コマンドの実行
- コマンドを送信して操作 ID を取得します
- 完了ステータスをポーリングします
- 出力を取得して報告します
- ポーリング中のタイムアウトを処理します
- 実行前にリソースの存在を検証します
サービスの呼び出し
- パラメーターを指定してサービスを呼び出します
- 完了を待機します(同期の場合)
- 出力/結果を返します
- サービス固有のエラーを処理します
リソースの状態変更
- 現在の状態を検証します
- 状態変更を適用します
- 目標の状態になるまでポーリングします
- 遷移中の状態を処理します
非同期ジョブの送信
- 設定を指定してジョブを送信します
- ジョブ ID を取得します
- 完了まで待機するかどうかを選択します
- ジョブのステータスを報告します
アクショントリガー
アクションは、Terraform の設定にある action_trigger ライフサイクルブロックを介して呼び出されます。対応するトリガーのない単独の action ブロックは宣言されるだけで、実行されません。
HCL 構文
アクションのパラメーターは config {} ブロックで囲む必要があります。トリガーの参照には action. プレフィックスを使用し、actions はリストです。イベントは引用符で囲まない識別子です。
action "provider_service_action" "name" {
config {
parameter = value
}
}
resource "terraform_data" "trigger" {
lifecycle {
action_trigger {
events = [after_create]
actions = [action.provider_service_action.name]
}
}
}
使用可能なトリガーイベント
サポート対象のイベント(Terraform 1.14 時点):
before_create- リソース作成前after_create- リソース作成後before_update- リソース更新前after_update- リソース更新後
サポート対象外(Terraform 1.14 時点。最新のリリースノートを確認してください):
before_destroy- 使用できません(検証エラーが発生します)after_destroy- 使用できません(検証エラーが発生します)
アクションのテスト
受け入れテスト
- 有効なパラメーターでアクションが呼び出されることをテストします
- タイムアウトの状況をテストします
- エラー条件をテストします
- プロバイダーの状態変更を検証します
- 進行状況の報告をテストします
- カスタムパラメーターを使用してテストします
- トリガーベースの呼び出しをテストします
テストパターン
func TestAccExampleAction_basic(t *testing.T) {
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.SkipBelow(tfversion.Version1_14_0),
},
Steps: []resource.TestStep{
{
Config: testAccActionConfig_basic(),
ConfigStateChecks: []statecheck.StateCheck{
// assert the observable effect of the action on the
// triggering resource
},
},
},
})
}
スイーパー関数によるテストのクリーンアップ
テストで呼び出したアクションによって、実際のリソースが残ることがあります。スイーパー(一覧取得 → テスト用プレフィックス付きの名前をフィルタリング → 削除)を登録して、残存したリソースをクリーンアップできるようにしてください。スイーパーはアクション固有のものではありません。スイーパー関数のパターン、登録、TestMain、依存関係の順序付けについては、利用可能であれば provider-test-patterns スキルを使用してください。
terraform_data をノーオペレーションのトリガーとして使用する
実際のインフラストラクチャを必要としないアクションのテストでは、terraform_data をノーオペレーションのトリガーリソースとして使用できます。これは、エラーケースや検証のテストに役立ちます。
resource "terraform_data" "trigger" {
lifecycle {
action_trigger {
events = [after_create]
actions = [action.provider_service_action.test]
}
}
}
action "provider_service_action" "test" {
config {
param = "invalid-value"
}
}
PostApplyFuncを使用した副作用の検証
アクションは、resource.TestCheckResourceAttrで確認できる状態を生成しません。resource.TestStepのPostApplyFuncを使用して、適用後にAPIを照会し、アクションによって想定した副作用が発生したことを確認します。
Steps: []resource.TestStep{
{
Config: testConfig,
PostApplyFunc: func() {
// query the API to verify the action's side effect occurred
},
},
},
テストのベストプラクティス
サービス固有の前提条件
- アクションが正常に実行されるために満たす必要がある、サービス固有の前提条件を必ず確認します
- アクションのドキュメントとテスト設定に前提条件を記載します
エラーパターンの照合
- Terraformはアクションのエラーに追加のコンテキストを付加します
- 柔軟な正規表現パターンを使用します:
regexp.MustCompile(\(?s)Error Title.*key phrase`)`
アクションには適用できないテストパターン
- アクションはライフサイクルイベントで実行され、設定の再適用では実行されません
- Destroy前後のテスト:Terraform 1.14時点ではサポートされていません
テストの実行
まずコンパイルを確認してから、対象を絞った受け入れテストを実行します:
go test -c -o /dev/null ./internal/provider
TF_ACC=1 go test ./internal/provider -run TestAccExampleAction_ -timeout 60m
環境変数の設定、失敗したテストのデバッグ、スイーパーの実行には、利用可能な場合はrun-acceptance-testsスキルを使用します。
ドキュメントの基準
プロバイダーでtfplugindocsを使用している場合は、これを使ってアクションのドキュメントを生成します(このワークフローでは、利用可能な場合はprovider-docsスキルを使用します)。各アクションのドキュメントページには、次の内容を含める必要があります。
-
フロントマター(手書きのレガシーレイアウトのみ)
--- subcategory: "Service Name" layout: "provider" page_title: "Provider: provider_service_action" description: |- Brief description of what the action does. --- -
警告を含むヘッダー
- ベータ版/アルファ版であることを示す、実験的なステータスに関する告知
- 意図しない結果が生じる可能性についての警告
- プロバイダーのドキュメントへのリンク
-
使用例
- 基本的な使用例
- すべてのオプションを含む高度な使用例
terraform_dataを使用したトリガーベースの使用例- 実際のユースケースの使用例
-
引数リファレンス
- 必須およびオプションの引数をすべて列挙します
- 説明とデフォルト値を記載します
- 検証ルールを明記します
-
ドキュメントのリント(任意のツール)
- リポジトリで
terrafmtを使用している場合は、提出前にterrafmt fmtを実行し、terrafmt diffで確認します
- リポジトリで
変更履歴エントリーの形式(プロバイダー固有の規約)
一部のプロバイダー(例:terraform-provider-aws)では、go-changelogを使い、.changelog/ディレクトリにPRごとに1つのファイルを作成してリリースノートを管理します。対象リポジトリのCONTRIBUTINGガイドを確認してください。リポジトリで使用していない場合は、この手順を省略します。
.changelog/<pr_number>.txt
コンテンツの形式:
action/provider_service_action: Brief description of the action
提出前のチェックリスト
アクションの実装を提出する前に、次の項目を確認します。
- コードがコンパイルできる:
go build -o /dev/null . - テストがコンパイルできる:
go test -c -o /dev/null ./internal/provider - コードがフォーマットされている:
gofmt(またはリポジトリのmake fmt) - リポジトリの規約に従ってドキュメントが生成またはフォーマットされている
- 変更履歴エントリーが作成されている(リポジトリで使用している場合)
- スキーマで正しい型が使われている
- すべてのList/Map属性にElementTypeが設定されている
- 長時間かかる操作に進捗更新が実装されている
- エラーメッセージにコンテキストとリソース識別子が含まれている
- ドキュメントに複数の使用例が含まれている
- ドキュメントに前提条件と警告が含まれている
参考資料
- Terraform Plugin Frameworkのドキュメント
- Terraformプロバイダーの開発
- terraform-plugin-framework GitHub
- terraform-plugin-testing
- Terraformアクションの作成(ブログ)
- 参考実装:
terraform-provider-tfe(action_query_run.go、action_query_run_test.go)、terraform-provider-vault(action_rotate_root.go)