如何将HCL块解析为Go Map用于Terraform Provider验收测试
问题描述
我在实现Terraform Provider的验收测试,希望复用同一个输入变量来编写测试配置并验证资源是否配置正确。我的场景需要用到Terraform Plugin SDK中的TestCheckTypeSetElemNestedAttrs方法(该方法要求接收map[string]string类型的参数)。请问如何无需定义结构体,轻松复用同一变量生成Terraform配置块与map[string]string?示例代码如下:
func TestAccResource(t *testing.T) { t.Parallel() pr := TestAccProtoV6ProviderFactories name := "the name" innerBlock := `{ field1 = 0 field2 = false field4 = "value" }` innerBlockMap map[string]string := <------------ 如何在此处将innerBlock解析为map[string]string? resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: pr, Steps: []resource.TestStep{ { Config: fmt.Sprintf(` resource "myprovider_myresource" "test" { name = %[1]q innerField = %[1]s }`, innerBlock, name), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("myprovider_myresource.test", "name", name), resource.TestCheckTypeSetElemNestedAttrs("myprovider_myresource.test", "name", innerBlockMap), ), }, }, }) }
解决方案
你可以借助HashiCorp官方的HCL解析库,直接将HCL格式的字符串解析为map[string]interface{},再转换为map[string]string,无需定义结构体。同时要注意修正fmt.Sprintf的参数占位符顺序(原代码中占位符和参数不匹配)。
步骤1:引入依赖
确保你的项目中引入了HCL v2相关包:
import ( "github.com/hashicorp/hcl/v2/hclsimple" "github.com/zclconf/go-cty/cty" )
步骤2:解析HCL字符串并转换为目标Map
替换代码中innerBlockMap的定义部分,添加解析逻辑:
// 先定义临时map接收解析结果 var tempMap map[string]interface{} // 解析HCL字符串 err := hclsimple.Decode("test-config", []byte(innerBlock), nil, &tempMap) if err != nil { t.Fatalf("Failed to parse innerBlock: %v", err) } // 将interface{}类型的值转换为string,适配TestCheckTypeSetElemNestedAttrs的要求 innerBlockMap := make(map[string]string) for k, v := range tempMap { // 利用cty将各种类型转换为Terraform状态中存储的字符串格式 ctyVal := cty.NormalizeValue(cty.FromValue(v)) innerBlockMap[k] = ctyVal.AsString() }
步骤3:修正配置模板的占位符
原代码中fmt.Sprintf的占位符顺序错误,%[1]q对应第一个参数innerBlock,%[1]s也对应同一个参数,这会导致name字段被错误赋值为innerBlock的内容。修正为:
Config: fmt.Sprintf(` resource "myprovider_myresource" "test" { name = %[2]q innerField = %[1]s }`, innerBlock, name),
完整修正后的代码
func TestAccResource(t *testing.T) { t.Parallel() pr := TestAccProtoV6ProviderFactories name := "the name" innerBlock := `{ field1 = 0 field2 = false field4 = "value" }` // 解析HCL字符串为临时map var tempMap map[string]interface{} err := hclsimple.Decode("test-config", []byte(innerBlock), nil, &tempMap) if err != nil { t.Fatalf("Failed to parse innerBlock: %v", err) } // 转换为map[string]string innerBlockMap := make(map[string]string) for k, v := range tempMap { ctyVal := cty.NormalizeValue(cty.FromValue(v)) innerBlockMap[k] = ctyVal.AsString() } resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: pr, Steps: []resource.TestStep{ { Config: fmt.Sprintf(` resource "myprovider_myresource" "test" { name = %[2]q innerField = %[1]s }`, innerBlock, name), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("myprovider_myresource.test", "name", name), resource.TestCheckTypeSetElemNestedAttrs("myprovider_myresource.test", "innerField.*", innerBlockMap), ), }, }, }) }
补充说明
- 用
hclsimple.Decode可以快速将HCL字符串解析为通用map,无需提前定义结构体,适合测试场景的快速复用。 - 借助
cty包转换值为字符串,是因为Terraform的状态中所有属性值都以字符串形式存储,这样转换能保证和实际状态中的值格式一致,避免验证失败。 - 注意
TestCheckTypeSetElemNestedAttrs的第二个参数是TypeSet的路径,通常格式为{attribute_name}.*(比如innerField.*),原代码中写的name是错误的,需要替换为对应的TypeSet字段名。
内容的提问来源于stack exchange,提问作者Nakeuh
相关产品推荐
相关产品推荐

