provider-resources
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTerraform Provider Resources Implementation Guide
Terraform Provider资源实现指南
Overview
概述
This guide covers developing Terraform Provider resources and data sources using the Terraform Plugin Framework. Resources represent infrastructure objects that Terraform manages through Create, Read, Update, and Delete (CRUD) operations.
References:
本指南介绍如何使用Terraform Plugin Framework开发Terraform Provider资源和数据源。资源代表Terraform通过创建、读取、更新和删除(CRUD)操作管理的基础设施对象。
参考资料:
File Structure
文件结构
Resources follow the standard service package structure:
internal/service/<service>/
├── <resource_name>.go # Resource implementation
├── <resource_name>_test.go # Acceptance tests
├── <resource_name>_data_source.go # Data source (if applicable)
├── find.go # Finder functions
├── exports_test.go # Test exports
└── service_package_gen.go # Auto-generated registrationDocumentation structure:
website/docs/r/
└── <service>_<resource_name>.html.markdown # Resource documentation
website/docs/d/
└── <service>_<resource_name>.html.markdown # Data source documentation资源遵循标准的服务包结构:
internal/service/<service>/
├── <resource_name>.go # 资源实现代码
├── <resource_name>_test.go # 验收测试
├── <resource_name>_data_source.go # 数据源(如适用)
├── find.go # 查找器函数
├── exports_test.go # 测试导出
└── service_package_gen.go # 自动生成的注册代码文档结构:
website/docs/r/
└── <service>_<resource_name>.html.markdown # 资源文档
website/docs/d/
└── <service>_<resource_name>.html.markdown # 数据源文档Resource Structure
资源结构
SDKv2 Resource Pattern
SDKv2资源模式
go
func ResourceExample() *schema.Resource {
return &schema.Resource{
CreateWithoutTimeout: resourceExampleCreate,
ReadWithoutTimeout: resourceExampleRead,
UpdateWithoutTimeout: resourceExampleUpdate,
DeleteWithoutTimeout: resourceExampleDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringLenBetween(1, 255),
},
"arn": {
Type: schema.TypeString,
Computed: true,
},
"tags": tftags.TagsSchema(),
"tags_all": tftags.TagsSchemaComputed(),
},
CustomizeDiff: verify.SetTagsDiff,
}
}go
func ResourceExample() *schema.Resource {
return &schema.Resource{
CreateWithoutTimeout: resourceExampleCreate,
ReadWithoutTimeout: resourceExampleRead,
UpdateWithoutTimeout: resourceExampleUpdate,
DeleteWithoutTimeout: resourceExampleDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringLenBetween(1, 255),
},
"arn": {
Type: schema.TypeString,
Computed: true,
},
"tags": tftags.TagsSchema(),
"tags_all": tftags.TagsSchemaComputed(),
},
CustomizeDiff: verify.SetTagsDiff,
}
}Plugin Framework Resource Pattern
Plugin Framework资源模式
go
type resourceExample struct {
framework.ResourceWithConfigure
}
func (r *resourceExample) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_example"
}
func (r *resourceExample) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"id": framework.IDAttribute(),
"name": schema.StringAttribute{
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
stringvalidator.LengthBetween(1, 255),
},
},
"arn": schema.StringAttribute{
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
},
}
}go
type resourceExample struct {
framework.ResourceWithConfigure
}
func (r *resourceExample) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_example"
}
func (r *resourceExample) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"id": framework.IDAttribute(),
"name": schema.StringAttribute{
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
stringvalidator.LengthBetween(1, 255),
},
},
"arn": schema.StringAttribute{
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
},
}
}CRUD Operations
CRUD操作
Create Operation
创建操作
go
func (r *resourceExample) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data resourceExampleModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
conn := r.Meta().ExampleClient(ctx)
input := &example.CreateExampleInput{
Name: data.Name.ValueStringPointer(),
}
output, err := conn.CreateExample(ctx, input)
if err != nil {
resp.Diagnostics.AddError(
"Error creating Example",
fmt.Sprintf("Could not create example %s: %s", data.Name.ValueString(), err),
)
return
}
data.ID = types.StringPointerValue(output.Id)
data.ARN = types.StringPointerValue(output.Arn)
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}go
func (r *resourceExample) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data resourceExampleModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
conn := r.Meta().ExampleClient(ctx)
input := &example.CreateExampleInput{
Name: data.Name.ValueStringPointer(),
}
output, err := conn.CreateExample(ctx, input)
if err != nil {
resp.Diagnostics.AddError(
"创建Example资源出错",
fmt.Sprintf("无法创建Example %s: %s", data.Name.ValueString(), err),
)
return
}
data.ID = types.StringPointerValue(output.Id)
data.ARN = types.StringPointerValue(output.Arn)
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}Read Operation
读取操作
go
func (r *resourceExample) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var data resourceExampleModel
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
conn := r.Meta().ExampleClient(ctx)
output, err := findExampleByID(ctx, conn, data.ID.ValueString())
if tfresource.NotFound(err) {
resp.Diagnostics.AddWarning(
"Resource not found",
fmt.Sprintf("Example %s not found, removing from state", data.ID.ValueString()),
)
resp.State.RemoveResource(ctx)
return
}
if err != nil {
resp.Diagnostics.AddError(
"Error reading Example",
fmt.Sprintf("Could not read example %s: %s", data.ID.ValueString(), err),
)
return
}
data.Name = types.StringPointerValue(output.Name)
data.ARN = types.StringPointerValue(output.Arn)
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}go
func (r *resourceExample) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var data resourceExampleModel
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
conn := r.Meta().ExampleClient(ctx)
output, err := findExampleByID(ctx, conn, data.ID.ValueString())
if tfresource.NotFound(err) {
resp.Diagnostics.AddWarning(
"资源未找到",
fmt.Sprintf("Example %s未找到,将从状态中移除", data.ID.ValueString()),
)
resp.State.RemoveResource(ctx)
return
}
if err != nil {
resp.Diagnostics.AddError(
"读取Example资源出错",
fmt.Sprintf("无法读取Example %s: %s", data.ID.ValueString(), err),
)
return
}
data.Name = types.StringPointerValue(output.Name)
data.ARN = types.StringPointerValue(output.Arn)
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}Update Operation
更新操作
go
func (r *resourceExample) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan, state resourceExampleModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
conn := r.Meta().ExampleClient(ctx)
if !plan.Description.Equal(state.Description) {
input := &example.UpdateExampleInput{
Id: plan.ID.ValueStringPointer(),
Description: plan.Description.ValueStringPointer(),
}
_, err := conn.UpdateExample(ctx, input)
if err != nil {
resp.Diagnostics.AddError(
"Error updating Example",
fmt.Sprintf("Could not update example %s: %s", plan.ID.ValueString(), err),
)
return
}
}
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
}go
func (r *resourceExample) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan, state resourceExampleModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
conn := r.Meta().ExampleClient(ctx)
if !plan.Description.Equal(state.Description) {
input := &example.UpdateExampleInput{
Id: plan.ID.ValueStringPointer(),
Description: plan.Description.ValueStringPointer(),
}
_, err := conn.UpdateExample(ctx, input)
if err != nil {
resp.Diagnostics.AddError(
"更新Example资源出错",
fmt.Sprintf("无法更新Example %s: %s", plan.ID.ValueString(), err),
)
return
}
}
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
}Delete Operation
删除操作
go
func (r *resourceExample) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var data resourceExampleModel
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
conn := r.Meta().ExampleClient(ctx)
_, err := conn.DeleteExample(ctx, &example.DeleteExampleInput{
Id: data.ID.ValueStringPointer(),
})
if tfresource.NotFound(err) {
return
}
if err != nil {
resp.Diagnostics.AddError(
"Error deleting Example",
fmt.Sprintf("Could not delete example %s: %s", data.ID.ValueString(), err),
)
return
}
}go
func (r *resourceExample) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var data resourceExampleModel
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
conn := r.Meta().ExampleClient(ctx)
_, err := conn.DeleteExample(ctx, &example.DeleteExampleInput{
Id: data.ID.ValueStringPointer(),
})
if tfresource.NotFound(err) {
return
}
if err != nil {
resp.Diagnostics.AddError(
"删除Example资源出错",
fmt.Sprintf("无法删除Example %s: %s", data.ID.ValueString(), err),
)
return
}
}Schema Design
Schema设计
Attribute Types
属性类型
| Terraform Type | Framework Type | Use Case |
|---|---|---|
| | Names, ARNs, IDs |
| | Counts, sizes |
| | Feature flags |
| | Ordered collections |
| | Unordered unique items |
| | Key-value pairs |
| | Complex nested config |
| Terraform类型 | Framework类型 | 使用场景 |
|---|---|---|
| | 名称、ARN、ID |
| | 计数、大小 |
| | 功能开关 |
| | 有序集合 |
| | 无序唯一项 |
| | 键值对 |
| | 复杂嵌套配置 |
Plan Modifiers
计划修饰器
go
// Force replacement when value changes
stringplanmodifier.RequiresReplace()
// Preserve unknown value during plan
stringplanmodifier.UseStateForUnknown()
// Custom plan modifier
stringplanmodifier.RequiresReplaceIf(
func(ctx context.Context, req planmodifier.StringRequest, resp *stringplanmodifier.RequiresReplaceIfFuncResponse) {
// Custom logic
},
"description",
"markdown description",
)go
// 值变更时强制替换资源
stringplanmodifier.RequiresReplace()
// 计划阶段保留未知值
stringplanmodifier.UseStateForUnknown()
// 自定义计划修饰器
stringplanmodifier.RequiresReplaceIf(
func(ctx context.Context, req planmodifier.StringRequest, resp *stringplanmodifier.RequiresReplaceIfFuncResponse) {
// 自定义逻辑
},
"description",
"markdown描述",
)Validators
验证器
go
// String validators
stringvalidator.LengthBetween(1, 255)
stringvalidator.RegexMatches(regexp.MustCompile(`^[a-z0-9-]+$`), "must be lowercase alphanumeric with hyphens")
stringvalidator.OneOf("option1", "option2", "option3")
// Int64 validators
int64validator.Between(1, 100)
int64validator.AtLeast(1)
int64validator.AtMost(1000)
// List validators
listvalidator.SizeAtLeast(1)
listvalidator.SizeAtMost(10)go
// 字符串验证器
stringvalidator.LengthBetween(1, 255)
stringvalidator.RegexMatches(regexp.MustCompile(`^[a-z0-9-]+$`), "必须是小写字母数字加连字符")
stringvalidator.OneOf("option1", "option2", "option3")
// Int64验证器
int64validator.Between(1, 100)
int64validator.AtLeast(1)
int64validator.AtMost(1000)
// 列表验证器
listvalidator.SizeAtLeast(1)
listvalidator.SizeAtMost(10)Sensitive Attributes
敏感属性
go
"password": schema.StringAttribute{
Required: true,
Sensitive: true,
Validators: []validator.String{
stringvalidator.LengthAtLeast(8),
},
}go
"password": schema.StringAttribute{
Required: true,
Sensitive: true,
Validators: []validator.String{
stringvalidator.LengthAtLeast(8),
},
}State Management
状态管理
Handling Resource Not Found
处理资源未找到的情况
go
func findExampleByID(ctx context.Context, conn *example.Client, id string) (*example.Example, error) {
input := &example.GetExampleInput{
Id: &id,
}
output, err := conn.GetExample(ctx, input)
if err != nil {
var notFound *types.ResourceNotFoundException
if errors.As(err, ¬Found) {
return nil, &retry.NotFoundError{
LastError: err,
LastRequest: input,
}
}
return nil, err
}
if output == nil || output.Example == nil {
return nil, tfresource.NewEmptyResultError(input)
}
return output.Example, nil
}go
func findExampleByID(ctx context.Context, conn *example.Client, id string) (*example.Example, error) {
input := &example.GetExampleInput{
Id: &id,
}
output, err := conn.GetExample(ctx, input)
if err != nil {
var notFound *types.ResourceNotFoundException
if errors.As(err, ¬Found) {
return nil, &retry.NotFoundError{
LastError: err,
LastRequest: input,
}
}
return nil, err
}
if output == nil || output.Example == nil {
return nil, tfresource.NewEmptyResultError(input)
}
return output.Example, nil
}Waiting for Resource States
等待资源状态
go
func waitExampleCreated(ctx context.Context, conn *example.Client, id string, timeout time.Duration) (*example.Example, error) {
stateConf := &retry.StateChangeConf{
Pending: []string{"CREATING", "PENDING"},
Target: []string{"ACTIVE", "AVAILABLE"},
Refresh: statusExample(ctx, conn, id),
Timeout: timeout,
}
outputRaw, err := stateConf.WaitForStateContext(ctx)
if output, ok := outputRaw.(*example.Example); ok {
return output, err
}
return nil, err
}
func statusExample(ctx context.Context, conn *example.Client, id string) retry.StateRefreshFunc {
return func() (interface{}, string, error) {
output, err := findExampleByID(ctx, conn, id)
if tfresource.NotFound(err) {
return nil, "", nil
}
if err != nil {
return nil, "", err
}
return output, string(output.Status), nil
}
}go
func waitExampleCreated(ctx context.Context, conn *example.Client, id string, timeout time.Duration) (*example.Example, error) {
stateConf := &retry.StateChangeConf{
Pending: []string{"CREATING", "PENDING"},
Target: []string{"ACTIVE", "AVAILABLE"},
Refresh: statusExample(ctx, conn, id),
Timeout: timeout,
}
outputRaw, err := stateConf.WaitForStateContext(ctx)
if output, ok := outputRaw.(*example.Example); ok {
return output, err
}
return nil, err
}
func statusExample(ctx context.Context, conn *example.Client, id string) retry.StateRefreshFunc {
return func() (interface{}, string, error) {
output, err := findExampleByID(ctx, conn, id)
if tfresource.NotFound(err) {
return nil, "", nil
}
if err != nil {
return nil, "", err
}
return output, string(output.Status), nil
}
}Testing
测试
Basic Acceptance Test
基础验收测试
go
func TestAccExampleResource_basic(t *testing.T) {
ctx := acctest.Context(t)
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "provider_example.test"
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckExampleDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccExampleConfig_basic(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckExampleExists(ctx, resourceName),
resource.TestCheckResourceAttr(resourceName, "name", rName),
resource.TestCheckResourceAttrSet(resourceName, "arn"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}go
func TestAccExampleResource_basic(t *testing.T) {
ctx := acctest.Context(t)
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "provider_example.test"
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckExampleDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccExampleConfig_basic(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckExampleExists(ctx, resourceName),
resource.TestCheckResourceAttr(resourceName, "name", rName),
resource.TestCheckResourceAttrSet(resourceName, "arn"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}Disappears Test
资源消失测试
go
func TestAccExampleResource_disappears(t *testing.T) {
ctx := acctest.Context(t)
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "provider_example.test"
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckExampleDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccExampleConfig_basic(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckExampleExists(ctx, resourceName),
acctest.CheckResourceDisappears(ctx, acctest.Provider, ResourceExample(), resourceName),
),
ExpectNonEmptyPlan: true,
},
},
})
}go
func TestAccExampleResource_disappears(t *testing.T) {
ctx := acctest.Context(t)
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "provider_example.test"
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckExampleDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccExampleConfig_basic(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckExampleExists(ctx, resourceName),
acctest.CheckResourceDisappears(ctx, acctest.Provider, ResourceExample(), resourceName),
),
ExpectNonEmptyPlan: true,
},
},
})
}Test Helper Functions
测试辅助函数
go
func testAccCheckExampleExists(ctx context.Context, name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Not found: %s", name)
}
conn := acctest.Provider.Meta().(*conns.Client).ExampleClient(ctx)
_, err := findExampleByID(ctx, conn, rs.Primary.ID)
return err
}
}
func testAccCheckExampleDestroy(ctx context.Context) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := acctest.Provider.Meta().(*conns.Client).ExampleClient(ctx)
for _, rs := range s.RootModule().Resources {
if rs.Type != "provider_example" {
continue
}
_, err := findExampleByID(ctx, conn, rs.Primary.ID)
if tfresource.NotFound(err) {
continue
}
if err != nil {
return err
}
return fmt.Errorf("Example %s still exists", rs.Primary.ID)
}
return nil
}
}go
func testAccCheckExampleExists(ctx context.Context, name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("未找到: %s", name)
}
conn := acctest.Provider.Meta().(*conns.Client).ExampleClient(ctx)
_, err := findExampleByID(ctx, conn, rs.Primary.ID)
return err
}
}
func testAccCheckExampleDestroy(ctx context.Context) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := acctest.Provider.Meta().(*conns.Client).ExampleClient(ctx)
for _, rs := range s.RootModule().Resources {
if rs.Type != "provider_example" {
continue
}
_, err := findExampleByID(ctx, conn, rs.Primary.ID)
if tfresource.NotFound(err) {
continue
}
if err != nil {
return err
}
return fmt.Errorf("Example %s仍然存在", rs.Primary.ID)
}
return nil
}
}Running Tests
运行测试
bash
undefinedbash
undefinedCompile tests
编译测试
go test -c -o /dev/null ./internal/service/<service>
go test -c -o /dev/null ./internal/service/<service>
Run acceptance tests
运行验收测试
TF_ACC=1 go test ./internal/service/<service> -run TestAccExample -v -timeout 60m
TF_ACC=1 go test ./internal/service/<service> -run TestAccExample -v -timeout 60m
Run with specific provider version
使用特定Provider版本运行
TF_ACC=1 go test ./internal/service/<service> -run TestAccExample -v
TF_ACC=1 go test ./internal/service/<service> -run TestAccExample -v
Run sweeper to clean up
运行清理工具
TF_ACC=1 go test ./internal/service/<service> -sweep=<region> -v
undefinedTF_ACC=1 go test ./internal/service/<service> -sweep=<region> -v
undefinedError Handling
错误处理
Common Error Patterns
常见错误模式
go
// Handle specific API errors
var notFound *types.ResourceNotFoundException
if errors.As(err, ¬Found) {
// Resource doesn't exist
}
var conflict *types.ConflictException
if errors.As(err, &conflict) {
// Resource state conflict
}
var throttle *types.ThrottlingException
if errors.As(err, &throttle) {
// Rate limited - SDK handles retry
}go
// 处理特定API错误
var notFound *types.ResourceNotFoundException
if errors.As(err, ¬Found) {
// 资源不存在
}
var conflict *types.ConflictException
if errors.As(err, &conflict) {
// 资源状态冲突
}
var throttle *types.ThrottlingException
if errors.As(err, &throttle) {
// 速率限制 - SDK会处理重试
}Diagnostics
诊断信息
go
// Add error
resp.Diagnostics.AddError(
"Error creating resource",
fmt.Sprintf("Could not create resource: %s", err),
)
// Add warning
resp.Diagnostics.AddWarning(
"Resource modified outside Terraform",
"Resource was modified outside of Terraform, state may be inconsistent",
)
// Add attribute error
resp.Diagnostics.AddAttributeError(
path.Root("name"),
"Invalid name",
"Name must be lowercase alphanumeric",
)go
// 添加错误信息
resp.Diagnostics.AddError(
"创建资源出错",
fmt.Sprintf("无法创建资源: %s", err),
)
// 添加警告信息
resp.Diagnostics.AddWarning(
"资源在Terraform外部被修改",
"资源在Terraform外部被修改,状态可能不一致",
)
// 添加属性错误
resp.Diagnostics.AddAttributeError(
path.Root("name"),
"无效名称",
"名称必须是小写字母数字",
)Documentation Standards
文档标准
Resource Documentation
资源文档
markdown
---
subcategory: "Service Name"
layout: "provider"
page_title: "Provider: provider_example"
description: |-
Manages an Example resource.
---markdown
---
subcategory: "服务名称"
layout: "provider"
page_title: "Provider: provider_example"
description: |-
管理Example资源。
---Resource: provider_example
资源: provider_example
Manages an Example resource.
管理Example资源。
Example Usage
示例用法
Basic Usage
基础用法
```hcl
resource "provider_example" "example" {
name = "my-example"
}
```
```hcl
resource "provider_example" "example" {
name = "my-example"
}
```
Argument Reference
参数参考
- - (Required) Name of the example.
name - - (Optional) Description of the example.
description
- - (必填) Example的名称。
name - - (可选) Example的描述。
description
Attribute Reference
属性参考
- - ID of the example.
id - - ARN of the example.
arn
- - Example的ID。
id - - Example的ARN。
arn
Import
导入
Example can be imported using the ID:
```
$ terraform import provider_example.example example-id-12345
```
undefined可以使用ID导入Example资源:
```
$ terraform import provider_example.example example-id-12345
```
undefinedPre-Submission Checklist
提交前检查清单
- Code compiles without errors
- All tests pass locally
- Resource has all CRUD operations implemented
- Import is implemented and tested
- Disappears test is included
- Documentation is complete with examples
- Error messages are clear and actionable
- Sensitive attributes are marked
- Plan modifiers are appropriate
- Validators cover edge cases
- 代码编译无错误
- 所有测试本地通过
- 资源已实现全部CRUD操作
- 导入功能已实现并测试
- 包含资源消失测试
- 文档完整且带有示例
- 错误消息清晰且可操作
- 敏感属性已标记
- 计划修饰器使用恰当
- 验证器覆盖边缘场景