Terraform SDKv2中如何让TypeList忽略元素顺序且无需改为TypeSet?
我有一个包含TypeList类型字段的Terraform资源,其schema定义如下:
&schema.Resource{ Schema: map[string]*schema.Schema{ "spec": &schema.Schema{ Description: "Specification of the resource", Elem: &schema.Resource{Schema: map[string]*schema.Schema{ "custom_addons": &schema.Schema{ Description: "some desc", Elem: &schema.Resource{Schema: map[string]*schema.Schema{ "depends_on": &schema.Schema{ Description: "some desc", Elem: &schema.Schema{Type: schema.TypeString}, Optional: true, Type: schema.TypeList, }, "name": &schema.Schema{ Description: "some desc", Optional: true, Type: schema.TypeString, } }}, MaxItems: 0, MinItems: 0, Optional: true, Type: schema.TypeList, }, // 其他复杂字段 // 其他复杂字段 // 其他复杂字段 }, MaxItems: 1, MinItems: 1, Optional: true, Type: schema.TypeList, }, }, }
我希望custom_addons字段忽略元素顺序的变化。由于后端API不记录元素顺序,其内部存储的顺序可能与Terraform配置文件中的顺序不一致,导致执行terraform plan时会出现无意义的顺序变更diff。
我知道这是TypeList的预期行为,若不关心顺序应使用TypeSet,但目前我们的Terraform Provider管理着约40个资源,合计有数千个应改为TypeSet的TypeList字段,修改不仅涉及数据类型变更,还需大量代码调整类型转换(从data.([]interface{})改为data.(*schema.Set)),成本过高。
尝试过DiffSupressFunc,仅能处理单纯的顺序变更场景,但当同时存在顺序变更和元素增改时,无法仅抑制顺序变更的diff;也探索过CustomizeDiff函数,但似乎仅允许修改computed字段。
请问在SDKv2中是否有可行的实现方式?
1. 基于CustomizeDiff的排序统一方案
CustomizeDiff支持修改非computed字段的diff,核心思路是在diff阶段,将配置和状态中的目标列表按相同规则排序,统一顺序后再对比,这样顺序变更不会产生diff,仅元素内容的增删改会触发有效diff,完全不需要修改字段的TypeList类型。
示例实现:
func resourceExampleCustomizeDiff(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error { // 处理配置侧的custom_addons排序 if configAddons, ok := d.GetOk("spec.0.custom_addons"); ok { configList := configAddons.([]interface{}) // 按name字段升序排序 sort.Slice(configList, func(i, j int) bool { iName := configList[i].(map[string]interface{})["name"].(string) jName := configList[j].(map[string]interface{})["name"].(string) return iName < jName }) if err := d.Set("spec.0.custom_addons", configList); err != nil { return err } } // 处理状态侧的custom_addons排序 if stateAddons, ok := d.GetOk("spec.0.custom_addons"); ok { stateList := stateAddons.([]interface{}) // 用相同规则排序状态列表 sort.Slice(stateList, func(i, j int) bool { iName := stateList[i].(map[string]interface{})["name"].(string) jName := stateList[j].(map[string]interface{})["name"].(string) return iName < jName }) if err := d.SetNew("spec.0.custom_addons", stateList); err != nil { return err } } return nil }
将该函数关联到资源定义:
&schema.Resource{ // 其他资源配置 CustomizeDiff: resourceExampleCustomizeDiff, Schema: // 你的schema定义 }
2. 通用化排序工具函数
如果需要批量处理多个类似TypeList字段,可以封装通用排序函数,减少重复代码:
func sortListByKey(d *schema.ResourceDiff, fieldPath string, sortKey string) error { // 排序配置侧字段 if configVal, ok := d.GetOk(fieldPath); ok { configList := configVal.([]interface{}) sort.Slice(configList, func(i, j int) bool { iKey := configList[i].(map[string]interface{})[sortKey].(string) jKey := configList[j].(map[string]interface{})[sortKey].(string) return iKey < jKey }) if err := d.Set(fieldPath, configList); err != nil { return err } } // 排序状态侧字段 if stateVal, ok := d.GetOk(fieldPath); ok { stateList := stateVal.([]interface{}) sort.Slice(stateList, func(i, j int) bool { iKey := stateList[i].(map[string]interface{})[sortKey].(string) jKey := stateList[j].(map[string]interface{})[sortKey].(string) return iKey < jKey }) if err := d.SetNew(fieldPath, stateList); err != nil { return err } } return nil }
在CustomizeDiff中批量调用:
func resourceExampleCustomizeDiff(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error { if err := sortListByKey(d, "spec.0.custom_addons", "name"); err != nil { return err } // 可添加更多需要忽略顺序的字段 // if err := sortListByKey(d, "spec.0.other_list", "id"); err != nil { // return err // } return nil }
3. 改进版DiffSuppressFunc(适配复杂场景)
如果偏好使用DiffSuppressFunc,可以实现排序后对比的逻辑,同时兼容顺序变更+元素增改的混合场景:
func suppressListOrderDiff(k, old, new string, d *schema.ResourceData) bool { // 解析新旧值为列表结构 var oldList, newList []map[string]interface{} if err := json.Unmarshal([]byte(old), &oldList); err != nil { return false } if err := json.Unmarshal([]byte(new), &newList); err != nil { return false } // 按name字段排序 sort.Slice(oldList, func(i, j int) bool { return oldList[i]["name"].(string) < oldList[j]["name"].(string) }) sort.Slice(newList, func(i, j int) bool { return newList[i]["name"].(string) < newList[j]["name"].(string) }) // 序列化后对比是否一致 oldSorted, _ := json.Marshal(oldList) newSorted, _ := json.Marshal(newList) return string(oldSorted) == string(newSorted) }
在schema中配置该函数:
"custom_addons": &schema.Schema{ // 其他字段配置 DiffSuppressFunc: suppressListOrderDiff, },
注意:该方案需要确保JSON序列化的兼容性(如空值、字段顺序),适合结构简单的列表字段。
内容的提问来源于stack exchange,提问作者ravi kumar

