provider-actions

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Terraform Provider Actions Implementation Guide

Terraform Provider Actions 实现指南

Overview

概述

Terraform Actions enable imperative operations during the Terraform lifecycle. Actions are experimental features that allow performing provider operations at specific lifecycle events (before/after create, update, destroy).
References:
Terraform Actions 允许在Terraform生命周期中执行命令式操作。Actions是实验性功能,支持在特定生命周期事件(创建前/后、更新、销毁)时执行Provider操作。
参考链接:

File Structure

文件结构

Actions follow the standard service package structure:
internal/service/<service>/
├── <action_name>_action.go       # Action implementation
├── <action_name>_action_test.go  # Action tests
└── service_package_gen.go        # Auto-generated service registration
Documentation structure:
website/docs/actions/
└── <service>_<action_name>.html.markdown  # User-facing documentation
Changelog entry:
.changelog/
└── <pr_number_or_description>.txt  # Release note entry
Actions遵循标准的服务包结构:
internal/service/<service>/
├── <action_name>_action.go       # Action implementation
├── <action_name>_action_test.go  # Action tests
└── service_package_gen.go        # Auto-generated service registration
文档结构:
website/docs/actions/
└── <service>_<action_name>.html.markdown  # User-facing documentation
变更日志条目:
.changelog/
└── <pr_number_or_description>.txt  # Release note entry

Action Schema Definition

Action Schema 定义

Actions use the Terraform Plugin Framework with a standard schema pattern:
go
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,
            },
        },
    }
}
Actions使用Terraform Plugin Framework的标准Schema模式:
go
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,
            },
        },
    }
}

Common Schema Issues

常见Schema问题

Pay special attention to the schema definition - common issues after a first draft:
  1. Type Mismatches
    • Using
      types.String
      instead of
      fwtypes.String
      in model structs
    • Using
      types.StringType
      instead of
      fwtypes.StringType
      in schema
    • Mixing framework types with plugin-framework types
  2. List/Map Element Types
    go
    // WRONG - missing ElementType
    "items": schema.ListAttribute{
        Optional: true,
    }
    
    // CORRECT
    "items": schema.ListAttribute{
        Optional:    true,
        ElementType: fwtypes.StringType,
    }
  3. Computed vs Optional
    • Attributes with defaults must be both
      Optional: true
      and
      Computed: true
    • Don't mark action inputs as
      Computed
      unless they have defaults
  4. Validator Imports
    go
    // Ensure proper imports
    "github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
    "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
  5. Region/Provider Attribute
    • Use framework-provided region handling when available
    • Don't manually define provider-specific config in schema if framework handles it
  6. Nested Attributes
    • Use appropriate nested object types for complex structures
    • Ensure nested types are properly defined
请特别注意Schema定义 - 初稿后常见的问题:
  1. 类型不匹配
    • 在模型结构体中使用
      types.String
      而非
      fwtypes.String
    • 在Schema中使用
      types.StringType
      而非
      fwtypes.StringType
    • 混合使用框架类型与plugin-framework类型
  2. 列表/映射元素类型
    go
    // WRONG - missing ElementType
    "items": schema.ListAttribute{
        Optional: true,
    }
    
    // CORRECT
    "items": schema.ListAttribute{
        Optional:    true,
        ElementType: fwtypes.StringType,
    }
  3. Computed与Optional设置
    • 带有默认值的属性必须同时设置
      Optional: true
      Computed: true
    • 除非有默认值,否则不要将Action输入标记为
      Computed
  4. 验证器导入
    go
    // Ensure proper imports
    "github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
    "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
  5. 区域/Provider属性
    • 尽可能使用框架提供的区域处理能力
    • 如果框架已处理,不要在Schema中手动定义Provider特定配置
  6. 嵌套属性
    • 对复杂结构使用合适的嵌套对象类型
    • 确保嵌套类型定义正确

Schema Validation Checklist

Schema 验证检查清单

Before submitting, verify:
  • All attributes have descriptions
  • List/Map attributes have ElementType defined
  • Validators are imported and applied correctly
  • Model struct uses correct framework types
  • Optional attributes with defaults are marked Computed
  • Code compiles without type errors
  • Run
    go build
    to catch type mismatches
提交前,请验证:
  • 所有属性都有描述
  • 列表/映射属性已定义ElementType
  • 验证器已正确导入并应用
  • 模型结构体使用正确的框架类型
  • 带有默认值的可选属性已标记为Computed
  • 代码编译无类型错误
  • 运行
    go build
    捕获类型不匹配问题

Action Invoke Method

Action Invoke 方法

The Invoke method contains the action logic:
go
func (a *actionType) Invoke(ctx context.Context, req action.InvokeRequest, resp *action.InvokeResponse) {
    var data actionModel
    resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)

    // Create provider client
    conn := a.Meta().Client(ctx)

    // Progress updates for long-running operations
    resp.Progress.Set(ctx, "Starting operation...")

    // Implement action logic with error handling
    // Use context for timeout management
    // Poll for completion if async operation

    resp.Progress.Set(ctx, "Operation completed")
}
Invoke方法包含Action的核心逻辑:
go
func (a *actionType) Invoke(ctx context.Context, req action.InvokeRequest, resp *action.InvokeResponse) {
    var data actionModel
    resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)

    // Create provider client
    conn := a.Meta().Client(ctx)

    // Progress updates for long-running operations
    resp.Progress.Set(ctx, "Starting operation...")

    // Implement action logic with error handling
    // Use context for timeout management
    // Poll for completion if async operation

    resp.Progress.Set(ctx, "Operation completed")
}

Key Implementation Requirements

核心实现要求

1. Progress Reporting

1. 进度报告

  • Use
    resp.SendProgress(action.InvokeProgressEvent{...})
    for real-time updates
  • Provide meaningful progress messages during long operations
  • Update progress at key milestones
  • Include elapsed time for long operations
  • 使用
    resp.SendProgress(action.InvokeProgressEvent{...})
    实现实时更新
  • 在长时操作中提供有意义的进度消息
  • 在关键里程碑处更新进度
  • 对长时操作显示已用时间

2. Timeout Management

2. 超时管理

  • Always include configurable timeout parameter (default: 1800s)
  • Use
    context.WithTimeout()
    for API calls
  • Handle timeout errors gracefully
  • Validate timeout ranges (typically 60-7200 seconds)
  • 始终包含可配置的超时参数(默认:1800秒)
  • 对API调用使用
    context.WithTimeout()
  • 优雅处理超时错误
  • 验证超时范围(通常为60-7200秒)

3. Error Handling

3. 错误处理

  • Add diagnostics with
    resp.Diagnostics.AddError()
  • Provide clear error messages with context
  • Include API error details when relevant
  • Map provider error types to user-friendly messages
  • Document all possible error cases
Example error handling:
go
// Handle specific errors
var notFound *types.ResourceNotFoundException
if errors.As(err, &notFound) {
    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),
)
  • 使用
    resp.Diagnostics.AddError()
    添加诊断信息
  • 提供带有上下文的清晰错误消息
  • 相关时包含API错误详情
  • 将Provider错误类型映射为用户友好的消息
  • 记录所有可能的错误情况
错误处理示例:
go
// Handle specific errors
var notFound *types.ResourceNotFoundException
if errors.As(err, &notFound) {
    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. Provider SDK Integration

4. Provider SDK 集成

  • Use provider SDK clients from
    a.Meta().<Service>Client(ctx)
  • Handle pagination for list operations
  • Implement retry logic for transient failures
  • Use appropriate error types
  • 使用
    a.Meta().<Service>Client(ctx)
    获取Provider SDK客户端
  • 处理列表操作的分页
  • 为临时故障实现重试逻辑
  • 使用合适的错误类型

5. Parameter Validation

5. 参数验证

  • Use framework validators for input validation
  • Validate resource existence before operations
  • Check for conflicting parameters
  • Validate against provider naming requirements
  • 使用框架验证器进行输入验证
  • 操作前验证资源是否存在
  • 检查冲突参数
  • 验证是否符合Provider命名要求

6. Polling and Waiting

6. 轮询与等待

For operations that require waiting for completion:
go
result, err := wait.WaitForStatus(ctx,
    func(ctx context.Context) (wait.FetchResult[*ResourceType], error) {
        // Fetch current status
        resource, err := findResource(ctx, conn, id)
        if err != nil {
            return wait.FetchResult[*ResourceType]{}, err
        }
        return wait.FetchResult[*ResourceType]{
            Status: wait.Status(resource.Status),
            Value:  resource,
        }, nil
    },
    wait.Options[*ResourceType]{
        Timeout:            timeout,
        Interval:           wait.FixedInterval(5 * time.Second),
        SuccessStates:      []wait.Status{"AVAILABLE", "COMPLETED"},
        TransitionalStates: []wait.Status{"CREATING", "PENDING"},
        ProgressInterval:   30 * time.Second,
        ProgressSink: func(fr wait.FetchResult[any], meta wait.ProgressMeta) {
            resp.SendProgress(action.InvokeProgressEvent{
                Message: fmt.Sprintf("Status: %s, Elapsed: %v", fr.Status, meta.Elapsed.Round(time.Second)),
            })
        },
    },
)
对于需要等待完成的操作:
go
result, err := wait.WaitForStatus(ctx,
    func(ctx context.Context) (wait.FetchResult[*ResourceType], error) {
        // Fetch current status
        resource, err := findResource(ctx, conn, id)
        if err != nil {
            return wait.FetchResult[*ResourceType]{}, err
        }
        return wait.FetchResult[*ResourceType]{
            Status: wait.Status(resource.Status),
            Value:  resource,
        }, nil
    },
    wait.Options[*ResourceType]{
        Timeout:            timeout,
        Interval:           wait.FixedInterval(5 * time.Second),
        SuccessStates:      []wait.Status{"AVAILABLE", "COMPLETED"},
        TransitionalStates: []wait.Status{"CREATING", "PENDING"},
        ProgressInterval:   30 * time.Second,
        ProgressSink: func(fr wait.FetchResult[any], meta wait.ProgressMeta) {
            resp.SendProgress(action.InvokeProgressEvent{
                Message: fmt.Sprintf("Status: %s, Elapsed: %v", fr.Status, meta.Elapsed.Round(time.Second)),
            })
        },
    },
)

Common Action Patterns

常见Action模式

Batch Operations

批量操作

  • Process items in configurable batches
  • Report progress per batch
  • Handle partial failures gracefully
  • Support prefix/filter parameters
  • 按可配置的批次处理项目
  • 按批次报告进度
  • 优雅处理部分失败
  • 支持前缀/过滤参数

Command Execution

命令执行

  • Submit command and get operation ID
  • Poll for completion status
  • Retrieve and report output
  • Handle timeout during polling
  • Validate resources exist before execution
  • 提交命令并获取操作ID
  • 轮询完成状态
  • 检索并报告输出
  • 处理轮询期间的超时
  • 执行前验证资源是否存在

Service Invocation

服务调用

  • Invoke service with parameters
  • Wait for completion (if synchronous)
  • Return output/results
  • Handle service-specific errors
  • 使用参数调用服务
  • 等待完成(如果是同步操作)
  • 返回输出/结果
  • 处理服务特定错误

Resource State Changes

资源状态变更

  • Validate current state
  • Apply state change
  • Poll for target state
  • Handle transitional states
  • 验证当前状态
  • 应用状态变更
  • 轮询目标状态
  • 处理过渡状态

Async Job Submission

异步任务提交

  • Submit job with configuration
  • Get job ID
  • Optionally wait for completion
  • Report job status
  • 使用配置提交任务
  • 获取任务ID
  • 可选等待完成
  • 报告任务状态

Action Triggers

Action 触发器

Actions are invoked via
action_trigger
lifecycle blocks in Terraform configurations:
hcl
action "provider_service_action" "name" {
  config {
    parameter = value
  }
}

resource "terraform_data" "trigger" {
  lifecycle {
    action_trigger {
      events  = [after_create]
      actions = [action.provider_service_action.name]
    }
  }
}
Actions通过Terraform配置中的
action_trigger
生命周期块调用:
hcl
action "provider_service_action" "name" {
  config {
    parameter = value
  }
}

resource "terraform_data" "trigger" {
  lifecycle {
    action_trigger {
      events  = [after_create]
      actions = [action.provider_service_action.name]
    }
  }
}

Available Trigger Events

可用触发事件

Terraform 1.14.0 Supported Events:
  • before_create
    - Before resource creation
  • after_create
    - After resource creation
  • before_update
    - Before resource update
  • after_update
    - After resource update
Not Supported in Terraform 1.14.0:
  • before_destroy
    - Not available (will cause validation error)
  • after_destroy
    - Not available (will cause validation error)
Terraform 1.14.0 支持的事件:
  • before_create
    - 资源创建前
  • after_create
    - 资源创建后
  • before_update
    - 资源更新前
  • after_update
    - 资源更新后
Terraform 1.14.0 不支持的事件:
  • before_destroy
    - 不可用(会导致验证错误)
  • after_destroy
    - 不可用(会导致验证错误)

Testing Actions

测试Actions

Acceptance Tests

验收测试

  • Test action invocation with valid parameters
  • Test timeout scenarios
  • Test error conditions
  • Verify provider state changes
  • Test progress reporting
  • Test with custom parameters
  • Test trigger-based invocation
  • 使用有效参数测试Action调用
  • 测试超时场景
  • 测试错误情况
  • 验证Provider状态变化
  • 测试进度报告
  • 使用自定义参数测试
  • 测试基于触发器的调用

Test Pattern

测试模式

go
func TestAccServiceAction_basic(t *testing.T) {
    ctx := acctest.Context(t)

    resource.ParallelTest(t, resource.TestCase{
        PreCheck:                 func() { acctest.PreCheck(ctx, t) },
        ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
        TerraformVersionChecks: []tfversion.TerraformVersionCheck{
            tfversion.SkipBelow(tfversion.Version1_14_0),
        },
        Steps: []resource.TestStep{
            {
                Config: testAccActionConfig_basic(),
                Check: resource.ComposeTestCheckFunc(
                    testAccCheckResourceExists(ctx, "provider_resource.test"),
                ),
            },
        },
    })
}
go
func TestAccServiceAction_basic(t *testing.T) {
    ctx := acctest.Context(t)

    resource.ParallelTest(t, resource.TestCase{
        PreCheck:                 func() { acctest.PreCheck(ctx, t) },
        ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
        TerraformVersionChecks: []tfversion.TerraformVersionCheck{
            tfversion.SkipBelow(tfversion.Version1_14_0),
        },
        Steps: []resource.TestStep{
            {
                Config: testAccActionConfig_basic(),
                Check: resource.ComposeTestCheckFunc(
                    testAccCheckResourceExists(ctx, "provider_resource.test"),
                ),
            },
        },
    })
}

Test Cleanup with Sweep Functions

使用清理函数进行测试清理

Add sweep functions to clean up test resources:
go
func sweepResources(region string) error {
    ctx := context.Background()
    client := /* get client for region */

    input := &service.ListInput{
        // Filter for test resources
    }

    var sweeperErrs *multierror.Error

    pages := service.NewListPaginator(client, input)
    for pages.HasMorePages() {
        page, err := pages.NextPage(ctx)
        if err != nil {
            sweeperErrs = multierror.Append(sweeperErrs, err)
            continue
        }

        for _, item := range page.Items {
            id := item.Id

            // Skip non-test resources
            if !strings.HasPrefix(id, "tf-acc-test") {
                continue
            }

            _, err := client.Delete(ctx, &service.DeleteInput{
                Id: id,
            })
            if err != nil {
                sweeperErrs = multierror.Append(sweeperErrs, err)
            }
        }
    }

    return sweeperErrs.ErrorOrNil()
}
添加清理函数以清理测试资源:
go
func sweepResources(region string) error {
    ctx := context.Background()
    client := /* get client for region */

    input := &service.ListInput{
        // Filter for test resources
    }

    var sweeperErrs *multierror.Error

    pages := service.NewListPaginator(client, input)
    for pages.HasMorePages() {
        page, err := pages.NextPage(ctx)
        if err != nil {
            sweeperErrs = multierror.Append(sweeperErrs, err)
            continue
        }

        for _, item := range page.Items {
            id := item.Id

            // Skip non-test resources
            if !strings.HasPrefix(id, "tf-acc-test") {
                continue
            }

            _, err := client.Delete(ctx, &service.DeleteInput{
                Id: id,
            })
            if err != nil {
                sweeperErrs = multierror.Append(sweeperErrs, err)
            }
        }
    }

    return sweeperErrs.ErrorOrNil()
}

Testing Best Practices

测试最佳实践

Service-Specific Prerequisites
  • Always check for service-specific prerequisites that must be met before actions can succeed
  • Document prerequisites in action documentation and test configurations
Error Pattern Matching
  • Terraform wraps action errors with additional context
  • Use flexible regex patterns:
    regexache.MustCompile(\
    (?s)Error Title.*key phrase`)`
Test Patterns Not Applicable to Actions
  1. Actions trigger on lifecycle events, not config reapplication
  2. Before/After Destroy Tests: Not supported in Terraform 1.14.0
服务特定前提条件
  • 始终检查Action成功前必须满足的服务特定前提条件
  • 在Action文档和测试配置中记录前提条件
错误模式匹配
  • Terraform会为Action错误添加额外上下文
  • 使用灵活的正则表达式:
    regexache.MustCompile(\
    (?s)Error Title.*key phrase`)`
不适用于Actions的测试模式
  1. Actions触发自生命周期事件,而非配置重新应用
  2. 销毁前/后测试:Terraform 1.14.0不支持

Running Tests

运行测试

Compile test to check for errors:
bash
go test -c -o /dev/null ./internal/service/<service>
Run specific action tests:
bash
TF_ACC=1 go test ./internal/service/<service> -run TestAccServiceAction_ -v
Run sweep to clean up test resources:
bash
TF_ACC=1 go test ./internal/service/<service> -sweep=<region> -v
编译测试以检查错误:
bash
go test -c -o /dev/null ./internal/service/<service>
运行特定Action测试:
bash
TF_ACC=1 go test ./internal/service/<service> -run TestAccServiceAction_ -v
运行清理以清理测试资源:
bash
TF_ACC=1 go test ./internal/service/<service> -sweep=<region> -v

Documentation Standards

文档标准

Each action documentation file must include:
  1. Front Matter
    yaml
    ---
    subcategory: "Service Name"
    layout: "provider"
    page_title: "Provider: provider_service_action"
    description: |-
      Brief description of what the action does.
    ---
  2. Header with Warnings
    • Beta/Alpha notice about experimental status
    • Warning about potential unintended consequences
    • Link to provider documentation
  3. Example Usage
    • Basic usage example
    • Advanced usage with all options
    • Trigger-based example with
      terraform_data
    • Real-world use case examples
  4. Argument Reference
    • List all required and optional arguments
    • Include descriptions and defaults
    • Note any validation rules
  5. Documentation Linting
    • Run
      terrafmt fmt
      before submission
    • Verify with
      terrafmt diff
每个Action文档文件必须包含:
  1. 前置内容
    yaml
    ---
    subcategory: "Service Name"
    layout: "provider"
    page_title: "Provider: provider_service_action"
    description: |-
      Brief description of what the action does.
    ---
  2. 带警告的标题
    • 实验状态的Beta/Alpha通知
    • 关于潜在意外后果的警告
    • 指向Provider文档的链接
  3. 示例用法
    • 基础用法示例
    • 包含所有选项的高级用法
    • 基于
      terraform_data
      的触发器示例
    • 真实场景用例示例
  4. 参数参考
    • 列出所有必填和可选参数
    • 包含描述和默认值
    • 注明任何验证规则
  5. 文档检查
    • 提交前运行
      terrafmt fmt
    • 使用
      terrafmt diff
      验证

Changelog Entry Format

变更日志条目格式

Create a changelog entry in
.changelog/
directory:
.changelog/<pr_number_or_description>.txt
Content format:
release
action/provider_service_action: Brief description of the action
.changelog/
目录创建变更日志条目:
.changelog/<pr_number_or_description>.txt
内容格式:
release
action/provider_service_action: Brief description of the action

Pre-Submission Checklist

提交前检查清单

Before submitting your action implementation:
  • Code compiles:
    go build -o /dev/null .
  • Tests compile:
    go test -c -o /dev/null ./internal/service/<service>
  • Code formatted:
    make fmt
  • Documentation formatted:
    terrafmt fmt website/docs/actions/<action>.html.markdown
  • Changelog entry created
  • Schema uses correct types
  • All List/Map attributes have ElementType
  • Progress updates implemented for long operations
  • Error messages include context and resource identifiers
  • Documentation includes multiple examples
  • Documentation includes prerequisites and warnings
提交Action实现前:
  • 代码可编译:
    go build -o /dev/null .
  • 测试可编译:
    go test -c -o /dev/null ./internal/service/<service>
  • 代码已格式化:
    make fmt
  • 文档已格式化:
    terrafmt fmt website/docs/actions/<action>.html.markdown
  • 已创建变更日志条目
  • Schema使用正确类型
  • 所有列表/映射属性都有ElementType
  • 长时操作已实现进度更新
  • 错误消息包含上下文和资源标识符
  • 文档包含多个示例
  • 文档包含前提条件和警告

References

参考链接