230 lines
6.8 KiB
Go
230 lines
6.8 KiB
Go
package routingtree
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"maps"
|
|
"slices"
|
|
|
|
"github.com/prometheus/alertmanager/config"
|
|
"github.com/prometheus/alertmanager/pkg/labels"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
promModel "github.com/prometheus/common/model"
|
|
|
|
model "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/resource/routingtree/v0alpha1"
|
|
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
|
gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils"
|
|
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
|
"github.com/grafana/grafana/pkg/util"
|
|
)
|
|
|
|
func convertToK8sResource(orgID int64, r definitions.Route, version string, namespacer request.NamespaceMapper) (*model.RoutingTree, error) {
|
|
spec := model.Spec{
|
|
Defaults: model.RouteDefaults{
|
|
GroupBy: r.GroupByStr,
|
|
GroupWait: optionalPrometheusDurationToString(r.GroupWait),
|
|
GroupInterval: optionalPrometheusDurationToString(r.GroupInterval),
|
|
RepeatInterval: optionalPrometheusDurationToString(r.RepeatInterval),
|
|
Receiver: r.Receiver,
|
|
},
|
|
}
|
|
for _, route := range r.Routes {
|
|
if route == nil {
|
|
continue
|
|
}
|
|
spec.Routes = append(spec.Routes, convertRouteToK8sSubRoute(route))
|
|
}
|
|
|
|
var result = &model.RoutingTree{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: model.UserDefinedRoutingTreeName,
|
|
Namespace: namespacer(orgID),
|
|
ResourceVersion: version,
|
|
},
|
|
Spec: spec,
|
|
}
|
|
result.SetProvenanceStatus(string(r.Provenance))
|
|
result.UID = gapiutil.CalculateClusterWideUID(result)
|
|
return result, nil
|
|
}
|
|
|
|
func convertRouteToK8sSubRoute(r *definitions.Route) model.Route {
|
|
result := model.Route{
|
|
GroupBy: r.GroupByStr,
|
|
MuteTimeIntervals: r.MuteTimeIntervals,
|
|
Continue: r.Continue,
|
|
GroupWait: optionalPrometheusDurationToString(r.GroupWait),
|
|
GroupInterval: optionalPrometheusDurationToString(r.GroupInterval),
|
|
RepeatInterval: optionalPrometheusDurationToString(r.RepeatInterval),
|
|
Routes: make([]model.Route, 0, len(r.Routes)),
|
|
}
|
|
if r.Receiver != "" {
|
|
result.Receiver = util.Pointer(r.Receiver)
|
|
}
|
|
|
|
if r.Match != nil {
|
|
keys := slices.Collect(maps.Keys(r.Match))
|
|
slices.Sort(keys)
|
|
for _, key := range keys {
|
|
result.Matchers = append(result.Matchers, model.Matcher{
|
|
Label: key,
|
|
Type: model.MatcherTypeEqual,
|
|
Value: r.Match[key],
|
|
})
|
|
}
|
|
}
|
|
|
|
if r.MatchRE != nil {
|
|
keys := slices.Collect(maps.Keys(r.MatchRE))
|
|
slices.Sort(keys)
|
|
for _, key := range keys {
|
|
m := model.Matcher{
|
|
Label: key,
|
|
Type: model.MatcherTypeEqualTilde,
|
|
}
|
|
value, _ := r.MatchRE[key].MarshalYAML()
|
|
if s, ok := value.(string); ok {
|
|
m.Value = s
|
|
}
|
|
result.Matchers = append(result.Matchers, m)
|
|
}
|
|
}
|
|
|
|
for _, m := range r.Matchers {
|
|
result.Matchers = append(result.Matchers, model.Matcher{
|
|
Label: m.Name,
|
|
Type: model.MatcherType(m.Type.String()),
|
|
Value: m.Value,
|
|
})
|
|
}
|
|
for _, m := range r.ObjectMatchers {
|
|
result.Matchers = append(result.Matchers, model.Matcher{
|
|
Label: m.Name,
|
|
Type: model.MatcherType(m.Type.String()),
|
|
Value: m.Value,
|
|
})
|
|
}
|
|
for _, route := range r.Routes {
|
|
if route == nil {
|
|
continue
|
|
}
|
|
result.Routes = append(result.Routes, convertRouteToK8sSubRoute(route))
|
|
}
|
|
return result
|
|
}
|
|
|
|
func convertToDomainModel(obj *model.RoutingTree) (definitions.Route, string, error) {
|
|
defaults := obj.Spec.Defaults
|
|
result := definitions.Route{
|
|
Receiver: defaults.Receiver,
|
|
GroupByStr: defaults.GroupBy,
|
|
Routes: make([]*definitions.Route, 0, len(obj.Spec.Routes)),
|
|
}
|
|
path := "."
|
|
var errs []error
|
|
|
|
result.GroupWait = parsePrometheusDuration(defaults.GroupWait, func(err error) {
|
|
errs = append(errs, fmt.Errorf("obj '%s' has invalid format of 'groupWait': %w", path, err))
|
|
})
|
|
result.GroupInterval = parsePrometheusDuration(defaults.GroupInterval, func(err error) {
|
|
errs = append(errs, fmt.Errorf("obj '%s' has invalid format of 'groupInterval': %w", path, err))
|
|
})
|
|
result.RepeatInterval = parsePrometheusDuration(defaults.RepeatInterval, func(err error) {
|
|
errs = append(errs, fmt.Errorf("obj '%s' has invalid format of 'repeatInterval': %w", path, err))
|
|
})
|
|
|
|
for idx, route := range obj.Spec.Routes {
|
|
p := fmt.Sprintf("%s[%d]", path, idx)
|
|
s, err := convertK8sSubRouteToRoute(route, p)
|
|
if len(err) > 0 {
|
|
errs = append(errs, err...)
|
|
} else {
|
|
result.Routes = append(result.Routes, &s)
|
|
}
|
|
}
|
|
if len(errs) > 0 {
|
|
return definitions.Route{}, "", errors.Join(errs...)
|
|
}
|
|
result.Provenance = ""
|
|
return result, obj.ResourceVersion, nil
|
|
}
|
|
|
|
func convertK8sSubRouteToRoute(r model.Route, path string) (definitions.Route, []error) {
|
|
result := definitions.Route{
|
|
GroupByStr: r.GroupBy,
|
|
MuteTimeIntervals: r.MuteTimeIntervals,
|
|
Routes: make([]*definitions.Route, 0, len(r.Routes)),
|
|
Matchers: make(config.Matchers, 0, len(r.Matchers)),
|
|
Continue: r.Continue,
|
|
}
|
|
if r.Receiver != nil {
|
|
result.Receiver = *r.Receiver
|
|
}
|
|
var errs []error
|
|
result.GroupWait = parsePrometheusDuration(r.GroupWait, func(err error) {
|
|
errs = append(errs, fmt.Errorf("route '%s' has invalid format of 'groupWait': %w", path, err))
|
|
})
|
|
result.GroupInterval = parsePrometheusDuration(r.GroupInterval, func(err error) {
|
|
errs = append(errs, fmt.Errorf("route '%s' has invalid format of 'groupInterval': %w", path, err))
|
|
})
|
|
result.RepeatInterval = parsePrometheusDuration(r.RepeatInterval, func(err error) {
|
|
errs = append(errs, fmt.Errorf("route '%s' has invalid format of 'repeatInterval': %w", path, err))
|
|
})
|
|
|
|
for _, matcher := range r.Matchers {
|
|
var mt labels.MatchType
|
|
switch matcher.Type {
|
|
case model.MatcherTypeEqual:
|
|
mt = labels.MatchEqual
|
|
case model.MatcherTypeNotEqual:
|
|
mt = labels.MatchNotEqual
|
|
case model.MatcherTypeEqualRegex:
|
|
mt = labels.MatchRegexp
|
|
case model.MatcherTypeNotEqualRegex:
|
|
mt = labels.MatchNotRegexp
|
|
default:
|
|
errs = append(errs, fmt.Errorf("route '%s' has unsupported matcher type: %s", path, matcher.Type))
|
|
continue
|
|
}
|
|
|
|
m, err := labels.NewMatcher(mt, matcher.Label, matcher.Value)
|
|
if err != nil {
|
|
errs = append(errs, fmt.Errorf("route '%s' has illegal matcher: %w", path, err))
|
|
continue
|
|
}
|
|
result.ObjectMatchers = append(result.ObjectMatchers, m)
|
|
}
|
|
|
|
for idx, route := range r.Routes {
|
|
p := fmt.Sprintf("%s[%d]", path, idx)
|
|
s, err := convertK8sSubRouteToRoute(route, p)
|
|
if len(err) > 0 {
|
|
errs = append(errs, err...)
|
|
} else {
|
|
result.Routes = append(result.Routes, &s)
|
|
}
|
|
}
|
|
return result, errs
|
|
}
|
|
|
|
func optionalPrometheusDurationToString(d *promModel.Duration) *string {
|
|
if d != nil {
|
|
result := d.String()
|
|
return &result
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func parsePrometheusDuration(s *string, callback func(e error)) *promModel.Duration {
|
|
if s == nil || *s == "" {
|
|
return nil
|
|
}
|
|
d, err := promModel.ParseDuration(*s)
|
|
if err != nil {
|
|
callback(err)
|
|
return nil
|
|
}
|
|
return &d
|
|
}
|