/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ package utils import ( "reflect" "slices" "strconv" "strings" ) // OrderedListFromMap generates a list from a map's values. The values are sorted based on the map's keys. // The sorting is done using a custom comparison function that compares the keys with special rules, assuming they // are strings representing device names or similar, i.e. "disk0", "net1", etc. func OrderedListFromMap(inputMap map[string]interface{}) []interface{} { itemCount := len(inputMap) keyList := make([]string, itemCount) i := 0 for key := range inputMap { keyList[i] = key i++ } slices.SortFunc(keyList, compareWithPrefix) return OrderedListFromMapByKeyValues(inputMap, keyList) } // CompareWithPrefix compares two string values with special rules: // - If both start with the same prefix, trims the prefix and compares the rest as numbers if possible. // - If numbers are equal, falls back to string comparison (preserving digit formatting). // - If numeric parsing fails, falls back to string comparison. // - If prefixes differ, compares the whole values as strings. func compareWithPrefix(a, b string) int { prefix := commonPrefix(a, b) if prefix != "" { aRest := strings.TrimPrefix(a, prefix) bRest := strings.TrimPrefix(b, prefix) aNum, aErr := strconv.Atoi(aRest) bNum, bErr := strconv.Atoi(bRest) if aErr == nil && bErr == nil { if aNum != bNum { if aNum < bNum { return -1 } return 1 } // numeric values equal, fallback to string comparison return strings.Compare(aRest, bRest) } return strings.Compare(aRest, bRest) } return strings.Compare(a, b) } // commonPrefix returns the longest common prefix of two strings. func commonPrefix(a, b string) string { minLen := len(a) if len(b) < minLen { minLen = len(b) } for i := range minLen { if a[i] != b[i] { return a[:i] } } return a[:minLen] } // ListResourcesAttributeValue generates a list of strings from a Terraform resource list (which is list of maps). // The list is generated by extracting a specific key attribute from each resource. If the attribute is not found in a // resource, it is skipped. func ListResourcesAttributeValue(resourceList []interface{}, keyAttr string) []string { var l []string for _, resource := range resourceList { if resource == nil { continue } r := resource.(map[string]interface{}) if value, ok := r[keyAttr].(string); ok { l = append(l, value) } } return l } // MapResourcesByAttribute generates a map of resources from a resource list, using a specified attribute as the key // and the resource as the value. If the attribute is not found in a resource, it is skipped. func MapResourcesByAttribute(resourceList []interface{}, keyAttr string) map[string]interface{} { m := make(map[string]interface{}, len(resourceList)) for _, resource := range resourceList { if resource == nil { continue } r := resource.(map[string]interface{}) if key, ok := r[keyAttr].(string); ok { m[key] = r } } return m } // OrderedListFromMapByKeyValues generates a list from a map's values. // The values are sorted based on the provided key list. If a key is not found in the map, it is skipped. func OrderedListFromMapByKeyValues(inputMap map[string]interface{}, keyList []string) []interface{} { orderedList := make([]interface{}, len(keyList)) for i, k := range keyList { val, ok := inputMap[k] if ok { orderedList[i] = val } } return orderedList } // MapDiff compares the difference between two maps and returns the elements that are in the plan but not // in the state (toCreate), the elements that are in the plan and in the state but are different (toUpdate), // and the elements that are in the state but not in the plan (toDelete). // The keyFunc is used to extract a unique key from each element to compare them. func MapDiff[T any](plan map[string]T, state map[string]T) (map[string]T, map[string]T, map[string]T) { toCreate := map[string]T{} toUpdate := map[string]T{} toDelete := map[string]T{} for key, p := range plan { s, ok := state[key] if !ok { toCreate[key] = p } else if !reflect.DeepEqual(p, s) { toUpdate[key] = p } } for key, s := range state { _, ok := plan[key] if !ok { toDelete[key] = s } } return toCreate, toUpdate, toDelete }