关于 generate-terraform-provider
使用 Speakeasy CLI 从 OpenAPI 规范生成 Terraform Provider 的技能,适用于构建和维护 Provider 的开发者。它涵盖实体标注、CRUD 映射、生成配置、测试及发布到 Terraform Registry。
主要功能
- 01从 OpenAPI 生成 Go Provider
- 02用扩展标注实体与操作
- 03映射 API 操作为 CRUD
- 04配置生成工作流并重新生成
- 05支持测试与 Registry 发布流程
适用场景
- 01为现有 API 创建 Terraform Provider
- 02将 OpenAPI 操作映射为 Terraform 资源
- 03更新规范后重新生成 Provider
- 04准备 Provider 测试和 Registry 发布
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ speakeasy-api/ sdk-generation-skills --skill generate-terraform-provider安装后在 Claude Code 中输入/generate-terraform-provider确认已加载。
附带文件content/customization.md
详细介绍
generate-terraform-provider
使用 Speakeasy CLI 从 OpenAPI 规范生成 Terraform 提供程序。本技能涵盖完整生命周期:为规范添加实体元数据、映射 CRUD 操作、生成提供程序、配置工作流,以及发布到 Terraform Registry。
内容指南
| 主题 | 指南 |
|---|---|
| 高级自定义 | content/customization.md |
自定义指南涵盖实体映射的放置位置、多操作资源、异步轮询、属性自定义、计划修改、验证和状态升级器。
适用场景
- 从 OpenAPI 规范生成新的 Terraform 提供程序
- 使用
x-speakeasy-entity和x-speakeasy-entity-operation为 OpenAPI 规范添加注解 - 将 API 操作映射到 Terraform CRUD 方法
- 了解如何根据 OpenAPI 架构推断 Terraform 类型
- 配置用于生成 Terraform 提供程序的
workflow.yaml - 将提供程序发布到 Terraform Registry
- 用户提到:“terraform provider”、“generate terraform”、“create terraform provider”、“CRUD mapping”、“x-speakeasy-entity”、“terraform resource”或“terraform registry”
输入
| 输入 | 必需 | 说明 |
|---|---|---|
| OpenAPI 规范 | 是 | OpenAPI 3.0 或 3.1 规范(本地文件、URL 或注册表源) |
| 提供程序名称 | 是 | 提供程序的 PascalCase 名称(例如,Petstore) |
| 包名称 | 是 | 小写包标识符(例如,petstore) |
| 实体注解 | 是 | 架构上的 x-speakeasy-entity,操作上的 x-speakeasy-entity-operation |
输出
| 输出 | 位置 |
|---|---|
| 工作流配置 | .speakeasy/workflow.yaml |
| 生成配置 | gen.yaml |
| 生成的 Go 提供程序 | 输出目录(默认为当前目录) |
| Terraform 示例 | examples/ 目录 |
前提条件
- 已安装并完成身份验证的 Speakeasy CLI
- 带有实体注解的 OpenAPI 3.0 或 3.1 规范
- 已安装 Go(Terraform 提供程序使用 Go 编写)
- **身份验证:**设置
SPEAKEASY_API_KEY环境变量,或运行speakeasy auth login
export SPEAKEASY_API_KEY="<your-api-key>"
运行 speakeasy auth login 进行交互式身份验证,或设置 SPEAKEASY_API_KEY 环境变量。
命令
首次生成(快速入门)
speakeasy quickstart --skip-interactive --output console \
-s <spec-path> \
-t terraform \
-n <ProviderName> \
-p <package-name>
更改后重新生成
speakeasy run --output console
重新生成指定目标
speakeasy run -t <target-name> --output console
实体注解
生成之前,使用两个扩展为 OpenAPI 规范添加注解:
1. 将架构标记为实体
在应转换为 Terraform 资源的组件架构中添加 x-speakeasy-entity:
components:
schemas:
Pet:
x-speakeasy-entity: Pet
type: object
properties:
id:
type: string
readOnly: true
name:
type: string
price:
type: number
required:
- name
- price
2. 将操作映射到 CRUD 方法
为每个 API 操作添加 x-speakeasy-entity-operation:
paths:
/pets:
post:
x-speakeasy-entity-operation: Pet#create
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
/pets/{id}:
parameters:
- name: id
in: path
required: true
schema:
type: string
get:
x-speakeasy-entity-operation: Pet#read
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
put:
x-speakeasy-entity-operation: Pet#update
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
delete:
x-speakeasy-entity-operation: Pet#delete
responses:
"204":
description: Deleted
CRUD 映射摘要
| HTTP 方法 | 路径 | 注解 | 用途 |
|---|---|---|---|
POST | /resource | Entity#create | 创建新资源 |
GET | /resource/{id} | Entity#read | 读取单个资源 |
PUT | /resource/{id} | Entity#update | 更新资源 |
DELETE | /resource/{id} | Entity#delete | 删除资源 |
**数据源(列表):**对于列表端点(GET /resources),请使用单独的复数实体名称并配合 #read(例如,Pets#read)。不要使用 #list——它不是有效的操作类型。
Terraform 类型推断
Speakeasy 会根据 OpenAPI 规范自动推断 Terraform 架构类型:
| 规则 | 条件 | Terraform 属性 |
|---|---|---|
| 必需 | 属性在 CREATE 请求正文中为 required | Required: true |
| 可选 | 属性在 CREATE 请求正文中不是 required | Optional: true |
| 计算得出 | 属性出现在响应中,但不在 CREATE 请求中 | Computed: true |
| 强制重建 | 属性存在于 CREATE 请求中,但不在 UPDATE 请求中 | ForceNew(强制重建资源) |
| 枚举验证 | 属性定义为枚举 | 添加 Validator 以执行运行时检查 |
READ、UPDATE 或 DELETE 所需的每个参数,都必须出现在 CREATE 响应中,或在 CREATE 请求中设为必需。
示例
完整工作流:Petstore Provider
# 1. Ensure your spec has entity annotations (see above)
# 2. Generate the provider
speakeasy quickstart --skip-interactive --output console \
-s ./openapi.yaml \
-t terraform \
-n Petstore \
-p petstore
# 3. Build and test
cd terraform-provider-petstore
go build ./...
go test ./...
# 4. After spec changes, regenerate
speakeasy run --output console
这会生成可按如下方式使用的 Terraform 资源:
resource "petstore_pet" "my_pet" {
name = "Buddy"
price = 1500
}
工作流配置
本地规范
# .speakeasy/workflow.yaml
workflowVersion: 1.0.0
speakeasyVersion: latest
sources:
my-api:
inputs:
- location: ./openapi.yaml
targets:
my-provider:
target: terraform
source: my-api
使用 overlays 的远程规范
对于基于第三方 API 构建的 Provider,请远程获取规范并应用本地 overlays:
# .speakeasy/workflow.yaml
workflowVersion: 1.0.0
speakeasyVersion: latest
sources:
vendor-api:
inputs:
- location: https://api.vendor.com/openapi.yaml
overlays:
- location: terraform_overlay.yaml
output: openapi.yaml
targets:
vendor-provider:
target: terraform
source: vendor-api
使用 speakeasy overlay compare 跟踪上游 API 变更:
speakeasy overlay compare \
--before https://api.vendor.com/openapi.yaml \
--after terraform_overlay.yaml \
--out overlay-diff.yaml
仓库和命名约定
仓库命名
将仓库命名为 terraform-provider-XXX,其中 XXX 是 Provider 类型名称。Provider 类型名称应为小写字母数字字符([a-z][a-z0-9]),但允许使用连字符和下划线。
实体命名
实体名称使用 PascalCase,以便正确转换为 Terraform 的下划线命名形式:
| 实体名称 | Terraform 资源 |
|---|---|
Pet | petstore_pet |
GatewayControlPlane | konnect_gateway_control_plane |
MeshControlPlane | konnect_mesh_control_plane |
对于列表数据源,使用 PascalCase 复数形式(例如 Pets)。
资源导入
生成的 Provider 支持将现有资源导入 Terraform 状态。
简单键
对于只有一个 ID 字段的资源:
terraform import petstore_pet.my_pet my_pet_id
复合键
对于有多个 ID 字段的资源,请传入 JSON 编码对象:
terraform import my_test_resource.my_example \
'{ "primary_key_one": "9cedad30-...", "primary_key_two": "e20c40a0-..." }'
或者使用导入块:
import {
id = jsonencode({
primary_key_one: "9cedad30-..."
primary_key_two: "e20c40a0-..."
})
to = my_test_resource.my_example
}
然后生成配置:
terraform plan -generate-config-out=generated.tf
发布到 Terraform Registry
前提条件
- 公开仓库,命名为
terraform-provider-{name}(小写) - 用于发布签名的 GPG 签名密钥
- GoReleaser 配置
- 在 registry.terraform.io 上完成 注册
第 1 步:生成 GPG 密钥
gpg --full-generate-key # Choose RSA, 4096 bits
gpg --armor --export-secret-keys YOUR_KEY_ID > private.key
gpg --armor --export YOUR_KEY_ID > public.key
第 2 步:配置仓库机密
将以下内容添加到 GitHub 仓库机密:
terraform_gpg_secret_key- 私钥内容terraform_gpg_passphrase- 密钥密码
第 3 步:添加发布工作流
# .github/workflows/release.yaml
name: Release
on:
push:
tags: ['v*']
permissions:
contents: write
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-go@v4
with:
go-version-file: 'go.mod'
- uses: crazy-max/ghaction-import-gpg@v5
id: import_gpg
with:
gpg_private_key: ${{ secrets.terraform_gpg_secret_key }}
passphrase: ${{ secrets.terraform_gpg_passphrase }}
- uses: goreleaser/goreleaser-action@v6
with:
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}
第 4 步:在 Terraform Registry 注册
- 前往 registry.terraform.io
- 使用 GitHub 登录(需要组织管理员权限)
- 选择 Publish → Provider → Select your repository
注册后,推送标签时会自动发布版本。
Beta Provider 模式
对于大型 API,请分别维护 稳定版 和 Beta 版 Provider:
- 稳定版:
terraform-provider-{name},使用语义化版本(x.y.z) - Beta 版:
terraform-provider-{name}-beta,使用0.x版本号
用户可以同时安装两者。Beta 功能成熟后,将其纳入稳定版 Provider。要设置 Beta Provider,请创建单独的 terraform-provider-{name}-beta 仓库,并使用自己的 gen.yaml 和 0.x 版本号,然后与稳定版 Provider 一起发布。
测试 Provider
添加测试依赖项
在 .speakeasy/gen.yaml 中:
terraform:
additionalDependencies:
github.com/hashicorp/terraform-plugin-testing: v1.13.3
验收测试结构
// internal/provider/resource_test.go
func TestAccPet_Lifecycle(t *testing.T) {
t.Parallel()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProviders(),
Steps: []resource.TestStep{
{
Config: testAccPetConfig("Buddy", 1500),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("petstore_pet.test", "name", "Buddy"),
),
},
{
ResourceName: "petstore_pet.test",
ImportState: true,
ImportStateVerify: true,
},
},
})
}
运行测试
# Unit tests
go test -v ./...
# Acceptance tests (REQUIRES TF_ACC=1)
TF_ACC=1 go test -v ./internal/provider/... -timeout 30m
**注意:**如果没有 TF_ACC=1,测试会静默跳过,并显示 PASS 状态。
不要做的事
- 不要将
#list用作操作类型——只有create、read、update、delete有效 - 不要直接修改生成的 Go 代码——重新生成时更改会被覆盖。请改用覆盖层或钩子
- 不要省略 CREATE 响应正文——Terraform 需要该响应来填充计算字段(例如
id) - 不要在架构中跳过
x-speakeasy-entity——没有它,Speakeasy 就无法识别 Terraform 资源 - 不要对实体名称使用 camelCase 或 snake_case——请使用 PascalCase,这样 Terraform 才能正确使用下划线命名
- 不要在 monorepo 模式下生成 Terraform 提供程序——HashiCorp 要求使用专用仓库
故障排除
| 问题 | 原因 | 解决方案 |
|---|---|---|
invalid entity operation type: list | 使用了 #list 而不是 #read | 改为 Entity#read;列表端点使用复数实体名称 |
| 导入后资源缺少字段 | READ 操作未返回所有属性 | 确保 GET 端点返回完整的资源架构 |
意外字段上出现 ForceNew | 字段存在于 CREATE 中,但不存在于 UPDATE 请求中 | 如果该字段应可修改,请将其添加到 UPDATE 请求正文中 |
| 提供程序编译失败 | 缺少 Go 依赖项 | 在提供程序目录中运行 go mod tidy |
| 未填充计算字段 | CREATE 响应中缺少该字段 | 确保 CREATE 响应返回包含计算字段在内的完整资源 |
| 实体未显示为资源 | 缺少 x-speakeasy-entity 注解 | 将 x-speakeasy-entity: EntityName 添加到组件架构中 |
| 身份验证不起作用 | 缺少 API 密钥 | 设置 SPEAKEASY_API_KEY 环境变量,或运行 speakeasy auth login |
相关技能
start-new-sdk-project- 初始项目设置manage-openapi-overlays- 通过覆盖层添加实体注解diagnose-generation-failure- 排查生成错误