如何在Terraform验收测试中获取已配置的Provider客户端?
问题描述
我基于Terraform Plugin Framework的脚手架示例开发Provider,核心代码如下:
// provider.go type ScaffoldingProvider struct { version string } // ScaffoldingProviderModel describes the provider data model. type ScaffoldingProviderModel struct { Endpoint types.String `tfsdk:"endpoint"` } type MyClientConfiguration struct { MyClient SomeClient } func (p *ScaffoldingProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ "endpoint": schema.StringAttribute{ MarkdownDescription: "Client API endpoint", Required: true, }, }, } } func (p *ScaffoldingProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { var data ScaffoldingProviderModel resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return } // 基于Terraform配置构建客户端 client := MyClientConfiguration { // 使用endpoint等配置初始化MyClient } resp.DataSourceData = client resp.ResourceData = client }
现在需要编写资源验收测试,实现自定义检查逻辑确认资源是否在后端真实创建。我希望直接使用已配置好的客户端调用后端API,且不想硬编码配置,而是获取测试中使用的已配置Provider。测试代码如下:
var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){ "scaffolding": providerserver.NewProtocol6WithError(New("test")()), } func TestAccExampleResource(t *testing.T) { const config = ` resource "scaffolding_example" "test" { configurable_attribute = "foo" } ` resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ // 创建和读取测试 { Config: config, Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("scaffolding_example.test", "configurable_attribute", "foo"), resource.TestCheckResourceAttr("scaffolding_example.test", "id", "example-id"), ), }, testAccExampleResourceActuallyCreated("scaffolding_example.test"), // 删除测试自动执行 }, }) } func testAccExampleResourceActuallyCreated(resourceName string) resource.TestCheckFunc { return func(s *terraform.State) error { // 从状态中获取资源 rs, ok := s.RootModule().Resources[resourceName] if !ok { return fmt.Errorf("未找到资源: %s", resourceName) } if rs.Primary.ID == "" { return fmt.Errorf("资源ID未设置") } // 官方文档示例用以下方式获取客户端 // client := testAccProvider.Meta().(*ExampleClient) // 但我用的是ProtoV6ProviderFactories而非全局testAccProvider,该如何获取MyClientConfiguration? // 有没有比全局变量更优的实现方式? // TODO: 使用客户端调用后端API检查资源是否存在 ... } }
官方文档示例通过全局变量testAccProvider获取客户端,但我使用的是ProtoV6ProviderFactories,请问如何获取已配置的MyClientConfiguration?是否有比全局变量更好的实现方式?
解决方案
针对Terraform Plugin Framework(ProtoV6)的测试场景,有两种优雅的方式获取已配置的MyClientConfiguration,无需依赖全局变量:
方法一:通过Terraform State获取Provider元数据
在Plugin Framework中,Configure方法里设置的resp.ResourceData会被自动关联到Provider的元数据中,可直接从terraform.State的Provider实例中提取:
修改你的检查函数如下:
func testAccExampleResourceActuallyCreated(resourceName string) resource.TestCheckFunc { return func(s *terraform.State) error { // 从状态中获取资源 rs, ok := s.RootModule().Resources[resourceName] if !ok { return fmt.Errorf("未找到资源: %s", resourceName) } if rs.Primary.ID == "" { return fmt.Errorf("资源ID未设置") } // 获取指定的Provider实例 prov, ok := s.Providers["scaffolding"] if !ok { return fmt.Errorf("未找到scaffolding provider") } // 将Provider元数据转换为MyClientConfiguration类型 clientConfig, ok := prov.Meta().(*MyClientConfiguration) if !ok { return fmt.Errorf("无法将Provider元数据转换为MyClientConfiguration") } // 现在可以使用clientConfig.MyClient调用后端API // 示例:检查资源是否存在 exists, err := clientConfig.MyClient.GetResource(rs.Primary.ID) if err != nil { return fmt.Errorf("调用后端API失败: %w", err) } if !exists { return fmt.Errorf("资源%s未在后端创建", rs.Primary.ID) } return nil } }
这种方式无需修改Provider核心代码,直接利用Terraform测试框架的内置机制获取配置好的客户端。
方法二:通过带状态的Provider工厂闭包获取
如果需要更直接的控制,可以在测试中使用闭包保存Provider实例,避免全局变量:
步骤1:修改Provider结构体,添加meta字段存储客户端
type ScaffoldingProvider struct { version string meta *MyClientConfiguration // 新增字段保存配置后的客户端 }
步骤2:更新Configure方法,保存客户端到Provider实例
func (p *ScaffoldingProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { var data ScaffoldingProviderModel resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return } // 基于Terraform配置构建客户端 client := MyClientConfiguration { // 使用endpoint等配置初始化MyClient } resp.DataSourceData = client resp.ResourceData = client p.meta = client // 保存到Provider实例中 }
步骤3:在测试中使用闭包创建Provider工厂,保存实例引用
func TestAccExampleResource(t *testing.T) { var testProvider *ScaffoldingProvider // 用闭包创建Provider工厂,保存实例引用 testAccProtoV6ProviderFactories := map[string]func() (tfprotov6.ProviderServer, error){ "scaffolding": func() (tfprotov6.ProviderServer, error) { p := New("test")().(*ScaffoldingProvider) testProvider = p return providerserver.NewProtocol6WithError(p) }, } const config = ` resource "scaffolding_example" "test" { configurable_attribute = "foo" } ` resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: config, Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("scaffolding_example.test", "configurable_attribute", "foo"), resource.TestCheckResourceAttr("scaffolding_example.test", "id", "example-id"), ), }, func(s *terraform.State) error { // 直接从保存的Provider实例获取客户端 clientConfig := testProvider.meta if clientConfig == nil { return fmt.Errorf("Provider未完成配置") } // 调用后端API检查资源 rs, ok := s.RootModule().Resources["scaffolding_example.test"] if !ok { return fmt.Errorf("未找到资源") } exists, err := clientConfig.MyClient.GetResource(rs.Primary.ID) if err != nil { return fmt.Errorf("调用API失败: %w", err) } if !exists { return fmt.Errorf("资源未在后端创建") } return nil }, }, }) }
这种方式更灵活,适合需要在测试中多次访问Provider内部状态的场景。
内容的提问来源于stack exchange,提问作者John
相关产品推荐
相关产品推荐

