package promutil import ( "fmt" "reflect" "regexp" "sort" "strings" "github.com/opencost/opencost/core/pkg/util/json" ) var invalidLabelCharRE = regexp.MustCompile(`[^a-zA-Z0-9_]`) // AnyToLabels will create prometheus labels based on the fields of the interface // passed. Note that this method is quite expensive and should only be used when absolutely // necessary. func AnyToLabels(a interface{}) (map[string]string, error) { val := reflect.ValueOf(a) if val.Kind() == reflect.Map { return MapToLabels(a), nil } b, e := json.Marshal(a) if e != nil { return nil, e } var m map[string]interface{} e = json.Unmarshal(b, &m) if e != nil { return nil, e } return MapToLabels(m), nil } // MapToLabels accepts a map type, and will return a new map containing all the nested // fields separated by _ with string versions of the values. func MapToLabels(m interface{}) map[string]string { val := reflect.ValueOf(m) if val.Kind() != reflect.Map { return map[string]string{} } r := make(map[string]string) for _, k := range val.MapKeys() { key := strings.ToLower(k.String()) v := val.MapIndex(k).Interface() switch v.(type) { case uint, uint8, uint16, uint32, uint64, int, int8, int16, int32, int64, string, bool, float32, float64: r[key] = fmt.Sprintf("%+v", v) default: mm := MapToLabels(v) for kk, vv := range mm { r[fmt.Sprintf("%s_%s", key, kk)] = vv } } } return r } // LabelNamesFrom accepts a mapping of labels to values and returns the label names. func LabelNamesFrom(labels map[string]string) []string { keys := []string{} for key := range labels { keys = append(keys, key) } return keys } // Prepends a qualifier string to the keys provided in the m map and returns the new keys and values. func KubePrependQualifierToLabels(m map[string]string, qualifier string) ([]string, []string) { // sanitize the keys in m to prevent duplicate output keys sanitizedM := make(map[string]string) for k, v := range m { sanitizedM[SanitizeLabelName(k)] = v } keys := make([]string, 0, len(sanitizedM)) for k := range sanitizedM { keys = append(keys, k) } sort.Strings(keys) values := make([]string, 0, len(sanitizedM)) for i, k := range keys { keys[i] = qualifier + k values = append(values, sanitizedM[k]) } return keys, values } // Converts kubernetes labels into prometheus labels. func KubeLabelsToLabels(labels map[string]string) ([]string, []string) { return KubePrependQualifierToLabels(labels, "label_") } // Converts kubernetes annotations into prometheus labels. func KubeAnnotationsToLabels(labels map[string]string) ([]string, []string) { return KubePrependQualifierToLabels(labels, "annotation_") } // Replaces all illegal prometheus label characters with _ func SanitizeLabelName(s string) string { return invalidLabelCharRE.ReplaceAllString(s, "_") } // SanitizeLabels sanitizes all label names in the given map. This may cause // collisions, which is intentional as collisions that are not caught prior to // attempted emission will cause fatal errors. In the case of a collision, the // last value seen will be set, and all previous values will be overwritten. func SanitizeLabels(labels map[string]string) map[string]string { response := make(map[string]string, len(labels)) for k, v := range labels { response[SanitizeLabelName(k)] = v } return response }