将map[string]接口{}类型的地形资源数据转换为struct



我正在创建一个自定义的地形提供程序,我遇到了这个问题。我试图将schema.TypeList字段转换为结构体,类型列表看起来像这样:

"template": {
Type:     schema.TypeList,
Required: true,
ForceNew: false,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"lists_test": {
Type:     schema.TypeSet,
Required: true,
ForceNew: false,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"name_test": {
Type:     schema.TypeString,
Required: true,
ForceNew: false,
},
},},

和我要对齐的结构体看起来像这样:

type TestStruct struct {
NameTest string   `json:"name_test"`
ListsTests   []string `json:"lists_test"`
}

我尝试了几个解决方案,例如,我尝试将其解组为json。如下所示:

template := d.Get("template").([]interface{})[0].(map[string]interface{})
templateStr, err := json.Marshal(template)
templateConverted := &TestStruct{}
json.Unmarshal(template, templateConverted)

然而,我得到一个错误json: unsupported type: SchemaSetFunc,这可能是因为它试图封送一个schema.Schema类型而不是map[string]接口{}类型,这让我感到困惑。我也尝试使用gohcl.DecodeBody,但我放弃了这个想法,因为它的使用似乎更倾向于读取直接的tf文件,而不是*schema.ResourceData类型。

有人有过处理这种情况的相同经历吗?任何帮助或建议都是感激的。谢谢你!

Terraform的旧SDK (SDKv2)不是围绕解码为标记结构的范例设计的,而是期望您使用d.Get并手动键入断言单个值,在您的情况下可能看起来像这样:

raw := d.Get("template").([]interface{})[0].(map[string]interface{})
t := &TestStruct{
NameTest: raw["name_test"].(string),
ListsTests: make([]string, len(raw["lists_test"].([]interface{})),
}
for i, itemRaw := range raw["lists_test"].([]interface{}) {
t.ListsTests[i] = itemRaw.(string)
}

大多数Terraform提供程序的习惯风格是为每个复杂类型的属性在单独的函数中编写这样的逻辑,其中每个函数返回目标平台SDK中适当类型的对象。通常还会有一个相反方向的匹配函数:给定目标平台SDK中的对象,返回一个map[string]interface{},可以使用d.Set将其分配给该属性。


然而,仅仅因为SDK中没有内置的东西来处理这个问题,这并不意味着你不能使用其他库,这些库是任何Go程序中使用的更通用的实用程序。

一个例子库是github.com/mitchellh/mapstructure,它的设计正是为了实现您心中的目标:获取某种接口类型的值,并尝试使用反射将其适合标记的结构类型。

如果你想使用这个库,那么你需要用mapstructure:来注释你的结构,而不是json:,然后将你的raw值传递给mapstructure.Decode函数:

raw := d.Get("template").([]interface{})[0].(map[string]interface{})
var t TestStruct
err := mapstructure.Decode(raw, &t)

由于SDKv2中的schema.ResourceData抽象保证根据您定义的模式返回特定的数据类型,只要您的模式和目标类型匹配,您通常不会从mapstructure.Decode中获得错误,但是检查错误仍然是一个好主意,因为否则您的t值可能不会完全填充,导致下游混乱的破坏行为。

这不是官方提供程序中使用的典型实现风格,但如果您发现这种风格更方便或更易于维护,那么以这种方式编写提供程序并没有真正的危害。


或者,如果你还没有深入投资于SDKv2,那么你可能希望考虑使用Terraform插件框架。除了围绕现代Terraform的类型系统设计(而SDKv2是为Terraform v0.11和更早的版本设计的)之外,它还支持更像您所希望的编程风格,使用tfsdk.Plan.Gettfsdk.Plan.GetAttribute这样的方法可以直接解码为适当形状和适当标记的"normal";价值。

我不能轻易地展示一个例子,因为它假定一个提供者以完全不同的方式编写,但希望你能从这两个函数的签名中看到它们是如何使用的。在访问状态、配置和计划中有更多的注释和示例。

最新更新