package models import ( "net/url" "slices" "strings" "github.com/fvbommel/sortorder" "github.com/go-json-experiment/json/jsontext" "github.com/prymitive/karma/internal/config" ) // Annotation extends Alertmanager scheme of key:value with additional data // to control how given annotation should be rendered type Annotation struct { Name string `json:"name"` Value string `json:"value"` Visible bool `json:"visible"` IsLink bool `json:"isLink"` IsAction bool `json:"isAction"` } func (a Annotation) MarshalJSONTo(enc *jsontext.Encoder) error { w := jsonWriter{enc: enc} a.marshalTo(&w) return w.err } func (a *Annotation) marshalTo(w *jsonWriter) { w.beginObject() w.key("name") w.str(a.Name) w.key("value") w.str(a.Value) w.key("visible") w.boolean(a.Visible) w.key("isLink") w.boolean(a.IsLink) w.key("isAction") w.boolean(a.IsAction) w.endObject() } // Annotations is a slice of Annotation structs, needed to implement sorting type Annotations []Annotation func (a Annotations) MarshalJSONTo(enc *jsontext.Encoder) error { w := jsonWriter{enc: enc} w.beginArray() for i := range a { a[i].marshalTo(&w) } w.endArray() return w.err } func compareAnnotations(a, b Annotation) int { // Sort the annotations listed in config.Config.Annotations.Order first, in // the order they appear in that list; remaining annotations are sorted alphabetically. ai, bi := -1, -1 for index, name := range config.Config.Annotations.Order { if a.Name == name { ai = index } else if b.Name == name { bi = index } // If both annotations are in c.C.A.Order, sort them according to the // order in that list. if ai >= 0 && bi >= 0 { if ai < bi { return -1 } return 1 } } // If only one of the annotations was in c.C.A.Order, that one goes first. if ai != bi { if bi < ai { return -1 } return 1 } // If neither annotation was in c.C.A.Order, sort alphabetically. if sortorder.NaturalLess(a.Name, b.Name) { return -1 } if sortorder.NaturalLess(b.Name, a.Name) { return 1 } return 0 } func NewAnnotation(name, value string) Annotation { return Annotation{ Name: name, Value: value, Visible: isVisible(name), IsLink: isLink(value), IsAction: isAction(name), } } // AnnotationsFromMap will convert a map[string]string to a list of Annotation // instances, it takes care of setting proper value for Visible attribute func AnnotationsFromMap(m map[string]string) Annotations { annotations := make(Annotations, 0, len(m)) for name, value := range m { a := Annotation{ Name: name, Value: value, Visible: isVisible(name), IsLink: isLink(value), IsAction: isAction(name), } annotations = append(annotations, a) } SortAnnotations(annotations) return annotations } func SortAnnotations(annotations Annotations) { slices.SortFunc(annotations, compareAnnotations) } var linkSchemes = []string{ "ftp", "http", "https", } func isLink(s string) bool { s = strings.TrimSpace(s) if strings.Contains(s, " ") { return false } u, err := url.ParseRequestURI(s) if err != nil { return false } if slices.Contains(linkSchemes, u.Scheme) { // parses with url.Parse and scheme is in the list of supported schemes return true } return false } func isVisible(name string) bool { if slices.Contains(config.Config.Annotations.Visible, name) { // annotation was explicitly marked as visible return true } if slices.Contains(config.Config.Annotations.Hidden, name) { // annotation was explicitly marked as hidden return false } if config.Config.Annotations.Default.Hidden { // user specified that default is to hide anything without explicit rules return false } // default to show everything return true } func isAction(name string) bool { return slices.Contains(config.Config.Annotations.Actions, name) }