You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 00:50:54