package models import ( // nolint:gosec "crypto/sha1" "encoding/json" "fmt" "sort" "github.com/grafana/grafana-plugin-sdk-go/data" ) // InstanceLabels is an extension to data.Labels with methods // for database serialization. type InstanceLabels data.Labels // FromDB loads labels stored in the database as json tuples into InstanceLabels. // FromDB is part of the xorm Conversion interface. func (il *InstanceLabels) FromDB(b []byte) error { tl := &tupleLabels{} err := json.Unmarshal(b, tl) if err != nil { return err } labels, err := tupleLablesToLabels(*tl) if err != nil { return err } *il = labels return nil } // ToDB is not implemented as serialization is handled with manual SQL queries). // ToDB is part of the xorm Conversion interface. func (il *InstanceLabels) ToDB() ([]byte, error) { // Currently handled manually in sql command, needed to fulfill the xorm // converter interface it seems return []byte{}, fmt.Errorf("database serialization of alerting ng Instance labels is not implemented") } // StringAndHash returns a the json representation of the labels as tuples // sorted by key. It also returns the a hash of that representation. func (il *InstanceLabels) StringAndHash() (string, string, error) { tl := labelsToTupleLabels(*il) b, err := json.Marshal(tl) if err != nil { return "", "", fmt.Errorf("can not gereate key for alert instance due to failure to encode labels: %w", err) } h := sha1.New() if _, err := h.Write(b); err != nil { return "", "", err } return string(b), fmt.Sprintf("%x", h.Sum(nil)), nil } // The following is based on SDK code, copied for now // tupleLables is an alternative representation of Labels (map[string]string) that can be sorted // and then marshalled into a consistent string that can be used a map key. All tupleLabel objects // in tupleLabels should have unique first elements (keys). type tupleLabels []tupleLabel // tupleLabel is an element of tupleLabels and should be in the form of [2]{"key", "value"}. type tupleLabel [2]string // Sort tupleLabels by each elements first property (key). func (t *tupleLabels) sortBtKey() { if t == nil { return } sort.Slice((*t)[:], func(i, j int) bool { return (*t)[i][0] < (*t)[j][0] }) } // labelsToTupleLabels converts Labels (map[string]string) to tupleLabels. func labelsToTupleLabels(l InstanceLabels) tupleLabels { t := make(tupleLabels, 0, len(l)) for k, v := range l { t = append(t, tupleLabel{k, v}) } t.sortBtKey() return t } // tupleLabelsToLabels converts tupleLabels to Labels (map[string]string), erroring if there are duplicate keys. func tupleLablesToLabels(tuples tupleLabels) (InstanceLabels, error) { if tuples == nil { return InstanceLabels{}, nil } labels := make(map[string]string) for _, tuple := range tuples { if key, ok := labels[tuple[0]]; ok { return nil, fmt.Errorf("duplicate key '%v' in lables: %v", key, tuples) } labels[tuple[0]] = tuple[1] } return labels, nil }