2023-12-19 12:25:13 -06:00
|
|
|
package models
|
|
|
|
|
|
|
|
import (
|
2024-01-05 17:19:12 -06:00
|
|
|
"strings"
|
|
|
|
|
2023-12-19 12:25:13 -06:00
|
|
|
"github.com/prometheus/alertmanager/config"
|
2024-01-05 17:19:12 -06:00
|
|
|
"github.com/prometheus/alertmanager/dispatch"
|
2023-12-19 12:25:13 -06:00
|
|
|
"github.com/prometheus/alertmanager/pkg/labels"
|
|
|
|
"github.com/prometheus/common/model"
|
|
|
|
|
|
|
|
apiModels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
|
|
|
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Alertmanager is a helper struct for creating migrated alertmanager configs.
|
|
|
|
type Alertmanager struct {
|
2024-01-05 17:19:12 -06:00
|
|
|
config *apiModels.PostableUserConfig
|
|
|
|
legacyRoute *apiModels.Route
|
|
|
|
legacyReceiverToRoute map[string]*apiModels.Route
|
|
|
|
legacyUIDToReceiver map[string]*apiModels.PostableGrafanaReceiver
|
|
|
|
matcherRoute *dispatch.Route
|
|
|
|
}
|
|
|
|
|
|
|
|
// FromPostableUserConfig creates an Alertmanager from a PostableUserConfig.
|
|
|
|
func FromPostableUserConfig(config *apiModels.PostableUserConfig) *Alertmanager {
|
|
|
|
if config == nil {
|
|
|
|
// No existing amConfig created from a previous migration.
|
|
|
|
config = createBaseConfig()
|
|
|
|
}
|
|
|
|
if config.AlertmanagerConfig.Route == nil {
|
|
|
|
// No existing base route created from a previous migration.
|
|
|
|
config.AlertmanagerConfig.Route = createDefaultRoute()
|
|
|
|
}
|
|
|
|
am := &Alertmanager{
|
|
|
|
config: config,
|
|
|
|
legacyRoute: getOrCreateNestedLegacyRoute(config),
|
|
|
|
legacyReceiverToRoute: make(map[string]*apiModels.Route),
|
|
|
|
legacyUIDToReceiver: config.GetGrafanaReceiverMap(),
|
|
|
|
matcherRoute: dispatch.NewRoute(config.AlertmanagerConfig.Route.AsAMRoute(), nil),
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, r := range am.legacyRoute.Routes {
|
|
|
|
am.legacyReceiverToRoute[r.Receiver] = r
|
|
|
|
}
|
|
|
|
return am
|
2023-12-19 12:25:13 -06:00
|
|
|
}
|
|
|
|
|
2024-01-05 17:19:12 -06:00
|
|
|
// CleanAlertmanager removes the nested legacy route from the base PostableUserConfig if it's empty.
|
|
|
|
func CleanAlertmanager(am *Alertmanager) *apiModels.PostableUserConfig {
|
|
|
|
for i, r := range am.config.AlertmanagerConfig.Route.Routes {
|
|
|
|
if isNestedLegacyRoute(r) && len(r.Routes) == 0 {
|
|
|
|
// Remove empty nested route.
|
|
|
|
am.config.AlertmanagerConfig.Route.Routes = append(am.config.AlertmanagerConfig.Route.Routes[:i], am.config.AlertmanagerConfig.Route.Routes[i+1:]...)
|
|
|
|
return am.config
|
|
|
|
}
|
2023-12-19 12:25:13 -06:00
|
|
|
}
|
2024-01-05 17:19:12 -06:00
|
|
|
return am.config
|
|
|
|
}
|
|
|
|
|
|
|
|
func (am *Alertmanager) Match(lset model.LabelSet) []*dispatch.Route {
|
|
|
|
return dispatch.NewRoute(am.config.AlertmanagerConfig.Route.AsAMRoute(), nil).Match(lset)
|
2023-12-19 12:25:13 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// AddRoute adds a route to the alertmanager config.
|
|
|
|
func (am *Alertmanager) AddRoute(route *apiModels.Route) {
|
2024-01-05 04:37:13 -06:00
|
|
|
if route == nil {
|
|
|
|
return
|
|
|
|
}
|
2024-01-05 17:19:12 -06:00
|
|
|
am.legacyReceiverToRoute[route.Receiver] = route
|
2023-12-19 12:25:13 -06:00
|
|
|
am.legacyRoute.Routes = append(am.legacyRoute.Routes, route)
|
2024-01-05 17:19:12 -06:00
|
|
|
am.matcherRoute = dispatch.NewRoute(am.config.AlertmanagerConfig.Route.AsAMRoute(), nil)
|
2023-12-19 12:25:13 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// AddReceiver adds a receiver to the alertmanager config.
|
2024-01-05 04:37:13 -06:00
|
|
|
func (am *Alertmanager) AddReceiver(recv *apiModels.PostableGrafanaReceiver) {
|
|
|
|
if recv == nil {
|
|
|
|
return
|
|
|
|
}
|
2024-01-05 17:19:12 -06:00
|
|
|
am.config.AlertmanagerConfig.Receivers = append(am.config.AlertmanagerConfig.Receivers, &apiModels.PostableApiReceiver{
|
2024-01-05 04:37:13 -06:00
|
|
|
Receiver: config.Receiver{
|
|
|
|
Name: recv.Name, // Channel name is unique within an Org.
|
|
|
|
},
|
|
|
|
PostableGrafanaReceivers: apiModels.PostableGrafanaReceivers{
|
|
|
|
GrafanaManagedReceivers: []*apiModels.PostableGrafanaReceiver{recv},
|
|
|
|
},
|
|
|
|
})
|
2024-01-05 17:19:12 -06:00
|
|
|
am.legacyUIDToReceiver[recv.UID] = recv
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveContactPointsAndRoutes removes all receivers and routes given legacy channel name.
|
|
|
|
func (am *Alertmanager) RemoveContactPointsAndRoutes(uid string) {
|
|
|
|
if recv, ok := am.legacyUIDToReceiver[uid]; ok {
|
|
|
|
for i, r := range am.config.AlertmanagerConfig.Receivers {
|
|
|
|
if r.Name == recv.Name {
|
|
|
|
am.config.AlertmanagerConfig.Receivers = append(am.config.AlertmanagerConfig.Receivers[:i], am.config.AlertmanagerConfig.Receivers[i+1:]...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't keep receiver and remove all nested routes that reference it.
|
|
|
|
// This will fail validation if the user has created other routes that reference this receiver.
|
|
|
|
// In that case, they must manually delete the added routes.
|
|
|
|
am.RemoveRoutes(recv.Name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveRoutes legacy routes that send to the given receiver.
|
|
|
|
func (am *Alertmanager) RemoveRoutes(recv string) {
|
|
|
|
var keptRoutes []*apiModels.Route
|
|
|
|
for i, route := range am.legacyRoute.Routes {
|
|
|
|
if route.Receiver != recv {
|
|
|
|
keptRoutes = append(keptRoutes, am.legacyRoute.Routes[i])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
delete(am.legacyReceiverToRoute, recv)
|
|
|
|
am.legacyRoute.Routes = keptRoutes
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetLegacyRoute retrieves the legacy route for a given migrated channel UID.
|
|
|
|
func (am *Alertmanager) GetLegacyRoute(recv string) (*apiModels.Route, bool) {
|
|
|
|
route, ok := am.legacyReceiverToRoute[recv]
|
|
|
|
return route, ok
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetReceiver retrieves the receiver for a given UID.
|
|
|
|
func (am *Alertmanager) GetReceiver(uid string) (*apiModels.PostableGrafanaReceiver, bool) {
|
|
|
|
recv, ok := am.legacyUIDToReceiver[uid]
|
|
|
|
return recv, ok
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetContactLabel retrieves the label used to route for a given UID.
|
|
|
|
func (am *Alertmanager) GetContactLabel(uid string) string {
|
|
|
|
if recv, ok := am.GetReceiver(uid); ok {
|
|
|
|
if route, ok := am.GetLegacyRoute(recv.Name); ok {
|
|
|
|
for _, match := range route.ObjectMatchers {
|
|
|
|
if match.Type == labels.MatchEqual && strings.HasPrefix(match.Name, ngmodels.MigratedContactLabelPrefix) {
|
|
|
|
return match.Name
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// getOrCreateNestedLegacyRoute finds or creates the nested route for migrated channels.
|
|
|
|
func getOrCreateNestedLegacyRoute(config *apiModels.PostableUserConfig) *apiModels.Route {
|
|
|
|
for _, r := range config.AlertmanagerConfig.Route.Routes {
|
|
|
|
if isNestedLegacyRoute(r) {
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
}
|
|
|
|
nestedLegacyChannelRoute := createNestedLegacyRoute()
|
|
|
|
// Add new nested route as the first of the top-level routes.
|
|
|
|
config.AlertmanagerConfig.Route.Routes = append([]*apiModels.Route{nestedLegacyChannelRoute}, config.AlertmanagerConfig.Route.Routes...)
|
|
|
|
return nestedLegacyChannelRoute
|
|
|
|
}
|
|
|
|
|
|
|
|
// isNestedLegacyRoute checks whether a route is the nested legacy route for migrated channels.
|
|
|
|
func isNestedLegacyRoute(r *apiModels.Route) bool {
|
|
|
|
return len(r.ObjectMatchers) == 1 && r.ObjectMatchers[0].Name == ngmodels.MigratedUseLegacyChannelsLabel
|
2023-12-19 12:25:13 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// createBaseConfig creates an alertmanager config with the root-level route, default receiver, and nested route
|
|
|
|
// for migrated channels.
|
2024-01-05 17:19:12 -06:00
|
|
|
func createBaseConfig() *apiModels.PostableUserConfig {
|
|
|
|
defaultRoute := createDefaultRoute()
|
2023-12-19 12:25:13 -06:00
|
|
|
return &apiModels.PostableUserConfig{
|
|
|
|
AlertmanagerConfig: apiModels.PostableApiAlertingConfig{
|
|
|
|
Receivers: []*apiModels.PostableApiReceiver{
|
|
|
|
{
|
|
|
|
Receiver: config.Receiver{
|
|
|
|
Name: "autogen-contact-point-default",
|
|
|
|
},
|
|
|
|
PostableGrafanaReceivers: apiModels.PostableGrafanaReceivers{
|
|
|
|
GrafanaManagedReceivers: []*apiModels.PostableGrafanaReceiver{},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Config: apiModels.Config{
|
|
|
|
Route: defaultRoute,
|
|
|
|
},
|
|
|
|
},
|
2024-01-05 17:19:12 -06:00
|
|
|
}
|
2023-12-19 12:25:13 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// createDefaultRoute creates a default root-level route and associated nested route that will contain all the migrated channels.
|
2024-01-05 17:19:12 -06:00
|
|
|
func createDefaultRoute() *apiModels.Route {
|
2023-12-19 12:25:13 -06:00
|
|
|
nestedRoute := createNestedLegacyRoute()
|
|
|
|
return &apiModels.Route{
|
|
|
|
Receiver: "autogen-contact-point-default",
|
|
|
|
Routes: []*apiModels.Route{nestedRoute},
|
|
|
|
GroupByStr: []string{ngmodels.FolderTitleLabel, model.AlertNameLabel}, // To keep parity with pre-migration notifications.
|
|
|
|
RepeatInterval: nil,
|
2024-01-05 17:19:12 -06:00
|
|
|
}
|
2023-12-19 12:25:13 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// createNestedLegacyRoute creates a nested route that will contain all the migrated channels.
|
|
|
|
// This route is matched on the UseLegacyChannelsLabel and mostly exists to keep the migrated channels separate and organized.
|
|
|
|
func createNestedLegacyRoute() *apiModels.Route {
|
|
|
|
mat, _ := labels.NewMatcher(labels.MatchEqual, ngmodels.MigratedUseLegacyChannelsLabel, "true")
|
|
|
|
return &apiModels.Route{
|
|
|
|
ObjectMatchers: apiModels.ObjectMatchers{mat},
|
|
|
|
Continue: true,
|
|
|
|
}
|
|
|
|
}
|