grafana/pkg/services/ngalert/migration/models/alertmanager.go

205 lines
7.5 KiB
Go
Raw Normal View History

package models
import (
"strings"
"github.com/prometheus/alertmanager/config"
"github.com/prometheus/alertmanager/dispatch"
"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 {
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
}
// 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
}
}
return am.config
}
func (am *Alertmanager) Match(lset model.LabelSet) []*dispatch.Route {
return dispatch.NewRoute(am.config.AlertmanagerConfig.Route.AsAMRoute(), nil).Match(lset)
}
// AddRoute adds a route to the alertmanager config.
func (am *Alertmanager) AddRoute(route *apiModels.Route) {
if route == nil {
return
}
am.legacyReceiverToRoute[route.Receiver] = route
am.legacyRoute.Routes = append(am.legacyRoute.Routes, route)
am.matcherRoute = dispatch.NewRoute(am.config.AlertmanagerConfig.Route.AsAMRoute(), nil)
}
// AddReceiver adds a receiver to the alertmanager config.
func (am *Alertmanager) AddReceiver(recv *apiModels.PostableGrafanaReceiver) {
if recv == nil {
return
}
am.config.AlertmanagerConfig.Receivers = append(am.config.AlertmanagerConfig.Receivers, &apiModels.PostableApiReceiver{
Receiver: config.Receiver{
Name: recv.Name, // Channel name is unique within an Org.
},
PostableGrafanaReceivers: apiModels.PostableGrafanaReceivers{
GrafanaManagedReceivers: []*apiModels.PostableGrafanaReceiver{recv},
},
})
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
}
// createBaseConfig creates an alertmanager config with the root-level route, default receiver, and nested route
// for migrated channels.
func createBaseConfig() *apiModels.PostableUserConfig {
defaultRoute := createDefaultRoute()
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,
},
},
}
}
// createDefaultRoute creates a default root-level route and associated nested route that will contain all the migrated channels.
func createDefaultRoute() *apiModels.Route {
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,
}
}
// 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,
}
}