diff --git a/alertobserver/alertobserver.go b/alertobserver/alertobserver.go new file mode 100644 index 0000000000..cf05d9210c --- /dev/null +++ b/alertobserver/alertobserver.go @@ -0,0 +1,36 @@ +// Copyright 2023 Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package alertobserver + +import ( + "github.com/prometheus/alertmanager/types" +) + +const ( + EventAlertReceived string = "received" + EventAlertRejected string = "rejected" + EventAlertAddedToAggrGroup string = "addedAggrGroup" + EventAlertFailedAddToAggrGroup string = "failedAddAggrGroup" + EventAlertPipelineStart string = "pipelineStart" + EventAlertPipelinePassStage string = "pipelinePassStage" + EventAlertMuted string = "muted" + EventAlertSent string = "sent" + EventAlertSendFailed string = "sendFailed" +) + +type AlertEventMeta map[string]interface{} + +type LifeCycleObserver interface { + Observe(event string, alerts []*types.Alert, meta AlertEventMeta) +} diff --git a/alertobserver/testing.go b/alertobserver/testing.go new file mode 100644 index 0000000000..66f774fbb7 --- /dev/null +++ b/alertobserver/testing.go @@ -0,0 +1,46 @@ +// Copyright 2023 Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package alertobserver + +import ( + "sync" + + "github.com/prometheus/alertmanager/types" +) + +type FakeLifeCycleObserver struct { + AlertsPerEvent map[string][]*types.Alert + PipelineStageAlerts map[string][]*types.Alert + MetaPerEvent map[string][]AlertEventMeta + Mtx sync.RWMutex +} + +func (o *FakeLifeCycleObserver) Observe(event string, alerts []*types.Alert, meta AlertEventMeta) { + o.Mtx.Lock() + defer o.Mtx.Unlock() + if event == EventAlertPipelinePassStage { + o.PipelineStageAlerts[meta["stageName"].(string)] = append(o.PipelineStageAlerts[meta["stageName"].(string)], alerts...) + } else { + o.AlertsPerEvent[event] = append(o.AlertsPerEvent[event], alerts...) + } + o.MetaPerEvent[event] = append(o.MetaPerEvent[event], meta) +} + +func NewFakeLifeCycleObserver() *FakeLifeCycleObserver { + return &FakeLifeCycleObserver{ + PipelineStageAlerts: map[string][]*types.Alert{}, + AlertsPerEvent: map[string][]*types.Alert{}, + MetaPerEvent: map[string][]AlertEventMeta{}, + } +} diff --git a/api/api.go b/api/api.go index 6839d2d282..dccbb57d97 100644 --- a/api/api.go +++ b/api/api.go @@ -26,6 +26,7 @@ import ( "github.com/prometheus/common/promslog" "github.com/prometheus/common/route" + "github.com/prometheus/alertmanager/alertobserver" apiv2 "github.com/prometheus/alertmanager/api/v2" "github.com/prometheus/alertmanager/cluster" "github.com/prometheus/alertmanager/config" @@ -33,6 +34,7 @@ import ( "github.com/prometheus/alertmanager/provider" "github.com/prometheus/alertmanager/silence" "github.com/prometheus/alertmanager/types" + "github.com/prometheus/alertmanager/util/callback" ) // API represents all APIs of Alertmanager. @@ -78,7 +80,15 @@ type Options struct { // GroupFunc returns a list of alert groups. The alerts are grouped // according to the current active configuration. Alerts returned are // filtered by the arguments provided to the function. - GroupFunc func(func(*dispatch.Route) bool, func(*types.Alert, time.Time) bool) (dispatch.AlertGroups, map[model.Fingerprint][]string) + GroupFunc func(func(*dispatch.Route) bool, func(*types.Alert, time.Time) bool, func(string) bool) (dispatch.AlertGroups, map[model.Fingerprint][]string) + // GroupInfoFunc returns a list of alert groups information. The alerts are grouped + // according to the current active configuration. This function will not return the alerts inside each group. + GroupInfoFunc func(func(*dispatch.Route) bool) dispatch.AlertGroupInfos + // APICallback define the callback function that each api call will perform before returned. + APICallback callback.Callback + // AlertLCObserver is used to add hooks to the different alert life cycle events. + // If nil then no observer methods will be invoked in the life cycle events. + AlertLCObserver alertobserver.LifeCycleObserver } func (o Options) validate() error { @@ -121,12 +131,15 @@ func New(opts Options) (*API, error) { v2, err := apiv2.NewAPI( opts.Alerts, opts.GroupFunc, + opts.GroupInfoFunc, opts.AlertStatusFunc, opts.GroupMutedFunc, opts.Silences, + opts.APICallback, opts.Peer, l.With("version", "v2"), opts.Registry, + opts.AlertLCObserver, ) if err != nil { return nil, err diff --git a/api/v2/api.go b/api/v2/api.go index 0e29375d40..7173b8eb67 100644 --- a/api/v2/api.go +++ b/api/v2/api.go @@ -14,6 +14,7 @@ package v2 import ( + "context" "errors" "fmt" "log/slog" @@ -32,12 +33,16 @@ import ( "github.com/prometheus/common/version" "github.com/rs/cors" + alertgroupinfolist_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/alertgroupinfolist" + + "github.com/prometheus/alertmanager/alertobserver" "github.com/prometheus/alertmanager/api/metrics" open_api_models "github.com/prometheus/alertmanager/api/v2/models" "github.com/prometheus/alertmanager/api/v2/restapi" "github.com/prometheus/alertmanager/api/v2/restapi/operations" alert_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/alert" alertgroup_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/alertgroup" + alertinfo_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/alertinfo" general_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/general" receiver_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/receiver" silence_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/silence" @@ -50,17 +55,20 @@ import ( "github.com/prometheus/alertmanager/silence" "github.com/prometheus/alertmanager/silence/silencepb" "github.com/prometheus/alertmanager/types" + "github.com/prometheus/alertmanager/util/callback" ) // API represents an Alertmanager API v2. type API struct { - peer cluster.ClusterPeer - silences *silence.Silences - alerts provider.Alerts - alertGroups groupsFn - getAlertStatus getAlertStatusFn - groupMutedFunc groupMutedFunc - uptime time.Time + peer cluster.ClusterPeer + silences *silence.Silences + alerts provider.Alerts + alertGroups groupsFn + alertGroupInfos groupInfosFn + getAlertStatus getAlertStatusFn + groupMutedFunc groupMutedFunc + apiCallback callback.Callback + uptime time.Time // mtx protects alertmanagerConfig, setAlertStatus and route. mtx sync.RWMutex @@ -70,15 +78,17 @@ type API struct { route *dispatch.Route setAlertStatus setAlertStatusFn - logger *slog.Logger - m *metrics.Alerts + logger *slog.Logger + m *metrics.Alerts + alertLCObserver alertobserver.LifeCycleObserver Handler http.Handler } type ( - groupsFn func(func(*dispatch.Route) bool, func(*types.Alert, time.Time) bool) (dispatch.AlertGroups, map[prometheus_model.Fingerprint][]string) + groupsFn func(func(*dispatch.Route) bool, func(*types.Alert, time.Time) bool, func(string) bool) (dispatch.AlertGroups, map[prometheus_model.Fingerprint][]string) groupMutedFunc func(routeID, groupKey string) ([]string, bool) + groupInfosFn func(func(*dispatch.Route) bool) dispatch.AlertGroupInfos getAlertStatusFn func(prometheus_model.Fingerprint) types.AlertStatus setAlertStatusFn func(prometheus_model.LabelSet) ) @@ -87,23 +97,32 @@ type ( func NewAPI( alerts provider.Alerts, gf groupsFn, + gif groupInfosFn, asf getAlertStatusFn, gmf groupMutedFunc, silences *silence.Silences, + apiCallback callback.Callback, peer cluster.ClusterPeer, l *slog.Logger, r prometheus.Registerer, + o alertobserver.LifeCycleObserver, ) (*API, error) { + if apiCallback == nil { + apiCallback = callback.NoopAPICallback{} + } api := API{ - alerts: alerts, - getAlertStatus: asf, - alertGroups: gf, - groupMutedFunc: gmf, - peer: peer, - silences: silences, - logger: l, - m: metrics.NewAlerts(r), - uptime: time.Now(), + alerts: alerts, + getAlertStatus: asf, + alertGroups: gf, + alertGroupInfos: gif, + groupMutedFunc: gmf, + peer: peer, + silences: silences, + apiCallback: apiCallback, + logger: l, + m: metrics.NewAlerts(r), + uptime: time.Now(), + alertLCObserver: o, } // Load embedded swagger file. @@ -125,8 +144,10 @@ func NewAPI( } openAPI.AlertGetAlertsHandler = alert_ops.GetAlertsHandlerFunc(api.getAlertsHandler) + openAPI.AlertinfoGetAlertInfosHandler = alertinfo_ops.GetAlertInfosHandlerFunc(api.getAlertInfosHandler) openAPI.AlertPostAlertsHandler = alert_ops.PostAlertsHandlerFunc(api.postAlertsHandler) openAPI.AlertgroupGetAlertGroupsHandler = alertgroup_ops.GetAlertGroupsHandlerFunc(api.getAlertGroupsHandler) + openAPI.AlertgroupinfolistGetAlertGroupInfoListHandler = alertgroupinfolist_ops.GetAlertGroupInfoListHandlerFunc(api.getAlertGroupInfoListHandler) openAPI.GeneralGetStatusHandler = general_ops.GetStatusHandlerFunc(api.getStatusHandler) openAPI.ReceiverGetReceiversHandler = receiver_ops.GetReceiversHandlerFunc(api.getReceiversHandler) openAPI.SilenceDeleteSilenceHandler = silence_ops.DeleteSilenceHandlerFunc(api.deleteSilenceHandler) @@ -238,10 +259,7 @@ func (api *API) getReceiversHandler(params receiver_ops.GetReceiversParams) midd func (api *API) getAlertsHandler(params alert_ops.GetAlertsParams) middleware.Responder { var ( receiverFilter *regexp.Regexp - // Initialize result slice to prevent api returning `null` when there - // are no alerts present - res = open_api_models.GettableAlerts{} - ctx = params.HTTPRequest.Context() + ctx = params.HTTPRequest.Context() logger = api.requestLogger(params.HTTPRequest) ) @@ -264,50 +282,87 @@ func (api *API) getAlertsHandler(params alert_ops.GetAlertsParams) middleware.Re } } - alerts := api.alerts.GetPending() - defer alerts.Close() - alertFilter := api.alertFilter(matchers, *params.Silenced, *params.Inhibited, *params.Active) - now := time.Now() + alerts, err := api.getAlerts(ctx, receiverFilter, alertFilter) + if err != nil { + logger.Error("Failed to get alerts", "err", err) + return alert_ops.NewGetAlertsInternalServerError().WithPayload(err.Error()) + } - api.mtx.RLock() - for a := range alerts.Next() { - if err = alerts.Err(); err != nil { - break - } - if err = ctx.Err(); err != nil { - break - } + callbackRes, err := api.apiCallback.V2GetAlertsCallback(alerts) + if err != nil { + logger.Error("Failed to call api callback", "err", err) + return alert_ops.NewGetAlertsInternalServerError().WithPayload(err.Error()) + } - routes := api.route.Match(a.Labels) - receivers := make([]string, 0, len(routes)) - for _, r := range routes { - receivers = append(receivers, r.RouteOpts.Receiver) - } + return alert_ops.NewGetAlertsOK().WithPayload(callbackRes) +} - if receiverFilter != nil && !receiversMatchFilter(receivers, receiverFilter) { - continue - } +func (api *API) getAlertInfosHandler(params alertinfo_ops.GetAlertInfosParams) middleware.Responder { + var ( + alerts open_api_models.GettableAlerts + receiverFilter *regexp.Regexp + ctx = params.HTTPRequest.Context() - if !alertFilter(a, now) { - continue + logger = api.requestLogger(params.HTTPRequest) + ) + + matchers, err := parseFilter(params.Filter) + if err != nil { + logger.Debug("Failed to parse matchers", "err", err) + return alertinfo_ops.NewGetAlertInfosBadRequest().WithPayload(err.Error()) + } + + if params.Receiver != nil { + receiverFilter, err = regexp.Compile("^(?:" + *params.Receiver + ")$") + if err != nil { + logger.Debug("Failed to compile receiver regex", "err", err) + return alertinfo_ops. + NewGetAlertInfosBadRequest(). + WithPayload( + fmt.Sprintf("failed to parse receiver param: %v", err.Error()), + ) } + } - alert := AlertToOpenAPIAlert(a, api.getAlertStatus(a.Fingerprint()), receivers, nil) + if err = validateMaxResult(params.MaxResults); err != nil { + logger.Error("Failed to parse MaxResults parameter", "err", err) + return alertinfo_ops. + NewGetAlertInfosBadRequest(). + WithPayload( + fmt.Sprintf("failed to parse MaxResults param: %v", *params.MaxResults), + ) + } - res = append(res, alert) + if err = validateAlertInfoNextToken(params.NextToken); err != nil { + logger.Error("Failed to parse NextToken parameter", "err", err) + return alertinfo_ops. + NewGetAlertInfosBadRequest(). + WithPayload( + fmt.Sprintf("failed to parse NextToken param: %v", *params.NextToken), + ) } - api.mtx.RUnlock() + alertFilter := api.alertFilter(matchers, *params.Silenced, *params.Inhibited, *params.Active) + groupIdsFilter := api.groupIDFilter(params.GroupID) + if len(params.GroupID) > 0 { + alerts, err = api.getAlertsFromAlertGroup(ctx, receiverFilter, alertFilter, groupIdsFilter) + } else { + alerts, err = api.getAlerts(ctx, receiverFilter, alertFilter) + } if err != nil { logger.Error("Failed to get alerts", "err", err) - return alert_ops.NewGetAlertsInternalServerError().WithPayload(err.Error()) + return alertinfo_ops.NewGetAlertInfosInternalServerError().WithPayload(err.Error()) } - sort.Slice(res, func(i, j int) bool { - return *res[i].Fingerprint < *res[j].Fingerprint - }) - return alert_ops.NewGetAlertsOK().WithPayload(res) + returnAlertInfos, nextItem := AlertInfosTruncate(alerts, params.MaxResults, params.NextToken) + + response := &open_api_models.GettableAlertInfos{ + Alerts: returnAlertInfos, + NextToken: nextItem, + } + + return alertinfo_ops.NewGetAlertInfosOK().WithPayload(response) } func (api *API) postAlertsHandler(params alert_ops.PostAlertsParams) middleware.Responder { @@ -355,12 +410,20 @@ func (api *API) postAlertsHandler(params alert_ops.PostAlertsParams) middleware. if err := a.Validate(); err != nil { validationErrs.Add(err) api.m.Invalid().Inc() + if api.alertLCObserver != nil { + m := alertobserver.AlertEventMeta{"msg": err.Error()} + api.alertLCObserver.Observe(alertobserver.EventAlertRejected, []*types.Alert{a}, m) + } continue } validAlerts = append(validAlerts, a) } if err := api.alerts.Put(validAlerts...); err != nil { logger.Error("Failed to create alerts", "err", err) + if api.alertLCObserver != nil { + m := alertobserver.AlertEventMeta{"msg": err.Error()} + api.alertLCObserver.Observe(alertobserver.EventAlertRejected, validAlerts, m) + } return alert_ops.NewPostAlertsInternalServerError().WithPayload(err.Error()) } @@ -368,6 +431,9 @@ func (api *API) postAlertsHandler(params alert_ops.PostAlertsParams) middleware. logger.Error("Failed to validate alerts", "err", validationErrs.Error()) return alert_ops.NewPostAlertsBadRequest().WithPayload(validationErrs.Error()) } + if api.alertLCObserver != nil { + api.alertLCObserver.Observe(alertobserver.EventAlertReceived, validAlerts, alertobserver.AlertEventMeta{}) + } return alert_ops.NewPostAlertsOK() } @@ -394,18 +460,10 @@ func (api *API) getAlertGroupsHandler(params alertgroup_ops.GetAlertGroupsParams } } - rf := func(receiverFilter *regexp.Regexp) func(r *dispatch.Route) bool { - return func(r *dispatch.Route) bool { - receiver := r.RouteOpts.Receiver - if receiverFilter != nil && !receiverFilter.MatchString(receiver) { - return false - } - return true - } - }(receiverFilter) - + rf := api.routeFilter(receiverFilter) af := api.alertFilter(matchers, *params.Silenced, *params.Inhibited, *params.Active) - alertGroups, allReceivers := api.alertGroups(rf, af) + gf := api.groupIDFilter([]string{}) + alertGroups, allReceivers := api.alertGroups(rf, af, gf) res := make(open_api_models.AlertGroups, 0, len(alertGroups)) @@ -431,7 +489,111 @@ func (api *API) getAlertGroupsHandler(params alertgroup_ops.GetAlertGroupsParams res = append(res, ag) } - return alertgroup_ops.NewGetAlertGroupsOK().WithPayload(res) + callbackRes, err := api.apiCallback.V2GetAlertGroupsCallback(res) + if err != nil { + logger.Error("Failed to call api callback", "err", err) + return alertgroup_ops.NewGetAlertGroupsInternalServerError().WithPayload(err.Error()) + } + + return alertgroup_ops.NewGetAlertGroupsOK().WithPayload(callbackRes) +} + +func (api *API) getAlertGroupInfoListHandler(params alertgroupinfolist_ops.GetAlertGroupInfoListParams) middleware.Responder { + logger := api.requestLogger(params.HTTPRequest) + + var receiverFilter *regexp.Regexp + var err error + if params.Receiver != nil { + receiverFilter, err = regexp.Compile("^(?:" + *params.Receiver + ")$") + if err != nil { + logger.Error("Failed to compile receiver regex", "err", err) + return alertgroupinfolist_ops. + NewGetAlertGroupInfoListBadRequest(). + WithPayload( + fmt.Sprintf("failed to parse receiver param: %v", err.Error()), + ) + } + } + + rf := func(receiverFilter *regexp.Regexp) func(r *dispatch.Route) bool { + return func(r *dispatch.Route) bool { + receiver := r.RouteOpts.Receiver + if receiverFilter != nil && !receiverFilter.MatchString(receiver) { + return false + } + return true + } + }(receiverFilter) + + if err = validateNextToken(params.NextToken); err != nil { + logger.Error("Failed to parse NextToken parameter", "err", err) + return alertgroupinfolist_ops. + NewGetAlertGroupInfoListBadRequest(). + WithPayload( + fmt.Sprintf("failed to parse NextToken param: %v", *params.NextToken), + ) + } + + if err = validateMaxResult(params.MaxResults); err != nil { + logger.Error("Failed to parse MaxResults parameter", "err", err) + return alertgroupinfolist_ops. + NewGetAlertGroupInfoListBadRequest(). + WithPayload( + fmt.Sprintf("failed to parse MaxResults param: %v", *params.MaxResults), + ) + } + + ags := api.alertGroupInfos(rf) + alertGroupInfos := make([]*open_api_models.AlertGroupInfo, 0, len(ags)) + for _, alertGroup := range ags { + + // Skip the aggregation group if the next token is set and hasn't arrived the nextToken item yet. + if params.NextToken != nil && *params.NextToken >= alertGroup.ID { + continue + } + + ag := &open_api_models.AlertGroupInfo{ + Receiver: &open_api_models.Receiver{Name: &alertGroup.Receiver}, + Labels: ModelLabelSetToAPILabelSet(alertGroup.Labels), + ID: &alertGroup.ID, + } + alertGroupInfos = append(alertGroupInfos, ag) + } + + returnAlertGroupInfos, nextItem := AlertGroupInfoListTruncate(alertGroupInfos, params.MaxResults) + + response := &open_api_models.AlertGroupInfoList{ + AlertGroupInfoList: returnAlertGroupInfos, + NextToken: nextItem, + } + + return alertgroupinfolist_ops.NewGetAlertGroupInfoListOK().WithPayload(response) +} + +func (api *API) groupIDFilter(groupIDsFilter []string) func(groupId string) bool { + return func(groupId string) bool { + if len(groupIDsFilter) <= 0 { + return true + } + for _, groupIDFilter := range groupIDsFilter { + if groupIDFilter == groupId { + return true + } + } + return false + } +} + +func (api *API) routeFilter(receiverFilter *regexp.Regexp) func(r *dispatch.Route) bool { + return func(receiverFilter *regexp.Regexp) func(r *dispatch.Route) bool { + return func(r *dispatch.Route) bool { + receiver := r.RouteOpts.Receiver + if receiverFilter != nil && !receiverFilter.MatchString(receiver) { + return false + } + return true + } + }(receiverFilter) } func (api *API) alertFilter(matchers []*labels.Matcher, silenced, inhibited, active bool) func(a *types.Alert, now time.Time) bool { @@ -723,3 +885,104 @@ func getSwaggerSpec() (*loads.Document, *analysis.Spec, error) { swaggerSpecAnalysisCache = analysis.New(swaggerSpec.Spec()) return swaggerSpec, swaggerSpecAnalysisCache, nil } + +func (api *API) getAlertsFromAlertGroup(ctx context.Context, receiverFilter *regexp.Regexp, alertFilter func(a *types.Alert, now time.Time) bool, groupIdsFilter func(groupId string) bool) (open_api_models.GettableAlerts, error) { + res := open_api_models.GettableAlerts{} + routeFilter := api.routeFilter(receiverFilter) + alertGroups, allReceivers := api.alertGroups(routeFilter, alertFilter, groupIdsFilter) + for _, alertGroup := range alertGroups { + for _, alert := range alertGroup.Alerts { + if err := ctx.Err(); err != nil { + break + } + fp := alert.Fingerprint() + receivers := allReceivers[fp] + status := api.getAlertStatus(fp) + apiAlert := AlertToOpenAPIAlert(alert, status, receivers, nil) + res = append(res, apiAlert) + } + } + + sort.Slice(res, func(i, j int) bool { + return *res[i].Fingerprint < *res[j].Fingerprint + }) + + return res, nil +} + +func (api *API) getAlerts(ctx context.Context, receiverFilter *regexp.Regexp, alertFilter func(a *types.Alert, now time.Time) bool) (open_api_models.GettableAlerts, error) { + var err error + res := open_api_models.GettableAlerts{} + + alerts := api.alerts.GetPending() + defer alerts.Close() + + now := time.Now() + + api.mtx.RLock() + for a := range alerts.Next() { + if err = alerts.Err(); err != nil { + break + } + if err = ctx.Err(); err != nil { + break + } + + routes := api.route.Match(a.Labels) + receivers := make([]string, 0, len(routes)) + for _, r := range routes { + receivers = append(receivers, r.RouteOpts.Receiver) + } + + if receiverFilter != nil && !receiversMatchFilter(receivers, receiverFilter) { + continue + } + + if !alertFilter(a, now) { + continue + } + + alert := AlertToOpenAPIAlert(a, api.getAlertStatus(a.Fingerprint()), receivers, nil) + + res = append(res, alert) + } + api.mtx.RUnlock() + + if err != nil { + return res, err + } + sort.Slice(res, func(i, j int) bool { + return *res[i].Fingerprint < *res[j].Fingerprint + }) + + return res, nil +} + +func validateMaxResult(maxItem *int64) error { + if maxItem != nil { + if *maxItem < 0 { + return errors.New("the maxItem need to be larger than or equal to 0") + } + } + return nil +} + +func validateAlertInfoNextToken(nextToken *string) error { + if nextToken != nil { + _, err := prometheus_model.ParseFingerprint(*nextToken) + if err != nil { + return err + } + } + return nil +} + +func validateNextToken(nextToken *string) error { + if nextToken != nil { + match, _ := regexp.MatchString("^[a-fA-F0-9]{40}$", *nextToken) + if !match { + return fmt.Errorf("invalid nextToken: %s", *nextToken) + } + } + return nil +} diff --git a/api/v2/api_test.go b/api/v2/api_test.go index 4baa4bdef4..08f06a11c1 100644 --- a/api/v2/api_test.go +++ b/api/v2/api_test.go @@ -17,6 +17,9 @@ import ( "bytes" "encoding/json" "fmt" + + "github.com/prometheus/client_golang/prometheus" + "io" "net/http" "net/http/httptest" @@ -31,6 +34,15 @@ import ( "github.com/prometheus/common/promslog" "github.com/stretchr/testify/require" + "github.com/prometheus/alertmanager/alertobserver" + "github.com/prometheus/alertmanager/api/metrics" + alert_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/alert" + alertgroup_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/alertgroup" + alertgroupinfolist_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/alertgroupinfolist" + alert_info_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/alertinfo" + "github.com/prometheus/alertmanager/dispatch" + "github.com/prometheus/alertmanager/util/callback" + open_api_models "github.com/prometheus/alertmanager/api/v2/models" general_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/general" receiver_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/receiver" @@ -121,6 +133,130 @@ func gettableSilence(id, state string, } } +func TestGetAlertGroupInfosHandler(t *testing.T) { + aginfos := dispatch.AlertGroupInfos{ + &dispatch.AlertGroupInfo{ + Labels: model.LabelSet{ + "alertname": "TestingAlert", + "service": "api", + }, + Receiver: "testing", + ID: "478b4114226224a35910d449fdba8186ebfb441f", + }, + &dispatch.AlertGroupInfo{ + Labels: model.LabelSet{ + "alertname": "HighErrorRate", + "service": "api", + "cluster": "bb", + }, + Receiver: "prod", + ID: "7f4084a078a3fe29d6de82fad15af8f1411e803f", + }, + &dispatch.AlertGroupInfo{ + Labels: model.LabelSet{ + "alertname": "OtherAlert", + }, + Receiver: "prod", + ID: "d525244929240cbdb75a497913c1890ab8de1962", + }, + &dispatch.AlertGroupInfo{ + Labels: model.LabelSet{ + "alertname": "HighErrorRate", + "service": "api", + "cluster": "aa", + }, + Receiver: "prod", + ID: "d73984d43949112ae1ea59dcc5af4af7b630a5b1", + }, + } + for _, tc := range []struct { + maxResult *int64 + nextToken *string + body string + expectedCode int + }{ + // Invalid next token. + { + convertIntToPointerInt64(int64(1)), + convertStringToPointer("$$$"), + `failed to parse NextToken param: $$$`, + 400, + }, + // Invalid next token. + { + convertIntToPointerInt64(int64(1)), + convertStringToPointer("1234s"), + `failed to parse NextToken param: 1234s`, + 400, + }, + // Invalid MaxResults. + { + convertIntToPointerInt64(int64(-1)), + convertStringToPointer("478b4114226224a35910d449fdba8186ebfb441f"), + `failed to parse MaxResults param: -1`, + 400, + }, + // One item to return, no next token. + { + convertIntToPointerInt64(int64(1)), + nil, + `{"alertGroupInfoList":[{"id":"478b4114226224a35910d449fdba8186ebfb441f","labels":{"alertname":"TestingAlert","service":"api"},"receiver":{"name":"testing"}}],"nextToken":"478b4114226224a35910d449fdba8186ebfb441f"}`, + 200, + }, + // One item to return, has next token. + { + convertIntToPointerInt64(int64(1)), + convertStringToPointer("478b4114226224a35910d449fdba8186ebfb441f"), + `{"alertGroupInfoList":[{"id":"7f4084a078a3fe29d6de82fad15af8f1411e803f","labels":{"alertname":"HighErrorRate","cluster":"bb","service":"api"},"receiver":{"name":"prod"}}],"nextToken":"7f4084a078a3fe29d6de82fad15af8f1411e803f"}`, + 200, + }, + // Five item to return, has next token. + { + convertIntToPointerInt64(int64(5)), + convertStringToPointer("7f4084a078a3fe29d6de82fad15af8f1411e803f"), + `{"alertGroupInfoList":[{"id":"d525244929240cbdb75a497913c1890ab8de1962","labels":{"alertname":"OtherAlert"},"receiver":{"name":"prod"}},{"id":"d73984d43949112ae1ea59dcc5af4af7b630a5b1","labels":{"alertname":"HighErrorRate","cluster":"aa","service":"api"},"receiver":{"name":"prod"}}]}`, + 200, + }, + // Return all results. + { + nil, + nil, + `{"alertGroupInfoList":[{"id":"478b4114226224a35910d449fdba8186ebfb441f","labels":{"alertname":"TestingAlert","service":"api"},"receiver":{"name":"testing"}},{"id":"7f4084a078a3fe29d6de82fad15af8f1411e803f","labels":{"alertname":"HighErrorRate","cluster":"bb","service":"api"},"receiver":{"name":"prod"}},{"id":"d525244929240cbdb75a497913c1890ab8de1962","labels":{"alertname":"OtherAlert"},"receiver":{"name":"prod"}},{"id":"d73984d43949112ae1ea59dcc5af4af7b630a5b1","labels":{"alertname":"HighErrorRate","cluster":"aa","service":"api"},"receiver":{"name":"prod"}}]}`, + 200, + }, + // return 0 result + { + convertIntToPointerInt64(int64(0)), + nil, + `{"alertGroupInfoList":[]}`, + 200, + }, + } { + api := API{ + uptime: time.Now(), + alertGroupInfos: func(f func(*dispatch.Route) bool) dispatch.AlertGroupInfos { + return aginfos + }, + logger: promslog.NewNopLogger(), + } + r, err := http.NewRequest("GET", "/api/v2/alertgroups", nil) + require.NoError(t, err) + + w := httptest.NewRecorder() + p := runtime.TextProducer() + responder := api.getAlertGroupInfoListHandler(alertgroupinfolist_ops.GetAlertGroupInfoListParams{ + MaxResults: tc.maxResult, + NextToken: tc.nextToken, + HTTPRequest: r, + }) + responder.WriteResponse(w, p) + body, _ := io.ReadAll(w.Result().Body) + + require.Equal(t, tc.expectedCode, w.Code) + require.Equal(t, tc.body, string(body)) + } +} + func TestGetSilencesHandler(t *testing.T) { updateTime := "2019-01-01T12:00:00+00:00" silences := []*open_api_models.GettableSilence{ @@ -584,3 +720,574 @@ receivers: require.Equal(t, tc.body, string(body)) } } + +func TestListAlertsHandler(t *testing.T) { + now := time.Now() + alerts := []*types.Alert{ + { + Alert: model.Alert{ + Labels: model.LabelSet{"state": "active", "alertname": "alert1"}, + StartsAt: now.Add(-time.Minute), + }, + }, + { + Alert: model.Alert{ + Labels: model.LabelSet{"state": "unprocessed", "alertname": "alert2"}, + StartsAt: now.Add(-time.Minute), + }, + }, + { + Alert: model.Alert{ + Labels: model.LabelSet{"state": "suppressed", "silenced_by": "abc", "alertname": "alert3"}, + StartsAt: now.Add(-time.Minute), + }, + }, + { + Alert: model.Alert{ + Labels: model.LabelSet{"state": "suppressed", "inhibited_by": "abc", "alertname": "alert4"}, + StartsAt: now.Add(-time.Minute), + }, + }, + { + Alert: model.Alert{ + Labels: model.LabelSet{"alertname": "alert5"}, + StartsAt: now.Add(-2 * time.Minute), + EndsAt: now.Add(-time.Minute), + }, + }, + } + + for _, tc := range []struct { + name string + booleanParams map[string]*bool + expectedCode int + anames []string + callback callback.Callback + }{ + { + "no call back, no filter", + map[string]*bool{}, + 200, + []string{"alert1", "alert2", "alert3", "alert4"}, + callback.NoopAPICallback{}, + }, + { + "callback: only return 1 alert, no filter", + map[string]*bool{}, + 200, + []string{"alert1"}, + limitNumberOfAlertsReturnedCallback{limit: 1}, + }, + { + "callback: only return 3 alert, no filter", + map[string]*bool{}, + 200, + []string{"alert1", "alert2", "alert3"}, + limitNumberOfAlertsReturnedCallback{limit: 3}, + }, + { + "no filter", + map[string]*bool{}, + 200, + []string{"alert1", "alert2", "alert3", "alert4"}, + callback.NoopAPICallback{}, + }, + { + "status filter", + map[string]*bool{"active": BoolPointer(true), "silenced": BoolPointer(true), "inhibited": BoolPointer(true)}, + 200, + []string{"alert1", "alert2", "alert3", "alert4"}, + callback.NoopAPICallback{}, + }, + { + "status filter - active false", + map[string]*bool{"active": BoolPointer(false), "silenced": BoolPointer(true), "inhibited": BoolPointer(true)}, + 200, + []string{"alert2", "alert3", "alert4"}, + callback.NoopAPICallback{}, + }, + { + "status filter - silenced false", + map[string]*bool{"active": BoolPointer(true), "silenced": BoolPointer(false), "inhibited": BoolPointer(true)}, + 200, + []string{"alert1", "alert2", "alert4"}, + callback.NoopAPICallback{}, + }, + { + "status filter - inhibited false", + map[string]*bool{"active": BoolPointer(true), "unprocessed": BoolPointer(true), "silenced": BoolPointer(true), "inhibited": BoolPointer(false)}, + 200, + []string{"alert1", "alert2", "alert3"}, + callback.NoopAPICallback{}, + }, + } { + t.Run(tc.name, func(t *testing.T) { + alertsProvider := newFakeAlerts(alerts) + api := API{ + uptime: time.Now(), + getAlertStatus: newGetAlertStatus(alertsProvider), + logger: promslog.NewNopLogger(), + apiCallback: tc.callback, + alerts: alertsProvider, + setAlertStatus: func(model.LabelSet) {}, + } + api.route = dispatch.NewRoute(&config.Route{Receiver: "def-receiver"}, nil) + r, err := http.NewRequest("GET", "/api/v2/alerts", nil) + require.NoError(t, err) + + w := httptest.NewRecorder() + p := runtime.TextProducer() + silence := tc.booleanParams["silenced"] + if silence == nil { + silence = BoolPointer(true) + } + inhibited := tc.booleanParams["inhibited"] + if inhibited == nil { + inhibited = BoolPointer(true) + } + active := tc.booleanParams["active"] + if active == nil { + active = BoolPointer(true) + } + responder := api.getAlertsHandler(alert_ops.GetAlertsParams{ + HTTPRequest: r, + Silenced: silence, + Inhibited: inhibited, + Active: active, + }) + responder.WriteResponse(w, p) + body, _ := io.ReadAll(w.Result().Body) + + require.Equal(t, tc.expectedCode, w.Code) + retAlerts := open_api_models.GettableAlerts{} + err = json.Unmarshal(body, &retAlerts) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + anames := []string{} + for _, a := range retAlerts { + name, ok := a.Labels["alertname"] + if ok { + anames = append(anames, string(name)) + } + } + require.Equal(t, tc.anames, anames) + }) + } +} + +func TestGetAlertGroupsHandler(t *testing.T) { + var startAt time.Time + alerts := []*types.Alert{ + { + Alert: model.Alert{ + Labels: model.LabelSet{"state": "active", "alertname": "alert1"}, + StartsAt: startAt, + }, + }, + { + Alert: model.Alert{ + Labels: model.LabelSet{"state": "unprocessed", "alertname": "alert2"}, + StartsAt: startAt, + }, + }, + } + aginfos := dispatch.AlertGroups{ + &dispatch.AlertGroup{ + Labels: model.LabelSet{ + "alertname": "TestingAlert", + }, + Receiver: "testing", + Alerts: alerts[:1], + }, + &dispatch.AlertGroup{ + Labels: model.LabelSet{ + "alertname": "HighErrorRate", + }, + Receiver: "prod", + Alerts: alerts[:2], + }, + } + for _, tc := range []struct { + name string + numberOfAG int + expectedCode int + callback callback.Callback + }{ + { + "no call back", + 2, + 200, + callback.NoopAPICallback{}, + }, + { + "callback: only return 1 alert group", + 1, + 200, + limitNumberOfAlertsReturnedCallback{limit: 1}, + }, + { + "callback: only return 2 alert group", + 2, + 200, + limitNumberOfAlertsReturnedCallback{limit: 2}, + }, + } { + t.Run(tc.name, func(t *testing.T) { + api := API{ + uptime: time.Now(), + alertGroups: func(f func(*dispatch.Route) bool, f2 func(*types.Alert, time.Time) bool, f3 func(string) bool) (dispatch.AlertGroups, map[model.Fingerprint][]string) { + return aginfos, nil + }, + getAlertStatus: getAlertStatus, + logger: promslog.NewNopLogger(), + apiCallback: tc.callback, + } + r, err := http.NewRequest("GET", "/api/v2/alertgroups", nil) + require.NoError(t, err) + + w := httptest.NewRecorder() + p := runtime.TextProducer() + silence := false + inhibited := false + active := true + responder := api.getAlertGroupsHandler(alertgroup_ops.GetAlertGroupsParams{ + HTTPRequest: r, + Silenced: &silence, + Inhibited: &inhibited, + Active: &active, + }) + responder.WriteResponse(w, p) + body, _ := io.ReadAll(w.Result().Body) + + require.Equal(t, tc.expectedCode, w.Code) + retAlertGroups := open_api_models.AlertGroups{} + err = json.Unmarshal(body, &retAlertGroups) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + require.Equal(t, tc.numberOfAG, len(retAlertGroups)) + }) + } +} + +func TestListAlertInfosHandler(t *testing.T) { + now := time.Now() + alerts := []*types.Alert{ + { + Alert: model.Alert{ + Labels: model.LabelSet{"state": "active", "alertname": "alert1"}, + StartsAt: now.Add(-time.Minute), + }, + }, + { + Alert: model.Alert{ + Labels: model.LabelSet{"state": "unprocessed", "alertname": "alert2"}, + StartsAt: now.Add(-time.Minute), + }, + }, + { + Alert: model.Alert{ + Labels: model.LabelSet{"state": "suppressed", "silenced_by": "abc", "alertname": "alert3"}, + StartsAt: now.Add(-time.Minute), + }, + }, + { + Alert: model.Alert{ + Labels: model.LabelSet{"state": "suppressed", "inhibited_by": "abc", "alertname": "alert4"}, + StartsAt: now.Add(-time.Minute), + }, + }, + } + ags := dispatch.AlertGroups{ + &dispatch.AlertGroup{ + Labels: model.LabelSet{ + "alertname": "TestingAlert", + "service": "api", + }, + Receiver: "testing", + Alerts: []*types.Alert{alerts[0], alerts[1]}, + }, + &dispatch.AlertGroup{ + Labels: model.LabelSet{ + "alertname": "HighErrorRate", + "service": "api", + "cluster": "bb", + }, + Receiver: "prod", + Alerts: []*types.Alert{alerts[2], alerts[3]}, + }, + } + + for _, tc := range []struct { + name string + booleanParams map[string]*bool + groupsFilter []string + expectedCode int + maxResult *int64 + nextToken *string + anames []string + expectNextToken string + }{ + { + "no filter", + map[string]*bool{}, + []string{}, + 200, + nil, + nil, + []string{"alert1", "alert2", "alert3", "alert4"}, + "", + }, + { + "status filter", + map[string]*bool{"active": BoolPointer(true), "silenced": BoolPointer(true), "inhibited": BoolPointer(true)}, + []string{}, + 200, + nil, + nil, + []string{"alert1", "alert2", "alert3", "alert4"}, + "", + }, + { + "status filter - active false", + map[string]*bool{"active": BoolPointer(false), "silenced": BoolPointer(true), "inhibited": BoolPointer(true)}, + []string{}, + 200, + nil, + nil, + []string{"alert2", "alert3", "alert4"}, + "", + }, + { + "status filter - silenced false", + map[string]*bool{"active": BoolPointer(true), "silenced": BoolPointer(false), "inhibited": BoolPointer(true)}, + []string{}, + 200, + nil, + nil, + []string{"alert1", "alert2", "alert4"}, + "", + }, + { + "status filter - inhibited false", + map[string]*bool{"active": BoolPointer(true), "unprocessed": BoolPointer(true), "silenced": BoolPointer(true), "inhibited": BoolPointer(false)}, + []string{}, + 200, + nil, + nil, + []string{"alert1", "alert2", "alert3"}, + "", + }, + { + "group filter", + map[string]*bool{"active": BoolPointer(true), "unprocessed": BoolPointer(true), "silenced": BoolPointer(true), "inhibited": BoolPointer(true)}, + []string{"123"}, + 200, + nil, + nil, + []string{"alert1", "alert2", "alert3", "alert4"}, + "", + }, + { + "MaxResults - only 1 alert return", + map[string]*bool{}, + []string{}, + 200, + convertIntToPointerInt64(int64(1)), + nil, + []string{"alert1"}, + alerts[0].Fingerprint().String(), + }, + { + "MaxResults - 0 alert return", + map[string]*bool{}, + []string{}, + 200, + convertIntToPointerInt64(int64(0)), + nil, + []string{}, + "", + }, + { + "MaxResults - all alert return", + map[string]*bool{}, + []string{}, + 200, + convertIntToPointerInt64(int64(8)), + nil, + []string{"alert1", "alert2", "alert3", "alert4"}, + "", + }, + { + "MaxResults - has begin next token, max 2 alerts", + map[string]*bool{}, + []string{}, + 200, + convertIntToPointerInt64(int64(2)), + convertStringToPointer(alerts[0].Fingerprint().String()), + []string{"alert2", "alert3"}, + alerts[2].Fingerprint().String(), + }, + { + "MaxResults - has begin next token, max 2 alerts, no response next token", + map[string]*bool{}, + []string{}, + 200, + convertIntToPointerInt64(int64(2)), + convertStringToPointer(alerts[1].Fingerprint().String()), + []string{"alert3", "alert4"}, + "", + }, + } { + t.Run(tc.name, func(t *testing.T) { + alertsProvider := newFakeAlerts(alerts) + api := API{ + uptime: time.Now(), + getAlertStatus: newGetAlertStatus(alertsProvider), + logger: promslog.NewNopLogger(), + alerts: alertsProvider, + alertGroups: func(f func(*dispatch.Route) bool, f2 func(*types.Alert, time.Time) bool, f3 func(string) bool) (dispatch.AlertGroups, map[model.Fingerprint][]string) { + return ags, nil + }, + setAlertStatus: func(model.LabelSet) {}, + } + api.route = dispatch.NewRoute(&config.Route{Receiver: "def-receiver"}, nil) + r, err := http.NewRequest("GET", "/api/v2/alertinfos", nil) + require.NoError(t, err) + + w := httptest.NewRecorder() + p := runtime.TextProducer() + silence := tc.booleanParams["silenced"] + if silence == nil { + silence = BoolPointer(true) + } + inhibited := tc.booleanParams["inhibited"] + if inhibited == nil { + inhibited = BoolPointer(true) + } + active := tc.booleanParams["active"] + if active == nil { + active = BoolPointer(true) + } + responder := api.getAlertInfosHandler(alert_info_ops.GetAlertInfosParams{ + HTTPRequest: r, + Silenced: silence, + Inhibited: inhibited, + Active: active, + GroupID: tc.groupsFilter, + MaxResults: tc.maxResult, + NextToken: tc.nextToken, + }) + responder.WriteResponse(w, p) + body, _ := io.ReadAll(w.Result().Body) + + require.Equal(t, tc.expectedCode, w.Code) + response := open_api_models.GettableAlertInfos{} + err = json.Unmarshal(body, &response) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + anames := []string{} + for _, a := range response.Alerts { + name, ok := a.Labels["alertname"] + if ok { + anames = append(anames, string(name)) + } + } + require.Equal(t, tc.anames, anames) + require.Equal(t, tc.expectNextToken, response.NextToken) + }) + } +} + +func TestPostAlertHandler(t *testing.T) { + now := time.Now() + for i, tc := range []struct { + start, end time.Time + err bool + code int + }{ + {time.Time{}, time.Time{}, false, 200}, + {now, time.Time{}, false, 200}, + {time.Time{}, now.Add(time.Duration(-1) * time.Second), false, 200}, + {time.Time{}, now, false, 200}, + {time.Time{}, now.Add(time.Duration(1) * time.Second), false, 200}, + {now.Add(time.Duration(-2) * time.Second), now.Add(time.Duration(-1) * time.Second), false, 200}, + {now.Add(time.Duration(1) * time.Second), now.Add(time.Duration(2) * time.Second), false, 200}, + {now.Add(time.Duration(1) * time.Second), now, false, 400}, + } { + alerts, alertsBytes := createAlert(t, tc.start, tc.end) + api := API{ + uptime: time.Now(), + alerts: newFakeAlerts([]*types.Alert{}), + logger: promslog.NewNopLogger(), + m: metrics.NewAlerts(prometheus.NewRegistry()), + } + api.Update(&config.Config{ + Global: &config.GlobalConfig{ + ResolveTimeout: model.Duration(5), + }, + Route: &config.Route{}, + }, nil) + + r, err := http.NewRequest("POST", "/api/v2/alerts", bytes.NewReader(alertsBytes)) + require.NoError(t, err) + + w := httptest.NewRecorder() + p := runtime.TextProducer() + responder := api.postAlertsHandler(alert_ops.PostAlertsParams{ + HTTPRequest: r, + Alerts: alerts, + }) + responder.WriteResponse(w, p) + body, _ := io.ReadAll(w.Result().Body) + + require.Equal(t, tc.code, w.Code, fmt.Sprintf("test case: %d, response: %s", i, string(body))) + + observer := alertobserver.NewFakeLifeCycleObserver() + api.alertLCObserver = observer + r, err = http.NewRequest("POST", "/api/v2/alerts", bytes.NewReader(alertsBytes)) + require.NoError(t, err) + api.postAlertsHandler(alert_ops.PostAlertsParams{ + HTTPRequest: r, + Alerts: alerts, + }) + amAlert := OpenAPIAlertsToAlerts(alerts) + if tc.code == 200 { + require.Equal(t, observer.AlertsPerEvent[alertobserver.EventAlertReceived][0].Fingerprint(), amAlert[0].Fingerprint()) + } else { + require.Equal(t, observer.AlertsPerEvent[alertobserver.EventAlertRejected][0].Fingerprint(), amAlert[0].Fingerprint()) + } + } +} + +type limitNumberOfAlertsReturnedCallback struct { + limit int +} + +func (n limitNumberOfAlertsReturnedCallback) V2GetAlertsCallback(alerts open_api_models.GettableAlerts) (open_api_models.GettableAlerts, error) { + return alerts[:n.limit], nil +} + +func (n limitNumberOfAlertsReturnedCallback) V2GetAlertGroupsCallback(alertgroups open_api_models.AlertGroups) (open_api_models.AlertGroups, error) { + return alertgroups[:n.limit], nil +} + +func getAlertStatus(model.Fingerprint) types.AlertStatus { + status := types.AlertStatus{SilencedBy: []string{}, InhibitedBy: []string{}} + status.State = types.AlertStateActive + return status +} + +func BoolPointer(b bool) *bool { + return &b +} + +func convertIntToPointerInt64(x int64) *int64 { + return &x +} + +func convertStringToPointer(x string) *string { + return &x +} diff --git a/api/v2/client/alertgroupinfolist/alertgroupinfolist_client.go b/api/v2/client/alertgroupinfolist/alertgroupinfolist_client.go new file mode 100644 index 0000000000..62171e8605 --- /dev/null +++ b/api/v2/client/alertgroupinfolist/alertgroupinfolist_client.go @@ -0,0 +1,93 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// Copyright Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package alertgroupinfolist + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "fmt" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" +) + +// New creates a new alertgroupinfolist API client. +func New(transport runtime.ClientTransport, formats strfmt.Registry) ClientService { + return &Client{transport: transport, formats: formats} +} + +/* +Client for alertgroupinfolist API +*/ +type Client struct { + transport runtime.ClientTransport + formats strfmt.Registry +} + +// ClientOption is the option for Client methods +type ClientOption func(*runtime.ClientOperation) + +// ClientService is the interface for Client methods +type ClientService interface { + GetAlertGroupInfoList(params *GetAlertGroupInfoListParams, opts ...ClientOption) (*GetAlertGroupInfoListOK, error) + + SetTransport(transport runtime.ClientTransport) +} + +/* +GetAlertGroupInfoList Get a list of alert groups information +*/ +func (a *Client) GetAlertGroupInfoList(params *GetAlertGroupInfoListParams, opts ...ClientOption) (*GetAlertGroupInfoListOK, error) { + // TODO: Validate the params before sending + if params == nil { + params = NewGetAlertGroupInfoListParams() + } + op := &runtime.ClientOperation{ + ID: "getAlertGroupInfoList", + Method: "GET", + PathPattern: "/alertgroups", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http"}, + Params: params, + Reader: &GetAlertGroupInfoListReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + success, ok := result.(*GetAlertGroupInfoListOK) + if ok { + return success, nil + } + // unexpected success response + // safeguard: normally, absent a default response, unknown success responses return an error above: so this is a codegen issue + msg := fmt.Sprintf("unexpected success response for getAlertGroupInfoList: API contract not enforced by server. Client expected to get an error, but got: %T", result) + panic(msg) +} + +// SetTransport changes the transport on the client +func (a *Client) SetTransport(transport runtime.ClientTransport) { + a.transport = transport +} diff --git a/api/v2/client/alertgroupinfolist/get_alert_group_info_list_parameters.go b/api/v2/client/alertgroupinfolist/get_alert_group_info_list_parameters.go new file mode 100644 index 0000000000..4761245904 --- /dev/null +++ b/api/v2/client/alertgroupinfolist/get_alert_group_info_list_parameters.go @@ -0,0 +1,246 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// Copyright Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package alertgroupinfolist + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// NewGetAlertGroupInfoListParams creates a new GetAlertGroupInfoListParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewGetAlertGroupInfoListParams() *GetAlertGroupInfoListParams { + return &GetAlertGroupInfoListParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewGetAlertGroupInfoListParamsWithTimeout creates a new GetAlertGroupInfoListParams object +// with the ability to set a timeout on a request. +func NewGetAlertGroupInfoListParamsWithTimeout(timeout time.Duration) *GetAlertGroupInfoListParams { + return &GetAlertGroupInfoListParams{ + timeout: timeout, + } +} + +// NewGetAlertGroupInfoListParamsWithContext creates a new GetAlertGroupInfoListParams object +// with the ability to set a context for a request. +func NewGetAlertGroupInfoListParamsWithContext(ctx context.Context) *GetAlertGroupInfoListParams { + return &GetAlertGroupInfoListParams{ + Context: ctx, + } +} + +// NewGetAlertGroupInfoListParamsWithHTTPClient creates a new GetAlertGroupInfoListParams object +// with the ability to set a custom HTTPClient for a request. +func NewGetAlertGroupInfoListParamsWithHTTPClient(client *http.Client) *GetAlertGroupInfoListParams { + return &GetAlertGroupInfoListParams{ + HTTPClient: client, + } +} + +/* +GetAlertGroupInfoListParams contains all the parameters to send to the API endpoint + + for the get alert group info list operation. + + Typically these are written to a http.Request. +*/ +type GetAlertGroupInfoListParams struct { + + /* MaxResults. + + The maximum number of alert groups to return in one getAlertGroupInfoList operation. + */ + MaxResults *int64 + + /* NextToken. + + The token for the next set of items to return + */ + NextToken *string + + /* Receiver. + + A regex matching receivers to filter alerts by + */ + Receiver *string + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the get alert group info list params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *GetAlertGroupInfoListParams) WithDefaults() *GetAlertGroupInfoListParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the get alert group info list params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *GetAlertGroupInfoListParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the get alert group info list params +func (o *GetAlertGroupInfoListParams) WithTimeout(timeout time.Duration) *GetAlertGroupInfoListParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the get alert group info list params +func (o *GetAlertGroupInfoListParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the get alert group info list params +func (o *GetAlertGroupInfoListParams) WithContext(ctx context.Context) *GetAlertGroupInfoListParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the get alert group info list params +func (o *GetAlertGroupInfoListParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the get alert group info list params +func (o *GetAlertGroupInfoListParams) WithHTTPClient(client *http.Client) *GetAlertGroupInfoListParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the get alert group info list params +func (o *GetAlertGroupInfoListParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithMaxResults adds the maxResults to the get alert group info list params +func (o *GetAlertGroupInfoListParams) WithMaxResults(maxResults *int64) *GetAlertGroupInfoListParams { + o.SetMaxResults(maxResults) + return o +} + +// SetMaxResults adds the maxResults to the get alert group info list params +func (o *GetAlertGroupInfoListParams) SetMaxResults(maxResults *int64) { + o.MaxResults = maxResults +} + +// WithNextToken adds the nextToken to the get alert group info list params +func (o *GetAlertGroupInfoListParams) WithNextToken(nextToken *string) *GetAlertGroupInfoListParams { + o.SetNextToken(nextToken) + return o +} + +// SetNextToken adds the nextToken to the get alert group info list params +func (o *GetAlertGroupInfoListParams) SetNextToken(nextToken *string) { + o.NextToken = nextToken +} + +// WithReceiver adds the receiver to the get alert group info list params +func (o *GetAlertGroupInfoListParams) WithReceiver(receiver *string) *GetAlertGroupInfoListParams { + o.SetReceiver(receiver) + return o +} + +// SetReceiver adds the receiver to the get alert group info list params +func (o *GetAlertGroupInfoListParams) SetReceiver(receiver *string) { + o.Receiver = receiver +} + +// WriteToRequest writes these params to a swagger request +func (o *GetAlertGroupInfoListParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + + if o.MaxResults != nil { + + // query param maxResults + var qrMaxResults int64 + + if o.MaxResults != nil { + qrMaxResults = *o.MaxResults + } + qMaxResults := swag.FormatInt64(qrMaxResults) + if qMaxResults != "" { + + if err := r.SetQueryParam("maxResults", qMaxResults); err != nil { + return err + } + } + } + + if o.NextToken != nil { + + // query param nextToken + var qrNextToken string + + if o.NextToken != nil { + qrNextToken = *o.NextToken + } + qNextToken := qrNextToken + if qNextToken != "" { + + if err := r.SetQueryParam("nextToken", qNextToken); err != nil { + return err + } + } + } + + if o.Receiver != nil { + + // query param receiver + var qrReceiver string + + if o.Receiver != nil { + qrReceiver = *o.Receiver + } + qReceiver := qrReceiver + if qReceiver != "" { + + if err := r.SetQueryParam("receiver", qReceiver); err != nil { + return err + } + } + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/api/v2/client/alertgroupinfolist/get_alert_group_info_list_responses.go b/api/v2/client/alertgroupinfolist/get_alert_group_info_list_responses.go new file mode 100644 index 0000000000..97d458ea07 --- /dev/null +++ b/api/v2/client/alertgroupinfolist/get_alert_group_info_list_responses.go @@ -0,0 +1,246 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// Copyright Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package alertgroupinfolist + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "fmt" + "io" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + + "github.com/prometheus/alertmanager/api/v2/models" +) + +// GetAlertGroupInfoListReader is a Reader for the GetAlertGroupInfoList structure. +type GetAlertGroupInfoListReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *GetAlertGroupInfoListReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) { + switch response.Code() { + case 200: + result := NewGetAlertGroupInfoListOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + case 400: + result := NewGetAlertGroupInfoListBadRequest() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + case 500: + result := NewGetAlertGroupInfoListInternalServerError() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + default: + return nil, runtime.NewAPIError("response status code does not match any response statuses defined for this endpoint in the swagger spec", response, response.Code()) + } +} + +// NewGetAlertGroupInfoListOK creates a GetAlertGroupInfoListOK with default headers values +func NewGetAlertGroupInfoListOK() *GetAlertGroupInfoListOK { + return &GetAlertGroupInfoListOK{} +} + +/* +GetAlertGroupInfoListOK describes a response with status code 200, with default header values. + +Get alert groups info response +*/ +type GetAlertGroupInfoListOK struct { + Payload *models.AlertGroupInfoList +} + +// IsSuccess returns true when this get alert group info list o k response has a 2xx status code +func (o *GetAlertGroupInfoListOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this get alert group info list o k response has a 3xx status code +func (o *GetAlertGroupInfoListOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this get alert group info list o k response has a 4xx status code +func (o *GetAlertGroupInfoListOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this get alert group info list o k response has a 5xx status code +func (o *GetAlertGroupInfoListOK) IsServerError() bool { + return false +} + +// IsCode returns true when this get alert group info list o k response a status code equal to that given +func (o *GetAlertGroupInfoListOK) IsCode(code int) bool { + return code == 200 +} + +func (o *GetAlertGroupInfoListOK) Error() string { + return fmt.Sprintf("[GET /alertgroups][%d] getAlertGroupInfoListOK %+v", 200, o.Payload) +} + +func (o *GetAlertGroupInfoListOK) String() string { + return fmt.Sprintf("[GET /alertgroups][%d] getAlertGroupInfoListOK %+v", 200, o.Payload) +} + +func (o *GetAlertGroupInfoListOK) GetPayload() *models.AlertGroupInfoList { + return o.Payload +} + +func (o *GetAlertGroupInfoListOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.AlertGroupInfoList) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { + return err + } + + return nil +} + +// NewGetAlertGroupInfoListBadRequest creates a GetAlertGroupInfoListBadRequest with default headers values +func NewGetAlertGroupInfoListBadRequest() *GetAlertGroupInfoListBadRequest { + return &GetAlertGroupInfoListBadRequest{} +} + +/* +GetAlertGroupInfoListBadRequest describes a response with status code 400, with default header values. + +Bad request +*/ +type GetAlertGroupInfoListBadRequest struct { + Payload string +} + +// IsSuccess returns true when this get alert group info list bad request response has a 2xx status code +func (o *GetAlertGroupInfoListBadRequest) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this get alert group info list bad request response has a 3xx status code +func (o *GetAlertGroupInfoListBadRequest) IsRedirect() bool { + return false +} + +// IsClientError returns true when this get alert group info list bad request response has a 4xx status code +func (o *GetAlertGroupInfoListBadRequest) IsClientError() bool { + return true +} + +// IsServerError returns true when this get alert group info list bad request response has a 5xx status code +func (o *GetAlertGroupInfoListBadRequest) IsServerError() bool { + return false +} + +// IsCode returns true when this get alert group info list bad request response a status code equal to that given +func (o *GetAlertGroupInfoListBadRequest) IsCode(code int) bool { + return code == 400 +} + +func (o *GetAlertGroupInfoListBadRequest) Error() string { + return fmt.Sprintf("[GET /alertgroups][%d] getAlertGroupInfoListBadRequest %+v", 400, o.Payload) +} + +func (o *GetAlertGroupInfoListBadRequest) String() string { + return fmt.Sprintf("[GET /alertgroups][%d] getAlertGroupInfoListBadRequest %+v", 400, o.Payload) +} + +func (o *GetAlertGroupInfoListBadRequest) GetPayload() string { + return o.Payload +} + +func (o *GetAlertGroupInfoListBadRequest) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + // response payload + if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF { + return err + } + + return nil +} + +// NewGetAlertGroupInfoListInternalServerError creates a GetAlertGroupInfoListInternalServerError with default headers values +func NewGetAlertGroupInfoListInternalServerError() *GetAlertGroupInfoListInternalServerError { + return &GetAlertGroupInfoListInternalServerError{} +} + +/* +GetAlertGroupInfoListInternalServerError describes a response with status code 500, with default header values. + +Internal server error +*/ +type GetAlertGroupInfoListInternalServerError struct { + Payload string +} + +// IsSuccess returns true when this get alert group info list internal server error response has a 2xx status code +func (o *GetAlertGroupInfoListInternalServerError) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this get alert group info list internal server error response has a 3xx status code +func (o *GetAlertGroupInfoListInternalServerError) IsRedirect() bool { + return false +} + +// IsClientError returns true when this get alert group info list internal server error response has a 4xx status code +func (o *GetAlertGroupInfoListInternalServerError) IsClientError() bool { + return false +} + +// IsServerError returns true when this get alert group info list internal server error response has a 5xx status code +func (o *GetAlertGroupInfoListInternalServerError) IsServerError() bool { + return true +} + +// IsCode returns true when this get alert group info list internal server error response a status code equal to that given +func (o *GetAlertGroupInfoListInternalServerError) IsCode(code int) bool { + return code == 500 +} + +func (o *GetAlertGroupInfoListInternalServerError) Error() string { + return fmt.Sprintf("[GET /alertgroups][%d] getAlertGroupInfoListInternalServerError %+v", 500, o.Payload) +} + +func (o *GetAlertGroupInfoListInternalServerError) String() string { + return fmt.Sprintf("[GET /alertgroups][%d] getAlertGroupInfoListInternalServerError %+v", 500, o.Payload) +} + +func (o *GetAlertGroupInfoListInternalServerError) GetPayload() string { + return o.Payload +} + +func (o *GetAlertGroupInfoListInternalServerError) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + // response payload + if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF { + return err + } + + return nil +} diff --git a/api/v2/client/alertinfo/alertinfo_client.go b/api/v2/client/alertinfo/alertinfo_client.go new file mode 100644 index 0000000000..851e0ff5c0 --- /dev/null +++ b/api/v2/client/alertinfo/alertinfo_client.go @@ -0,0 +1,93 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// Copyright Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package alertinfo + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "fmt" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" +) + +// New creates a new alertinfo API client. +func New(transport runtime.ClientTransport, formats strfmt.Registry) ClientService { + return &Client{transport: transport, formats: formats} +} + +/* +Client for alertinfo API +*/ +type Client struct { + transport runtime.ClientTransport + formats strfmt.Registry +} + +// ClientOption is the option for Client methods +type ClientOption func(*runtime.ClientOperation) + +// ClientService is the interface for Client methods +type ClientService interface { + GetAlertInfos(params *GetAlertInfosParams, opts ...ClientOption) (*GetAlertInfosOK, error) + + SetTransport(transport runtime.ClientTransport) +} + +/* +GetAlertInfos Get a list of alert infos +*/ +func (a *Client) GetAlertInfos(params *GetAlertInfosParams, opts ...ClientOption) (*GetAlertInfosOK, error) { + // TODO: Validate the params before sending + if params == nil { + params = NewGetAlertInfosParams() + } + op := &runtime.ClientOperation{ + ID: "getAlertInfos", + Method: "GET", + PathPattern: "/alertinfos", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http"}, + Params: params, + Reader: &GetAlertInfosReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + success, ok := result.(*GetAlertInfosOK) + if ok { + return success, nil + } + // unexpected success response + // safeguard: normally, absent a default response, unknown success responses return an error above: so this is a codegen issue + msg := fmt.Sprintf("unexpected success response for getAlertInfos: API contract not enforced by server. Client expected to get an error, but got: %T", result) + panic(msg) +} + +// SetTransport changes the transport on the client +func (a *Client) SetTransport(transport runtime.ClientTransport) { + a.transport = transport +} diff --git a/api/v2/client/alertinfo/get_alert_infos_parameters.go b/api/v2/client/alertinfo/get_alert_infos_parameters.go new file mode 100644 index 0000000000..3f1ff567bf --- /dev/null +++ b/api/v2/client/alertinfo/get_alert_infos_parameters.go @@ -0,0 +1,500 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// Copyright Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package alertinfo + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// NewGetAlertInfosParams creates a new GetAlertInfosParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewGetAlertInfosParams() *GetAlertInfosParams { + return &GetAlertInfosParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewGetAlertInfosParamsWithTimeout creates a new GetAlertInfosParams object +// with the ability to set a timeout on a request. +func NewGetAlertInfosParamsWithTimeout(timeout time.Duration) *GetAlertInfosParams { + return &GetAlertInfosParams{ + timeout: timeout, + } +} + +// NewGetAlertInfosParamsWithContext creates a new GetAlertInfosParams object +// with the ability to set a context for a request. +func NewGetAlertInfosParamsWithContext(ctx context.Context) *GetAlertInfosParams { + return &GetAlertInfosParams{ + Context: ctx, + } +} + +// NewGetAlertInfosParamsWithHTTPClient creates a new GetAlertInfosParams object +// with the ability to set a custom HTTPClient for a request. +func NewGetAlertInfosParamsWithHTTPClient(client *http.Client) *GetAlertInfosParams { + return &GetAlertInfosParams{ + HTTPClient: client, + } +} + +/* +GetAlertInfosParams contains all the parameters to send to the API endpoint + + for the get alert infos operation. + + Typically these are written to a http.Request. +*/ +type GetAlertInfosParams struct { + + /* Active. + + Show active alerts + + Default: true + */ + Active *bool + + /* Filter. + + A list of matchers to filter alerts by + */ + Filter []string + + /* GroupID. + + A list of group IDs to filter alerts by + */ + GroupID []string + + /* Inhibited. + + Show inhibited alerts + + Default: true + */ + Inhibited *bool + + /* MaxResults. + + The maximum number of alert to return in one getAlertInfos operation. + */ + MaxResults *int64 + + /* NextToken. + + The token for the next set of items to return + */ + NextToken *string + + /* Receiver. + + A regex matching receivers to filter alerts by + */ + Receiver *string + + /* Silenced. + + Show silenced alerts + + Default: true + */ + Silenced *bool + + /* Unprocessed. + + Show unprocessed alerts + + Default: true + */ + Unprocessed *bool + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the get alert infos params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *GetAlertInfosParams) WithDefaults() *GetAlertInfosParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the get alert infos params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *GetAlertInfosParams) SetDefaults() { + var ( + activeDefault = bool(true) + + inhibitedDefault = bool(true) + + silencedDefault = bool(true) + + unprocessedDefault = bool(true) + ) + + val := GetAlertInfosParams{ + Active: &activeDefault, + Inhibited: &inhibitedDefault, + Silenced: &silencedDefault, + Unprocessed: &unprocessedDefault, + } + + val.timeout = o.timeout + val.Context = o.Context + val.HTTPClient = o.HTTPClient + *o = val +} + +// WithTimeout adds the timeout to the get alert infos params +func (o *GetAlertInfosParams) WithTimeout(timeout time.Duration) *GetAlertInfosParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the get alert infos params +func (o *GetAlertInfosParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the get alert infos params +func (o *GetAlertInfosParams) WithContext(ctx context.Context) *GetAlertInfosParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the get alert infos params +func (o *GetAlertInfosParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the get alert infos params +func (o *GetAlertInfosParams) WithHTTPClient(client *http.Client) *GetAlertInfosParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the get alert infos params +func (o *GetAlertInfosParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithActive adds the active to the get alert infos params +func (o *GetAlertInfosParams) WithActive(active *bool) *GetAlertInfosParams { + o.SetActive(active) + return o +} + +// SetActive adds the active to the get alert infos params +func (o *GetAlertInfosParams) SetActive(active *bool) { + o.Active = active +} + +// WithFilter adds the filter to the get alert infos params +func (o *GetAlertInfosParams) WithFilter(filter []string) *GetAlertInfosParams { + o.SetFilter(filter) + return o +} + +// SetFilter adds the filter to the get alert infos params +func (o *GetAlertInfosParams) SetFilter(filter []string) { + o.Filter = filter +} + +// WithGroupID adds the groupID to the get alert infos params +func (o *GetAlertInfosParams) WithGroupID(groupID []string) *GetAlertInfosParams { + o.SetGroupID(groupID) + return o +} + +// SetGroupID adds the groupId to the get alert infos params +func (o *GetAlertInfosParams) SetGroupID(groupID []string) { + o.GroupID = groupID +} + +// WithInhibited adds the inhibited to the get alert infos params +func (o *GetAlertInfosParams) WithInhibited(inhibited *bool) *GetAlertInfosParams { + o.SetInhibited(inhibited) + return o +} + +// SetInhibited adds the inhibited to the get alert infos params +func (o *GetAlertInfosParams) SetInhibited(inhibited *bool) { + o.Inhibited = inhibited +} + +// WithMaxResults adds the maxResults to the get alert infos params +func (o *GetAlertInfosParams) WithMaxResults(maxResults *int64) *GetAlertInfosParams { + o.SetMaxResults(maxResults) + return o +} + +// SetMaxResults adds the maxResults to the get alert infos params +func (o *GetAlertInfosParams) SetMaxResults(maxResults *int64) { + o.MaxResults = maxResults +} + +// WithNextToken adds the nextToken to the get alert infos params +func (o *GetAlertInfosParams) WithNextToken(nextToken *string) *GetAlertInfosParams { + o.SetNextToken(nextToken) + return o +} + +// SetNextToken adds the nextToken to the get alert infos params +func (o *GetAlertInfosParams) SetNextToken(nextToken *string) { + o.NextToken = nextToken +} + +// WithReceiver adds the receiver to the get alert infos params +func (o *GetAlertInfosParams) WithReceiver(receiver *string) *GetAlertInfosParams { + o.SetReceiver(receiver) + return o +} + +// SetReceiver adds the receiver to the get alert infos params +func (o *GetAlertInfosParams) SetReceiver(receiver *string) { + o.Receiver = receiver +} + +// WithSilenced adds the silenced to the get alert infos params +func (o *GetAlertInfosParams) WithSilenced(silenced *bool) *GetAlertInfosParams { + o.SetSilenced(silenced) + return o +} + +// SetSilenced adds the silenced to the get alert infos params +func (o *GetAlertInfosParams) SetSilenced(silenced *bool) { + o.Silenced = silenced +} + +// WithUnprocessed adds the unprocessed to the get alert infos params +func (o *GetAlertInfosParams) WithUnprocessed(unprocessed *bool) *GetAlertInfosParams { + o.SetUnprocessed(unprocessed) + return o +} + +// SetUnprocessed adds the unprocessed to the get alert infos params +func (o *GetAlertInfosParams) SetUnprocessed(unprocessed *bool) { + o.Unprocessed = unprocessed +} + +// WriteToRequest writes these params to a swagger request +func (o *GetAlertInfosParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + + if o.Active != nil { + + // query param active + var qrActive bool + + if o.Active != nil { + qrActive = *o.Active + } + qActive := swag.FormatBool(qrActive) + if qActive != "" { + + if err := r.SetQueryParam("active", qActive); err != nil { + return err + } + } + } + + if o.Filter != nil { + + // binding items for filter + joinedFilter := o.bindParamFilter(reg) + + // query array param filter + if err := r.SetQueryParam("filter", joinedFilter...); err != nil { + return err + } + } + + if o.GroupID != nil { + + // binding items for groupId + joinedGroupID := o.bindParamGroupID(reg) + + // query array param groupId + if err := r.SetQueryParam("groupId", joinedGroupID...); err != nil { + return err + } + } + + if o.Inhibited != nil { + + // query param inhibited + var qrInhibited bool + + if o.Inhibited != nil { + qrInhibited = *o.Inhibited + } + qInhibited := swag.FormatBool(qrInhibited) + if qInhibited != "" { + + if err := r.SetQueryParam("inhibited", qInhibited); err != nil { + return err + } + } + } + + if o.MaxResults != nil { + + // query param maxResults + var qrMaxResults int64 + + if o.MaxResults != nil { + qrMaxResults = *o.MaxResults + } + qMaxResults := swag.FormatInt64(qrMaxResults) + if qMaxResults != "" { + + if err := r.SetQueryParam("maxResults", qMaxResults); err != nil { + return err + } + } + } + + if o.NextToken != nil { + + // query param nextToken + var qrNextToken string + + if o.NextToken != nil { + qrNextToken = *o.NextToken + } + qNextToken := qrNextToken + if qNextToken != "" { + + if err := r.SetQueryParam("nextToken", qNextToken); err != nil { + return err + } + } + } + + if o.Receiver != nil { + + // query param receiver + var qrReceiver string + + if o.Receiver != nil { + qrReceiver = *o.Receiver + } + qReceiver := qrReceiver + if qReceiver != "" { + + if err := r.SetQueryParam("receiver", qReceiver); err != nil { + return err + } + } + } + + if o.Silenced != nil { + + // query param silenced + var qrSilenced bool + + if o.Silenced != nil { + qrSilenced = *o.Silenced + } + qSilenced := swag.FormatBool(qrSilenced) + if qSilenced != "" { + + if err := r.SetQueryParam("silenced", qSilenced); err != nil { + return err + } + } + } + + if o.Unprocessed != nil { + + // query param unprocessed + var qrUnprocessed bool + + if o.Unprocessed != nil { + qrUnprocessed = *o.Unprocessed + } + qUnprocessed := swag.FormatBool(qrUnprocessed) + if qUnprocessed != "" { + + if err := r.SetQueryParam("unprocessed", qUnprocessed); err != nil { + return err + } + } + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindParamGetAlertInfos binds the parameter filter +func (o *GetAlertInfosParams) bindParamFilter(formats strfmt.Registry) []string { + filterIR := o.Filter + + var filterIC []string + for _, filterIIR := range filterIR { // explode []string + + filterIIV := filterIIR // string as string + filterIC = append(filterIC, filterIIV) + } + + // items.CollectionFormat: "multi" + filterIS := swag.JoinByFormat(filterIC, "multi") + + return filterIS +} + +// bindParamGetAlertInfos binds the parameter groupId +func (o *GetAlertInfosParams) bindParamGroupID(formats strfmt.Registry) []string { + groupIDIR := o.GroupID + + var groupIDIC []string + for _, groupIDIIR := range groupIDIR { // explode []string + + groupIDIIV := groupIDIIR // string as string + groupIDIC = append(groupIDIC, groupIDIIV) + } + + // items.CollectionFormat: "multi" + groupIDIS := swag.JoinByFormat(groupIDIC, "multi") + + return groupIDIS +} diff --git a/api/v2/client/alertinfo/get_alert_infos_responses.go b/api/v2/client/alertinfo/get_alert_infos_responses.go new file mode 100644 index 0000000000..1c1482db0f --- /dev/null +++ b/api/v2/client/alertinfo/get_alert_infos_responses.go @@ -0,0 +1,246 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// Copyright Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package alertinfo + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "fmt" + "io" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + + "github.com/prometheus/alertmanager/api/v2/models" +) + +// GetAlertInfosReader is a Reader for the GetAlertInfos structure. +type GetAlertInfosReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *GetAlertInfosReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) { + switch response.Code() { + case 200: + result := NewGetAlertInfosOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + case 400: + result := NewGetAlertInfosBadRequest() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + case 500: + result := NewGetAlertInfosInternalServerError() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + default: + return nil, runtime.NewAPIError("response status code does not match any response statuses defined for this endpoint in the swagger spec", response, response.Code()) + } +} + +// NewGetAlertInfosOK creates a GetAlertInfosOK with default headers values +func NewGetAlertInfosOK() *GetAlertInfosOK { + return &GetAlertInfosOK{} +} + +/* +GetAlertInfosOK describes a response with status code 200, with default header values. + +Get alerts response +*/ +type GetAlertInfosOK struct { + Payload *models.GettableAlertInfos +} + +// IsSuccess returns true when this get alert infos o k response has a 2xx status code +func (o *GetAlertInfosOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this get alert infos o k response has a 3xx status code +func (o *GetAlertInfosOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this get alert infos o k response has a 4xx status code +func (o *GetAlertInfosOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this get alert infos o k response has a 5xx status code +func (o *GetAlertInfosOK) IsServerError() bool { + return false +} + +// IsCode returns true when this get alert infos o k response a status code equal to that given +func (o *GetAlertInfosOK) IsCode(code int) bool { + return code == 200 +} + +func (o *GetAlertInfosOK) Error() string { + return fmt.Sprintf("[GET /alertinfos][%d] getAlertInfosOK %+v", 200, o.Payload) +} + +func (o *GetAlertInfosOK) String() string { + return fmt.Sprintf("[GET /alertinfos][%d] getAlertInfosOK %+v", 200, o.Payload) +} + +func (o *GetAlertInfosOK) GetPayload() *models.GettableAlertInfos { + return o.Payload +} + +func (o *GetAlertInfosOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.GettableAlertInfos) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { + return err + } + + return nil +} + +// NewGetAlertInfosBadRequest creates a GetAlertInfosBadRequest with default headers values +func NewGetAlertInfosBadRequest() *GetAlertInfosBadRequest { + return &GetAlertInfosBadRequest{} +} + +/* +GetAlertInfosBadRequest describes a response with status code 400, with default header values. + +Bad request +*/ +type GetAlertInfosBadRequest struct { + Payload string +} + +// IsSuccess returns true when this get alert infos bad request response has a 2xx status code +func (o *GetAlertInfosBadRequest) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this get alert infos bad request response has a 3xx status code +func (o *GetAlertInfosBadRequest) IsRedirect() bool { + return false +} + +// IsClientError returns true when this get alert infos bad request response has a 4xx status code +func (o *GetAlertInfosBadRequest) IsClientError() bool { + return true +} + +// IsServerError returns true when this get alert infos bad request response has a 5xx status code +func (o *GetAlertInfosBadRequest) IsServerError() bool { + return false +} + +// IsCode returns true when this get alert infos bad request response a status code equal to that given +func (o *GetAlertInfosBadRequest) IsCode(code int) bool { + return code == 400 +} + +func (o *GetAlertInfosBadRequest) Error() string { + return fmt.Sprintf("[GET /alertinfos][%d] getAlertInfosBadRequest %+v", 400, o.Payload) +} + +func (o *GetAlertInfosBadRequest) String() string { + return fmt.Sprintf("[GET /alertinfos][%d] getAlertInfosBadRequest %+v", 400, o.Payload) +} + +func (o *GetAlertInfosBadRequest) GetPayload() string { + return o.Payload +} + +func (o *GetAlertInfosBadRequest) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + // response payload + if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF { + return err + } + + return nil +} + +// NewGetAlertInfosInternalServerError creates a GetAlertInfosInternalServerError with default headers values +func NewGetAlertInfosInternalServerError() *GetAlertInfosInternalServerError { + return &GetAlertInfosInternalServerError{} +} + +/* +GetAlertInfosInternalServerError describes a response with status code 500, with default header values. + +Internal server error +*/ +type GetAlertInfosInternalServerError struct { + Payload string +} + +// IsSuccess returns true when this get alert infos internal server error response has a 2xx status code +func (o *GetAlertInfosInternalServerError) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this get alert infos internal server error response has a 3xx status code +func (o *GetAlertInfosInternalServerError) IsRedirect() bool { + return false +} + +// IsClientError returns true when this get alert infos internal server error response has a 4xx status code +func (o *GetAlertInfosInternalServerError) IsClientError() bool { + return false +} + +// IsServerError returns true when this get alert infos internal server error response has a 5xx status code +func (o *GetAlertInfosInternalServerError) IsServerError() bool { + return true +} + +// IsCode returns true when this get alert infos internal server error response a status code equal to that given +func (o *GetAlertInfosInternalServerError) IsCode(code int) bool { + return code == 500 +} + +func (o *GetAlertInfosInternalServerError) Error() string { + return fmt.Sprintf("[GET /alertinfos][%d] getAlertInfosInternalServerError %+v", 500, o.Payload) +} + +func (o *GetAlertInfosInternalServerError) String() string { + return fmt.Sprintf("[GET /alertinfos][%d] getAlertInfosInternalServerError %+v", 500, o.Payload) +} + +func (o *GetAlertInfosInternalServerError) GetPayload() string { + return o.Payload +} + +func (o *GetAlertInfosInternalServerError) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + // response payload + if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF { + return err + } + + return nil +} diff --git a/api/v2/client/alertmanager_api_client.go b/api/v2/client/alertmanager_api_client.go index 5b7c56e5e5..e1fd0161db 100644 --- a/api/v2/client/alertmanager_api_client.go +++ b/api/v2/client/alertmanager_api_client.go @@ -26,6 +26,8 @@ import ( "github.com/prometheus/alertmanager/api/v2/client/alert" "github.com/prometheus/alertmanager/api/v2/client/alertgroup" + "github.com/prometheus/alertmanager/api/v2/client/alertgroupinfolist" + "github.com/prometheus/alertmanager/api/v2/client/alertinfo" "github.com/prometheus/alertmanager/api/v2/client/general" "github.com/prometheus/alertmanager/api/v2/client/receiver" "github.com/prometheus/alertmanager/api/v2/client/silence" @@ -75,6 +77,8 @@ func New(transport runtime.ClientTransport, formats strfmt.Registry) *Alertmanag cli.Transport = transport cli.Alert = alert.New(transport, formats) cli.Alertgroup = alertgroup.New(transport, formats) + cli.Alertgroupinfolist = alertgroupinfolist.New(transport, formats) + cli.Alertinfo = alertinfo.New(transport, formats) cli.General = general.New(transport, formats) cli.Receiver = receiver.New(transport, formats) cli.Silence = silence.New(transport, formats) @@ -126,6 +130,10 @@ type AlertmanagerAPI struct { Alertgroup alertgroup.ClientService + Alertgroupinfolist alertgroupinfolist.ClientService + + Alertinfo alertinfo.ClientService + General general.ClientService Receiver receiver.ClientService @@ -140,6 +148,8 @@ func (c *AlertmanagerAPI) SetTransport(transport runtime.ClientTransport) { c.Transport = transport c.Alert.SetTransport(transport) c.Alertgroup.SetTransport(transport) + c.Alertgroupinfolist.SetTransport(transport) + c.Alertinfo.SetTransport(transport) c.General.SetTransport(transport) c.Receiver.SetTransport(transport) c.Silence.SetTransport(transport) diff --git a/api/v2/compat.go b/api/v2/compat.go index 8954f2f752..674f422deb 100644 --- a/api/v2/compat.go +++ b/api/v2/compat.go @@ -207,3 +207,60 @@ func APILabelSetToModelLabelSet(apiLabelSet open_api_models.LabelSet) prometheus return modelLabelSet } + +// AlertInfosTruncate truncate the open_api_models.GettableAlerts using maxResult and return a nextToken if there are items has been truncated. +func AlertInfosTruncate(alerts open_api_models.GettableAlerts, maxResult *int64, nextToken *string) (open_api_models.GettableAlerts, string) { + resultNumber := 0 + var previousAlertID *string + var returnPaginationToken string + returnAlerts := make(open_api_models.GettableAlerts, 0, len(alerts)) + for _, alert := range alerts { + + // Skip the alert if the next token is set and hasn't arrived the nextToken item yet. + alertFP := alert.Fingerprint + if nextToken != nil && alertFP != nil && *nextToken >= *alertFP { + continue + } + + // Add the alert to the return slice if the maxItem is not hit + if maxResult == nil || resultNumber < int(*maxResult) { + previousAlertID = alert.Fingerprint + returnAlerts = append(returnAlerts, alert) + resultNumber++ + continue + } + + // Return the next token if there is more alert + if resultNumber == int(*maxResult) && previousAlertID != nil { + returnPaginationToken = *previousAlertID + break + } + } + + return returnAlerts, returnPaginationToken +} + +func AlertGroupInfoListTruncate(alertGroupInfos []*open_api_models.AlertGroupInfo, maxResult *int64) ([]*open_api_models.AlertGroupInfo, string) { + resultNumber := 0 + var previousAgID *string + var returnPaginationToken string + returnAlertGroupInfos := make([]*open_api_models.AlertGroupInfo, 0, len(alertGroupInfos)) + for _, alertGroup := range alertGroupInfos { + + // Add the aggregation group to the return slice if the maxItem is not hit + if maxResult == nil || resultNumber < int(*maxResult) { + previousAgID = alertGroup.ID + returnAlertGroupInfos = append(returnAlertGroupInfos, alertGroup) + resultNumber++ + continue + } + + // Return the next token if there is more aggregation group + if resultNumber == int(*maxResult) && previousAgID != nil { + returnPaginationToken = *previousAgID + break + } + } + + return returnAlertGroupInfos, returnPaginationToken +} diff --git a/api/v2/models/alert_group_info.go b/api/v2/models/alert_group_info.go new file mode 100644 index 0000000000..ba4c91ba07 --- /dev/null +++ b/api/v2/models/alert_group_info.go @@ -0,0 +1,184 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// Copyright Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// AlertGroupInfo alert group info +// +// swagger:model alertGroupInfo +type AlertGroupInfo struct { + + // id + // Required: true + ID *string `json:"id"` + + // labels + // Required: true + Labels LabelSet `json:"labels"` + + // receiver + // Required: true + Receiver *Receiver `json:"receiver"` +} + +// Validate validates this alert group info +func (m *AlertGroupInfo) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateLabels(formats); err != nil { + res = append(res, err) + } + + if err := m.validateReceiver(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *AlertGroupInfo) validateID(formats strfmt.Registry) error { + + if err := validate.Required("id", "body", m.ID); err != nil { + return err + } + + return nil +} + +func (m *AlertGroupInfo) validateLabels(formats strfmt.Registry) error { + + if err := validate.Required("labels", "body", m.Labels); err != nil { + return err + } + + if m.Labels != nil { + if err := m.Labels.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("labels") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("labels") + } + return err + } + } + + return nil +} + +func (m *AlertGroupInfo) validateReceiver(formats strfmt.Registry) error { + + if err := validate.Required("receiver", "body", m.Receiver); err != nil { + return err + } + + if m.Receiver != nil { + if err := m.Receiver.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("receiver") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("receiver") + } + return err + } + } + + return nil +} + +// ContextValidate validate this alert group info based on the context it is used +func (m *AlertGroupInfo) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateLabels(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateReceiver(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *AlertGroupInfo) contextValidateLabels(ctx context.Context, formats strfmt.Registry) error { + + if err := m.Labels.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("labels") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("labels") + } + return err + } + + return nil +} + +func (m *AlertGroupInfo) contextValidateReceiver(ctx context.Context, formats strfmt.Registry) error { + + if m.Receiver != nil { + if err := m.Receiver.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("receiver") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("receiver") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *AlertGroupInfo) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *AlertGroupInfo) UnmarshalBinary(b []byte) error { + var res AlertGroupInfo + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/api/v2/models/alert_group_info_list.go b/api/v2/models/alert_group_info_list.go new file mode 100644 index 0000000000..b907ba1083 --- /dev/null +++ b/api/v2/models/alert_group_info_list.go @@ -0,0 +1,133 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// Copyright Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// AlertGroupInfoList alert group info list +// +// swagger:model alertGroupInfoList +type AlertGroupInfoList struct { + + // alert group info list + AlertGroupInfoList []*AlertGroupInfo `json:"alertGroupInfoList"` + + // next token + NextToken string `json:"nextToken,omitempty"` +} + +// Validate validates this alert group info list +func (m *AlertGroupInfoList) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateAlertGroupInfoList(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *AlertGroupInfoList) validateAlertGroupInfoList(formats strfmt.Registry) error { + if swag.IsZero(m.AlertGroupInfoList) { // not required + return nil + } + + for i := 0; i < len(m.AlertGroupInfoList); i++ { + if swag.IsZero(m.AlertGroupInfoList[i]) { // not required + continue + } + + if m.AlertGroupInfoList[i] != nil { + if err := m.AlertGroupInfoList[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("alertGroupInfoList" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("alertGroupInfoList" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// ContextValidate validate this alert group info list based on the context it is used +func (m *AlertGroupInfoList) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateAlertGroupInfoList(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *AlertGroupInfoList) contextValidateAlertGroupInfoList(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.AlertGroupInfoList); i++ { + + if m.AlertGroupInfoList[i] != nil { + if err := m.AlertGroupInfoList[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("alertGroupInfoList" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("alertGroupInfoList" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// MarshalBinary interface implementation +func (m *AlertGroupInfoList) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *AlertGroupInfoList) UnmarshalBinary(b []byte) error { + var res AlertGroupInfoList + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/api/v2/models/gettable_alert_infos.go b/api/v2/models/gettable_alert_infos.go new file mode 100644 index 0000000000..cbd793d19c --- /dev/null +++ b/api/v2/models/gettable_alert_infos.go @@ -0,0 +1,133 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// Copyright Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// GettableAlertInfos gettable alert infos +// +// swagger:model gettableAlertInfos +type GettableAlertInfos struct { + + // alerts + Alerts []*GettableAlert `json:"alerts"` + + // next token + NextToken string `json:"nextToken,omitempty"` +} + +// Validate validates this gettable alert infos +func (m *GettableAlertInfos) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateAlerts(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *GettableAlertInfos) validateAlerts(formats strfmt.Registry) error { + if swag.IsZero(m.Alerts) { // not required + return nil + } + + for i := 0; i < len(m.Alerts); i++ { + if swag.IsZero(m.Alerts[i]) { // not required + continue + } + + if m.Alerts[i] != nil { + if err := m.Alerts[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("alerts" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("alerts" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// ContextValidate validate this gettable alert infos based on the context it is used +func (m *GettableAlertInfos) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateAlerts(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *GettableAlertInfos) contextValidateAlerts(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.Alerts); i++ { + + if m.Alerts[i] != nil { + if err := m.Alerts[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("alerts" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("alerts" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// MarshalBinary interface implementation +func (m *GettableAlertInfos) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *GettableAlertInfos) UnmarshalBinary(b []byte) error { + var res GettableAlertInfos + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/api/v2/openapi.yaml b/api/v2/openapi.yaml index bc525ff37a..252d48d756 100644 --- a/api/v2/openapi.yaml +++ b/api/v2/openapi.yaml @@ -201,6 +201,74 @@ paths: $ref: '#/responses/InternalServerError' '400': $ref: '#/responses/BadRequest' + + /alertinfos: + get: + tags: + - alertinfo + operationId: getAlertInfos + description: Get a list of alert infos + parameters: + - in: query + name: active + type: boolean + description: Show active alerts + default: true + - in: query + name: silenced + type: boolean + description: Show silenced alerts + default: true + - in: query + name: inhibited + type: boolean + description: Show inhibited alerts + default: true + - in: query + name: unprocessed + type: boolean + description: Show unprocessed alerts + default: true + - name: filter + in: query + description: A list of matchers to filter alerts by + required: false + type: array + collectionFormat: multi + items: + type: string + - name: receiver + in: query + description: A regex matching receivers to filter alerts by + required: false + type: string + - name: groupId + in: query + description: A list of group IDs to filter alerts by + required: false + type: array + collectionFormat: multi + items: + type: string + - name: nextToken + in: query + description: The token for the next set of items to return + required: false + type: string + - name: maxResults + in: query + description: The maximum number of alert to return in one getAlertInfos operation. + required: false + type: integer + responses: + '200': + description: Get alerts response + schema: + '$ref': '#/definitions/gettableAlertInfos' + '400': + $ref: '#/responses/BadRequest' + '500': + $ref: '#/responses/InternalServerError' /alerts/groups: get: tags: @@ -250,6 +318,37 @@ paths: $ref: '#/responses/BadRequest' '500': $ref: '#/responses/InternalServerError' + /alertgroups: + get: + tags: + - alertgroupinfolist + operationId: getAlertGroupInfoList + description: Get a list of alert groups information + parameters: + - name: receiver + in: query + description: A regex matching receivers to filter alerts by + required: false + type: string + - name: nextToken + in: query + description: The token for the next set of items to return + required: false + type: string + - name: maxResults + in: query + description: The maximum number of alert groups to return in one getAlertGroupInfoList operation. + required: false + type: integer + responses: + '200': + description: Get alert groups info response + schema: + '$ref': '#/definitions/alertGroupInfoList' + '400': + $ref: '#/responses/BadRequest' + '500': + $ref: '#/responses/InternalServerError' responses: BadRequest: @@ -424,6 +523,15 @@ definitions: type: array items: $ref: '#/definitions/gettableAlert' + gettableAlertInfos: + type: object + properties: + alerts: + type: array + items: + $ref: '#/definitions/gettableAlert' + nextToken: + type: string gettableAlert: allOf: - type: object @@ -492,6 +600,28 @@ definitions: - labels - receiver - alerts + alertGroupInfoList: + type: object + properties: + alertGroupInfoList: + type: array + items: + $ref: '#/definitions/alertGroupInfo' + nextToken: + type: string + alertGroupInfo: + type: object + properties: + labels: + $ref: '#/definitions/labelSet' + receiver: + $ref: '#/definitions/receiver' + id: + type: string + required: + - labels + - receiver + - id alertStatus: type: object properties: diff --git a/api/v2/restapi/configure_alertmanager.go b/api/v2/restapi/configure_alertmanager.go index 69dfe66bf9..9f6b1e9573 100644 --- a/api/v2/restapi/configure_alertmanager.go +++ b/api/v2/restapi/configure_alertmanager.go @@ -27,6 +27,8 @@ import ( "github.com/prometheus/alertmanager/api/v2/restapi/operations" "github.com/prometheus/alertmanager/api/v2/restapi/operations/alert" "github.com/prometheus/alertmanager/api/v2/restapi/operations/alertgroup" + "github.com/prometheus/alertmanager/api/v2/restapi/operations/alertgroupinfolist" + "github.com/prometheus/alertmanager/api/v2/restapi/operations/alertinfo" "github.com/prometheus/alertmanager/api/v2/restapi/operations/general" "github.com/prometheus/alertmanager/api/v2/restapi/operations/receiver" "github.com/prometheus/alertmanager/api/v2/restapi/operations/silence" @@ -61,11 +63,21 @@ func configureAPI(api *operations.AlertmanagerAPI) http.Handler { return middleware.NotImplemented("operation silence.DeleteSilence has not yet been implemented") }) } + if api.AlertgroupinfolistGetAlertGroupInfoListHandler == nil { + api.AlertgroupinfolistGetAlertGroupInfoListHandler = alertgroupinfolist.GetAlertGroupInfoListHandlerFunc(func(params alertgroupinfolist.GetAlertGroupInfoListParams) middleware.Responder { + return middleware.NotImplemented("operation alertgroupinfolist.GetAlertGroupInfoList has not yet been implemented") + }) + } if api.AlertgroupGetAlertGroupsHandler == nil { api.AlertgroupGetAlertGroupsHandler = alertgroup.GetAlertGroupsHandlerFunc(func(params alertgroup.GetAlertGroupsParams) middleware.Responder { return middleware.NotImplemented("operation alertgroup.GetAlertGroups has not yet been implemented") }) } + if api.AlertinfoGetAlertInfosHandler == nil { + api.AlertinfoGetAlertInfosHandler = alertinfo.GetAlertInfosHandlerFunc(func(params alertinfo.GetAlertInfosParams) middleware.Responder { + return middleware.NotImplemented("operation alertinfo.GetAlertInfos has not yet been implemented") + }) + } if api.AlertGetAlertsHandler == nil { api.AlertGetAlertsHandler = alert.GetAlertsHandlerFunc(func(params alert.GetAlertsParams) middleware.Responder { return middleware.NotImplemented("operation alert.GetAlerts has not yet been implemented") diff --git a/api/v2/restapi/embedded_spec.go b/api/v2/restapi/embedded_spec.go index 37938bdc14..44eb4a5d0d 100644 --- a/api/v2/restapi/embedded_spec.go +++ b/api/v2/restapi/embedded_spec.go @@ -50,6 +50,140 @@ func init() { }, "basePath": "/api/v2/", "paths": { + "/alertgroups": { + "get": { + "description": "Get a list of alert groups information", + "tags": [ + "alertgroupinfolist" + ], + "operationId": "getAlertGroupInfoList", + "parameters": [ + { + "type": "string", + "description": "A regex matching receivers to filter alerts by", + "name": "receiver", + "in": "query" + }, + { + "type": "string", + "description": "The token for the next set of items to return", + "name": "nextToken", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of alert groups to return in one getAlertGroupInfoList operation.", + "name": "maxResults", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Get alert groups info response", + "schema": { + "$ref": "#/definitions/alertGroupInfoList" + } + }, + "400": { + "$ref": "#/responses/BadRequest" + }, + "500": { + "$ref": "#/responses/InternalServerError" + } + } + } + }, + "/alertinfos": { + "get": { + "description": "Get a list of alert infos", + "tags": [ + "alertinfo" + ], + "operationId": "getAlertInfos", + "parameters": [ + { + "type": "boolean", + "default": true, + "description": "Show active alerts", + "name": "active", + "in": "query" + }, + { + "type": "boolean", + "default": true, + "description": "Show silenced alerts", + "name": "silenced", + "in": "query" + }, + { + "type": "boolean", + "default": true, + "description": "Show inhibited alerts", + "name": "inhibited", + "in": "query" + }, + { + "type": "boolean", + "default": true, + "description": "Show unprocessed alerts", + "name": "unprocessed", + "in": "query" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi", + "description": "A list of matchers to filter alerts by", + "name": "filter", + "in": "query" + }, + { + "type": "string", + "description": "A regex matching receivers to filter alerts by", + "name": "receiver", + "in": "query" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi", + "description": "A list of group IDs to filter alerts by", + "name": "groupId", + "in": "query" + }, + { + "type": "string", + "description": "The token for the next set of items to return", + "name": "nextToken", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of alert to return in one getAlertInfos operation.", + "name": "maxResults", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Get alerts response", + "schema": { + "$ref": "#/definitions/gettableAlertInfos" + } + }, + "400": { + "$ref": "#/responses/BadRequest" + }, + "500": { + "$ref": "#/responses/InternalServerError" + } + } + } + }, "/alerts": { "get": { "description": "Get a list of alerts", @@ -429,6 +563,39 @@ func init() { } } }, + "alertGroupInfo": { + "type": "object", + "required": [ + "labels", + "receiver", + "id" + ], + "properties": { + "id": { + "type": "string" + }, + "labels": { + "$ref": "#/definitions/labelSet" + }, + "receiver": { + "$ref": "#/definitions/receiver" + } + } + }, + "alertGroupInfoList": { + "type": "object", + "properties": { + "alertGroupInfoList": { + "type": "array", + "items": { + "$ref": "#/definitions/alertGroupInfo" + } + }, + "nextToken": { + "type": "string" + } + } + }, "alertGroups": { "type": "array", "items": { @@ -580,6 +747,20 @@ func init() { } ] }, + "gettableAlertInfos": { + "type": "object", + "properties": { + "alerts": { + "type": "array", + "items": { + "$ref": "#/definitions/gettableAlert" + } + }, + "nextToken": { + "type": "string" + } + } + }, "gettableAlerts": { "type": "array", "items": { @@ -854,6 +1035,152 @@ func init() { }, "basePath": "/api/v2/", "paths": { + "/alertgroups": { + "get": { + "description": "Get a list of alert groups information", + "tags": [ + "alertgroupinfolist" + ], + "operationId": "getAlertGroupInfoList", + "parameters": [ + { + "type": "string", + "description": "A regex matching receivers to filter alerts by", + "name": "receiver", + "in": "query" + }, + { + "type": "string", + "description": "The token for the next set of items to return", + "name": "nextToken", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of alert groups to return in one getAlertGroupInfoList operation.", + "name": "maxResults", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Get alert groups info response", + "schema": { + "$ref": "#/definitions/alertGroupInfoList" + } + }, + "400": { + "description": "Bad request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "type": "string" + } + } + } + } + }, + "/alertinfos": { + "get": { + "description": "Get a list of alert infos", + "tags": [ + "alertinfo" + ], + "operationId": "getAlertInfos", + "parameters": [ + { + "type": "boolean", + "default": true, + "description": "Show active alerts", + "name": "active", + "in": "query" + }, + { + "type": "boolean", + "default": true, + "description": "Show silenced alerts", + "name": "silenced", + "in": "query" + }, + { + "type": "boolean", + "default": true, + "description": "Show inhibited alerts", + "name": "inhibited", + "in": "query" + }, + { + "type": "boolean", + "default": true, + "description": "Show unprocessed alerts", + "name": "unprocessed", + "in": "query" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi", + "description": "A list of matchers to filter alerts by", + "name": "filter", + "in": "query" + }, + { + "type": "string", + "description": "A regex matching receivers to filter alerts by", + "name": "receiver", + "in": "query" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi", + "description": "A list of group IDs to filter alerts by", + "name": "groupId", + "in": "query" + }, + { + "type": "string", + "description": "The token for the next set of items to return", + "name": "nextToken", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of alert to return in one getAlertInfos operation.", + "name": "maxResults", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Get alerts response", + "schema": { + "$ref": "#/definitions/gettableAlertInfos" + } + }, + "400": { + "description": "Bad request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "type": "string" + } + } + } + } + }, "/alerts": { "get": { "description": "Get a list of alerts", @@ -1266,6 +1593,39 @@ func init() { } } }, + "alertGroupInfo": { + "type": "object", + "required": [ + "labels", + "receiver", + "id" + ], + "properties": { + "id": { + "type": "string" + }, + "labels": { + "$ref": "#/definitions/labelSet" + }, + "receiver": { + "$ref": "#/definitions/receiver" + } + } + }, + "alertGroupInfoList": { + "type": "object", + "properties": { + "alertGroupInfoList": { + "type": "array", + "items": { + "$ref": "#/definitions/alertGroupInfo" + } + }, + "nextToken": { + "type": "string" + } + } + }, "alertGroups": { "type": "array", "items": { @@ -1417,6 +1777,20 @@ func init() { } ] }, + "gettableAlertInfos": { + "type": "object", + "properties": { + "alerts": { + "type": "array", + "items": { + "$ref": "#/definitions/gettableAlert" + } + }, + "nextToken": { + "type": "string" + } + } + }, "gettableAlerts": { "type": "array", "items": { diff --git a/api/v2/restapi/operations/alertgroupinfolist/get_alert_group_info_list.go b/api/v2/restapi/operations/alertgroupinfolist/get_alert_group_info_list.go new file mode 100644 index 0000000000..3820f73720 --- /dev/null +++ b/api/v2/restapi/operations/alertgroupinfolist/get_alert_group_info_list.go @@ -0,0 +1,70 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// Copyright Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package alertgroupinfolist + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// GetAlertGroupInfoListHandlerFunc turns a function with the right signature into a get alert group info list handler +type GetAlertGroupInfoListHandlerFunc func(GetAlertGroupInfoListParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetAlertGroupInfoListHandlerFunc) Handle(params GetAlertGroupInfoListParams) middleware.Responder { + return fn(params) +} + +// GetAlertGroupInfoListHandler interface for that can handle valid get alert group info list params +type GetAlertGroupInfoListHandler interface { + Handle(GetAlertGroupInfoListParams) middleware.Responder +} + +// NewGetAlertGroupInfoList creates a new http.Handler for the get alert group info list operation +func NewGetAlertGroupInfoList(ctx *middleware.Context, handler GetAlertGroupInfoListHandler) *GetAlertGroupInfoList { + return &GetAlertGroupInfoList{Context: ctx, Handler: handler} +} + +/* + GetAlertGroupInfoList swagger:route GET /alertgroups alertgroupinfolist getAlertGroupInfoList + +Get a list of alert groups information +*/ +type GetAlertGroupInfoList struct { + Context *middleware.Context + Handler GetAlertGroupInfoListHandler +} + +func (o *GetAlertGroupInfoList) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGetAlertGroupInfoListParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/api/v2/restapi/operations/alertgroupinfolist/get_alert_group_info_list_parameters.go b/api/v2/restapi/operations/alertgroupinfolist/get_alert_group_info_list_parameters.go new file mode 100644 index 0000000000..7c01e4aac2 --- /dev/null +++ b/api/v2/restapi/operations/alertgroupinfolist/get_alert_group_info_list_parameters.go @@ -0,0 +1,151 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// Copyright Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package alertgroupinfolist + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// NewGetAlertGroupInfoListParams creates a new GetAlertGroupInfoListParams object +// +// There are no default values defined in the spec. +func NewGetAlertGroupInfoListParams() GetAlertGroupInfoListParams { + + return GetAlertGroupInfoListParams{} +} + +// GetAlertGroupInfoListParams contains all the bound params for the get alert group info list operation +// typically these are obtained from a http.Request +// +// swagger:parameters getAlertGroupInfoList +type GetAlertGroupInfoListParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /*The maximum number of alert groups to return in one getAlertGroupInfoList operation. + In: query + */ + MaxResults *int64 + /*The token for the next set of items to return + In: query + */ + NextToken *string + /*A regex matching receivers to filter alerts by + In: query + */ + Receiver *string +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewGetAlertGroupInfoListParams() beforehand. +func (o *GetAlertGroupInfoListParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + qs := runtime.Values(r.URL.Query()) + + qMaxResults, qhkMaxResults, _ := qs.GetOK("maxResults") + if err := o.bindMaxResults(qMaxResults, qhkMaxResults, route.Formats); err != nil { + res = append(res, err) + } + + qNextToken, qhkNextToken, _ := qs.GetOK("nextToken") + if err := o.bindNextToken(qNextToken, qhkNextToken, route.Formats); err != nil { + res = append(res, err) + } + + qReceiver, qhkReceiver, _ := qs.GetOK("receiver") + if err := o.bindReceiver(qReceiver, qhkReceiver, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindMaxResults binds and validates parameter MaxResults from query. +func (o *GetAlertGroupInfoListParams) bindMaxResults(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("maxResults", "query", "int64", raw) + } + o.MaxResults = &value + + return nil +} + +// bindNextToken binds and validates parameter NextToken from query. +func (o *GetAlertGroupInfoListParams) bindNextToken(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.NextToken = &raw + + return nil +} + +// bindReceiver binds and validates parameter Receiver from query. +func (o *GetAlertGroupInfoListParams) bindReceiver(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.Receiver = &raw + + return nil +} diff --git a/api/v2/restapi/operations/alertgroupinfolist/get_alert_group_info_list_responses.go b/api/v2/restapi/operations/alertgroupinfolist/get_alert_group_info_list_responses.go new file mode 100644 index 0000000000..12b3a4a613 --- /dev/null +++ b/api/v2/restapi/operations/alertgroupinfolist/get_alert_group_info_list_responses.go @@ -0,0 +1,159 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// Copyright Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package alertgroupinfolist + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "github.com/prometheus/alertmanager/api/v2/models" +) + +// GetAlertGroupInfoListOKCode is the HTTP code returned for type GetAlertGroupInfoListOK +const GetAlertGroupInfoListOKCode int = 200 + +/* +GetAlertGroupInfoListOK Get alert groups info response + +swagger:response getAlertGroupInfoListOK +*/ +type GetAlertGroupInfoListOK struct { + + /* + In: Body + */ + Payload *models.AlertGroupInfoList `json:"body,omitempty"` +} + +// NewGetAlertGroupInfoListOK creates GetAlertGroupInfoListOK with default headers values +func NewGetAlertGroupInfoListOK() *GetAlertGroupInfoListOK { + + return &GetAlertGroupInfoListOK{} +} + +// WithPayload adds the payload to the get alert group info list o k response +func (o *GetAlertGroupInfoListOK) WithPayload(payload *models.AlertGroupInfoList) *GetAlertGroupInfoListOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get alert group info list o k response +func (o *GetAlertGroupInfoListOK) SetPayload(payload *models.AlertGroupInfoList) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetAlertGroupInfoListOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// GetAlertGroupInfoListBadRequestCode is the HTTP code returned for type GetAlertGroupInfoListBadRequest +const GetAlertGroupInfoListBadRequestCode int = 400 + +/* +GetAlertGroupInfoListBadRequest Bad request + +swagger:response getAlertGroupInfoListBadRequest +*/ +type GetAlertGroupInfoListBadRequest struct { + + /* + In: Body + */ + Payload string `json:"body,omitempty"` +} + +// NewGetAlertGroupInfoListBadRequest creates GetAlertGroupInfoListBadRequest with default headers values +func NewGetAlertGroupInfoListBadRequest() *GetAlertGroupInfoListBadRequest { + + return &GetAlertGroupInfoListBadRequest{} +} + +// WithPayload adds the payload to the get alert group info list bad request response +func (o *GetAlertGroupInfoListBadRequest) WithPayload(payload string) *GetAlertGroupInfoListBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get alert group info list bad request response +func (o *GetAlertGroupInfoListBadRequest) SetPayload(payload string) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetAlertGroupInfoListBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } +} + +// GetAlertGroupInfoListInternalServerErrorCode is the HTTP code returned for type GetAlertGroupInfoListInternalServerError +const GetAlertGroupInfoListInternalServerErrorCode int = 500 + +/* +GetAlertGroupInfoListInternalServerError Internal server error + +swagger:response getAlertGroupInfoListInternalServerError +*/ +type GetAlertGroupInfoListInternalServerError struct { + + /* + In: Body + */ + Payload string `json:"body,omitempty"` +} + +// NewGetAlertGroupInfoListInternalServerError creates GetAlertGroupInfoListInternalServerError with default headers values +func NewGetAlertGroupInfoListInternalServerError() *GetAlertGroupInfoListInternalServerError { + + return &GetAlertGroupInfoListInternalServerError{} +} + +// WithPayload adds the payload to the get alert group info list internal server error response +func (o *GetAlertGroupInfoListInternalServerError) WithPayload(payload string) *GetAlertGroupInfoListInternalServerError { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get alert group info list internal server error response +func (o *GetAlertGroupInfoListInternalServerError) SetPayload(payload string) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetAlertGroupInfoListInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(500) + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } +} diff --git a/api/v2/restapi/operations/alertgroupinfolist/get_alert_group_info_list_urlbuilder.go b/api/v2/restapi/operations/alertgroupinfolist/get_alert_group_info_list_urlbuilder.go new file mode 100644 index 0000000000..6224bb9da5 --- /dev/null +++ b/api/v2/restapi/operations/alertgroupinfolist/get_alert_group_info_list_urlbuilder.go @@ -0,0 +1,137 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// Copyright Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package alertgroupinfolist + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + + "github.com/go-openapi/swag" +) + +// GetAlertGroupInfoListURL generates an URL for the get alert group info list operation +type GetAlertGroupInfoListURL struct { + MaxResults *int64 + NextToken *string + Receiver *string + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetAlertGroupInfoListURL) WithBasePath(bp string) *GetAlertGroupInfoListURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetAlertGroupInfoListURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetAlertGroupInfoListURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/alertgroups" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v2/" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + qs := make(url.Values) + + var maxResultsQ string + if o.MaxResults != nil { + maxResultsQ = swag.FormatInt64(*o.MaxResults) + } + if maxResultsQ != "" { + qs.Set("maxResults", maxResultsQ) + } + + var nextTokenQ string + if o.NextToken != nil { + nextTokenQ = *o.NextToken + } + if nextTokenQ != "" { + qs.Set("nextToken", nextTokenQ) + } + + var receiverQ string + if o.Receiver != nil { + receiverQ = *o.Receiver + } + if receiverQ != "" { + qs.Set("receiver", receiverQ) + } + + _result.RawQuery = qs.Encode() + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *GetAlertGroupInfoListURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *GetAlertGroupInfoListURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetAlertGroupInfoListURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetAlertGroupInfoListURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetAlertGroupInfoListURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *GetAlertGroupInfoListURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/api/v2/restapi/operations/alertinfo/get_alert_infos.go b/api/v2/restapi/operations/alertinfo/get_alert_infos.go new file mode 100644 index 0000000000..66b5a29a48 --- /dev/null +++ b/api/v2/restapi/operations/alertinfo/get_alert_infos.go @@ -0,0 +1,70 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// Copyright Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package alertinfo + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// GetAlertInfosHandlerFunc turns a function with the right signature into a get alert infos handler +type GetAlertInfosHandlerFunc func(GetAlertInfosParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetAlertInfosHandlerFunc) Handle(params GetAlertInfosParams) middleware.Responder { + return fn(params) +} + +// GetAlertInfosHandler interface for that can handle valid get alert infos params +type GetAlertInfosHandler interface { + Handle(GetAlertInfosParams) middleware.Responder +} + +// NewGetAlertInfos creates a new http.Handler for the get alert infos operation +func NewGetAlertInfos(ctx *middleware.Context, handler GetAlertInfosHandler) *GetAlertInfos { + return &GetAlertInfos{Context: ctx, Handler: handler} +} + +/* + GetAlertInfos swagger:route GET /alertinfos alertinfo getAlertInfos + +Get a list of alert infos +*/ +type GetAlertInfos struct { + Context *middleware.Context + Handler GetAlertInfosHandler +} + +func (o *GetAlertInfos) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGetAlertInfosParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/api/v2/restapi/operations/alertinfo/get_alert_infos_parameters.go b/api/v2/restapi/operations/alertinfo/get_alert_infos_parameters.go new file mode 100644 index 0000000000..ea0c47ff24 --- /dev/null +++ b/api/v2/restapi/operations/alertinfo/get_alert_infos_parameters.go @@ -0,0 +1,369 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// Copyright Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package alertinfo + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// NewGetAlertInfosParams creates a new GetAlertInfosParams object +// with the default values initialized. +func NewGetAlertInfosParams() GetAlertInfosParams { + + var ( + // initialize parameters with default values + + activeDefault = bool(true) + + inhibitedDefault = bool(true) + + silencedDefault = bool(true) + unprocessedDefault = bool(true) + ) + + return GetAlertInfosParams{ + Active: &activeDefault, + + Inhibited: &inhibitedDefault, + + Silenced: &silencedDefault, + + Unprocessed: &unprocessedDefault, + } +} + +// GetAlertInfosParams contains all the bound params for the get alert infos operation +// typically these are obtained from a http.Request +// +// swagger:parameters getAlertInfos +type GetAlertInfosParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /*Show active alerts + In: query + Default: true + */ + Active *bool + /*A list of matchers to filter alerts by + In: query + Collection Format: multi + */ + Filter []string + /*A list of group IDs to filter alerts by + In: query + Collection Format: multi + */ + GroupID []string + /*Show inhibited alerts + In: query + Default: true + */ + Inhibited *bool + /*The maximum number of alert to return in one getAlertInfos operation. + In: query + */ + MaxResults *int64 + /*The token for the next set of items to return + In: query + */ + NextToken *string + /*A regex matching receivers to filter alerts by + In: query + */ + Receiver *string + /*Show silenced alerts + In: query + Default: true + */ + Silenced *bool + /*Show unprocessed alerts + In: query + Default: true + */ + Unprocessed *bool +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewGetAlertInfosParams() beforehand. +func (o *GetAlertInfosParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + qs := runtime.Values(r.URL.Query()) + + qActive, qhkActive, _ := qs.GetOK("active") + if err := o.bindActive(qActive, qhkActive, route.Formats); err != nil { + res = append(res, err) + } + + qFilter, qhkFilter, _ := qs.GetOK("filter") + if err := o.bindFilter(qFilter, qhkFilter, route.Formats); err != nil { + res = append(res, err) + } + + qGroupID, qhkGroupID, _ := qs.GetOK("groupId") + if err := o.bindGroupID(qGroupID, qhkGroupID, route.Formats); err != nil { + res = append(res, err) + } + + qInhibited, qhkInhibited, _ := qs.GetOK("inhibited") + if err := o.bindInhibited(qInhibited, qhkInhibited, route.Formats); err != nil { + res = append(res, err) + } + + qMaxResults, qhkMaxResults, _ := qs.GetOK("maxResults") + if err := o.bindMaxResults(qMaxResults, qhkMaxResults, route.Formats); err != nil { + res = append(res, err) + } + + qNextToken, qhkNextToken, _ := qs.GetOK("nextToken") + if err := o.bindNextToken(qNextToken, qhkNextToken, route.Formats); err != nil { + res = append(res, err) + } + + qReceiver, qhkReceiver, _ := qs.GetOK("receiver") + if err := o.bindReceiver(qReceiver, qhkReceiver, route.Formats); err != nil { + res = append(res, err) + } + + qSilenced, qhkSilenced, _ := qs.GetOK("silenced") + if err := o.bindSilenced(qSilenced, qhkSilenced, route.Formats); err != nil { + res = append(res, err) + } + + qUnprocessed, qhkUnprocessed, _ := qs.GetOK("unprocessed") + if err := o.bindUnprocessed(qUnprocessed, qhkUnprocessed, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindActive binds and validates parameter Active from query. +func (o *GetAlertInfosParams) bindActive(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + // Default values have been previously initialized by NewGetAlertInfosParams() + return nil + } + + value, err := swag.ConvertBool(raw) + if err != nil { + return errors.InvalidType("active", "query", "bool", raw) + } + o.Active = &value + + return nil +} + +// bindFilter binds and validates array parameter Filter from query. +// +// Arrays are parsed according to CollectionFormat: "multi" (defaults to "csv" when empty). +func (o *GetAlertInfosParams) bindFilter(rawData []string, hasKey bool, formats strfmt.Registry) error { + // CollectionFormat: multi + filterIC := rawData + if len(filterIC) == 0 { + return nil + } + + var filterIR []string + for _, filterIV := range filterIC { + filterI := filterIV + + filterIR = append(filterIR, filterI) + } + + o.Filter = filterIR + + return nil +} + +// bindGroupID binds and validates array parameter GroupID from query. +// +// Arrays are parsed according to CollectionFormat: "multi" (defaults to "csv" when empty). +func (o *GetAlertInfosParams) bindGroupID(rawData []string, hasKey bool, formats strfmt.Registry) error { + // CollectionFormat: multi + groupIDIC := rawData + if len(groupIDIC) == 0 { + return nil + } + + var groupIDIR []string + for _, groupIDIV := range groupIDIC { + groupIDI := groupIDIV + + groupIDIR = append(groupIDIR, groupIDI) + } + + o.GroupID = groupIDIR + + return nil +} + +// bindInhibited binds and validates parameter Inhibited from query. +func (o *GetAlertInfosParams) bindInhibited(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + // Default values have been previously initialized by NewGetAlertInfosParams() + return nil + } + + value, err := swag.ConvertBool(raw) + if err != nil { + return errors.InvalidType("inhibited", "query", "bool", raw) + } + o.Inhibited = &value + + return nil +} + +// bindMaxResults binds and validates parameter MaxResults from query. +func (o *GetAlertInfosParams) bindMaxResults(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("maxResults", "query", "int64", raw) + } + o.MaxResults = &value + + return nil +} + +// bindNextToken binds and validates parameter NextToken from query. +func (o *GetAlertInfosParams) bindNextToken(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.NextToken = &raw + + return nil +} + +// bindReceiver binds and validates parameter Receiver from query. +func (o *GetAlertInfosParams) bindReceiver(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.Receiver = &raw + + return nil +} + +// bindSilenced binds and validates parameter Silenced from query. +func (o *GetAlertInfosParams) bindSilenced(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + // Default values have been previously initialized by NewGetAlertInfosParams() + return nil + } + + value, err := swag.ConvertBool(raw) + if err != nil { + return errors.InvalidType("silenced", "query", "bool", raw) + } + o.Silenced = &value + + return nil +} + +// bindUnprocessed binds and validates parameter Unprocessed from query. +func (o *GetAlertInfosParams) bindUnprocessed(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + // Default values have been previously initialized by NewGetAlertInfosParams() + return nil + } + + value, err := swag.ConvertBool(raw) + if err != nil { + return errors.InvalidType("unprocessed", "query", "bool", raw) + } + o.Unprocessed = &value + + return nil +} diff --git a/api/v2/restapi/operations/alertinfo/get_alert_infos_responses.go b/api/v2/restapi/operations/alertinfo/get_alert_infos_responses.go new file mode 100644 index 0000000000..e0ff2075c3 --- /dev/null +++ b/api/v2/restapi/operations/alertinfo/get_alert_infos_responses.go @@ -0,0 +1,159 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// Copyright Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package alertinfo + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "github.com/prometheus/alertmanager/api/v2/models" +) + +// GetAlertInfosOKCode is the HTTP code returned for type GetAlertInfosOK +const GetAlertInfosOKCode int = 200 + +/* +GetAlertInfosOK Get alerts response + +swagger:response getAlertInfosOK +*/ +type GetAlertInfosOK struct { + + /* + In: Body + */ + Payload *models.GettableAlertInfos `json:"body,omitempty"` +} + +// NewGetAlertInfosOK creates GetAlertInfosOK with default headers values +func NewGetAlertInfosOK() *GetAlertInfosOK { + + return &GetAlertInfosOK{} +} + +// WithPayload adds the payload to the get alert infos o k response +func (o *GetAlertInfosOK) WithPayload(payload *models.GettableAlertInfos) *GetAlertInfosOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get alert infos o k response +func (o *GetAlertInfosOK) SetPayload(payload *models.GettableAlertInfos) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetAlertInfosOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// GetAlertInfosBadRequestCode is the HTTP code returned for type GetAlertInfosBadRequest +const GetAlertInfosBadRequestCode int = 400 + +/* +GetAlertInfosBadRequest Bad request + +swagger:response getAlertInfosBadRequest +*/ +type GetAlertInfosBadRequest struct { + + /* + In: Body + */ + Payload string `json:"body,omitempty"` +} + +// NewGetAlertInfosBadRequest creates GetAlertInfosBadRequest with default headers values +func NewGetAlertInfosBadRequest() *GetAlertInfosBadRequest { + + return &GetAlertInfosBadRequest{} +} + +// WithPayload adds the payload to the get alert infos bad request response +func (o *GetAlertInfosBadRequest) WithPayload(payload string) *GetAlertInfosBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get alert infos bad request response +func (o *GetAlertInfosBadRequest) SetPayload(payload string) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetAlertInfosBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } +} + +// GetAlertInfosInternalServerErrorCode is the HTTP code returned for type GetAlertInfosInternalServerError +const GetAlertInfosInternalServerErrorCode int = 500 + +/* +GetAlertInfosInternalServerError Internal server error + +swagger:response getAlertInfosInternalServerError +*/ +type GetAlertInfosInternalServerError struct { + + /* + In: Body + */ + Payload string `json:"body,omitempty"` +} + +// NewGetAlertInfosInternalServerError creates GetAlertInfosInternalServerError with default headers values +func NewGetAlertInfosInternalServerError() *GetAlertInfosInternalServerError { + + return &GetAlertInfosInternalServerError{} +} + +// WithPayload adds the payload to the get alert infos internal server error response +func (o *GetAlertInfosInternalServerError) WithPayload(payload string) *GetAlertInfosInternalServerError { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get alert infos internal server error response +func (o *GetAlertInfosInternalServerError) SetPayload(payload string) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetAlertInfosInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(500) + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } +} diff --git a/api/v2/restapi/operations/alertinfo/get_alert_infos_urlbuilder.go b/api/v2/restapi/operations/alertinfo/get_alert_infos_urlbuilder.go new file mode 100644 index 0000000000..b22a5fb658 --- /dev/null +++ b/api/v2/restapi/operations/alertinfo/get_alert_infos_urlbuilder.go @@ -0,0 +1,203 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// Copyright Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package alertinfo + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + + "github.com/go-openapi/swag" +) + +// GetAlertInfosURL generates an URL for the get alert infos operation +type GetAlertInfosURL struct { + Active *bool + Filter []string + GroupID []string + Inhibited *bool + MaxResults *int64 + NextToken *string + Receiver *string + Silenced *bool + Unprocessed *bool + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetAlertInfosURL) WithBasePath(bp string) *GetAlertInfosURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetAlertInfosURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetAlertInfosURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/alertinfos" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v2/" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + qs := make(url.Values) + + var activeQ string + if o.Active != nil { + activeQ = swag.FormatBool(*o.Active) + } + if activeQ != "" { + qs.Set("active", activeQ) + } + + var filterIR []string + for _, filterI := range o.Filter { + filterIS := filterI + if filterIS != "" { + filterIR = append(filterIR, filterIS) + } + } + + filter := swag.JoinByFormat(filterIR, "multi") + + for _, qsv := range filter { + qs.Add("filter", qsv) + } + + var groupIDIR []string + for _, groupIDI := range o.GroupID { + groupIDIS := groupIDI + if groupIDIS != "" { + groupIDIR = append(groupIDIR, groupIDIS) + } + } + + groupID := swag.JoinByFormat(groupIDIR, "multi") + + for _, qsv := range groupID { + qs.Add("groupId", qsv) + } + + var inhibitedQ string + if o.Inhibited != nil { + inhibitedQ = swag.FormatBool(*o.Inhibited) + } + if inhibitedQ != "" { + qs.Set("inhibited", inhibitedQ) + } + + var maxResultsQ string + if o.MaxResults != nil { + maxResultsQ = swag.FormatInt64(*o.MaxResults) + } + if maxResultsQ != "" { + qs.Set("maxResults", maxResultsQ) + } + + var nextTokenQ string + if o.NextToken != nil { + nextTokenQ = *o.NextToken + } + if nextTokenQ != "" { + qs.Set("nextToken", nextTokenQ) + } + + var receiverQ string + if o.Receiver != nil { + receiverQ = *o.Receiver + } + if receiverQ != "" { + qs.Set("receiver", receiverQ) + } + + var silencedQ string + if o.Silenced != nil { + silencedQ = swag.FormatBool(*o.Silenced) + } + if silencedQ != "" { + qs.Set("silenced", silencedQ) + } + + var unprocessedQ string + if o.Unprocessed != nil { + unprocessedQ = swag.FormatBool(*o.Unprocessed) + } + if unprocessedQ != "" { + qs.Set("unprocessed", unprocessedQ) + } + + _result.RawQuery = qs.Encode() + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *GetAlertInfosURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *GetAlertInfosURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetAlertInfosURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetAlertInfosURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetAlertInfosURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *GetAlertInfosURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/api/v2/restapi/operations/alertmanager_api.go b/api/v2/restapi/operations/alertmanager_api.go index e28c76b32e..7c6bd2f1da 100644 --- a/api/v2/restapi/operations/alertmanager_api.go +++ b/api/v2/restapi/operations/alertmanager_api.go @@ -35,6 +35,8 @@ import ( "github.com/prometheus/alertmanager/api/v2/restapi/operations/alert" "github.com/prometheus/alertmanager/api/v2/restapi/operations/alertgroup" + "github.com/prometheus/alertmanager/api/v2/restapi/operations/alertgroupinfolist" + "github.com/prometheus/alertmanager/api/v2/restapi/operations/alertinfo" "github.com/prometheus/alertmanager/api/v2/restapi/operations/general" "github.com/prometheus/alertmanager/api/v2/restapi/operations/receiver" "github.com/prometheus/alertmanager/api/v2/restapi/operations/silence" @@ -65,9 +67,15 @@ func NewAlertmanagerAPI(spec *loads.Document) *AlertmanagerAPI { SilenceDeleteSilenceHandler: silence.DeleteSilenceHandlerFunc(func(params silence.DeleteSilenceParams) middleware.Responder { return middleware.NotImplemented("operation silence.DeleteSilence has not yet been implemented") }), + AlertgroupinfolistGetAlertGroupInfoListHandler: alertgroupinfolist.GetAlertGroupInfoListHandlerFunc(func(params alertgroupinfolist.GetAlertGroupInfoListParams) middleware.Responder { + return middleware.NotImplemented("operation alertgroupinfolist.GetAlertGroupInfoList has not yet been implemented") + }), AlertgroupGetAlertGroupsHandler: alertgroup.GetAlertGroupsHandlerFunc(func(params alertgroup.GetAlertGroupsParams) middleware.Responder { return middleware.NotImplemented("operation alertgroup.GetAlertGroups has not yet been implemented") }), + AlertinfoGetAlertInfosHandler: alertinfo.GetAlertInfosHandlerFunc(func(params alertinfo.GetAlertInfosParams) middleware.Responder { + return middleware.NotImplemented("operation alertinfo.GetAlertInfos has not yet been implemented") + }), AlertGetAlertsHandler: alert.GetAlertsHandlerFunc(func(params alert.GetAlertsParams) middleware.Responder { return middleware.NotImplemented("operation alert.GetAlerts has not yet been implemented") }), @@ -127,8 +135,12 @@ type AlertmanagerAPI struct { // SilenceDeleteSilenceHandler sets the operation handler for the delete silence operation SilenceDeleteSilenceHandler silence.DeleteSilenceHandler + // AlertgroupinfolistGetAlertGroupInfoListHandler sets the operation handler for the get alert group info list operation + AlertgroupinfolistGetAlertGroupInfoListHandler alertgroupinfolist.GetAlertGroupInfoListHandler // AlertgroupGetAlertGroupsHandler sets the operation handler for the get alert groups operation AlertgroupGetAlertGroupsHandler alertgroup.GetAlertGroupsHandler + // AlertinfoGetAlertInfosHandler sets the operation handler for the get alert infos operation + AlertinfoGetAlertInfosHandler alertinfo.GetAlertInfosHandler // AlertGetAlertsHandler sets the operation handler for the get alerts operation AlertGetAlertsHandler alert.GetAlertsHandler // ReceiverGetReceiversHandler sets the operation handler for the get receivers operation @@ -223,9 +235,15 @@ func (o *AlertmanagerAPI) Validate() error { if o.SilenceDeleteSilenceHandler == nil { unregistered = append(unregistered, "silence.DeleteSilenceHandler") } + if o.AlertgroupinfolistGetAlertGroupInfoListHandler == nil { + unregistered = append(unregistered, "alertgroupinfolist.GetAlertGroupInfoListHandler") + } if o.AlertgroupGetAlertGroupsHandler == nil { unregistered = append(unregistered, "alertgroup.GetAlertGroupsHandler") } + if o.AlertinfoGetAlertInfosHandler == nil { + unregistered = append(unregistered, "alertinfo.GetAlertInfosHandler") + } if o.AlertGetAlertsHandler == nil { unregistered = append(unregistered, "alert.GetAlertsHandler") } @@ -342,10 +360,18 @@ func (o *AlertmanagerAPI) initHandlerCache() { if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) } + o.handlers["GET"]["/alertgroups"] = alertgroupinfolist.NewGetAlertGroupInfoList(o.context, o.AlertgroupinfolistGetAlertGroupInfoListHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } o.handlers["GET"]["/alerts/groups"] = alertgroup.NewGetAlertGroups(o.context, o.AlertgroupGetAlertGroupsHandler) if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) } + o.handlers["GET"]["/alertinfos"] = alertinfo.NewGetAlertInfos(o.context, o.AlertinfoGetAlertInfosHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } o.handlers["GET"]["/alerts"] = alert.NewGetAlerts(o.context, o.AlertGetAlertsHandler) if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) diff --git a/api/v2/testing.go b/api/v2/testing.go index 7665a19f67..2f1046c3d8 100644 --- a/api/v2/testing.go +++ b/api/v2/testing.go @@ -14,9 +14,16 @@ package v2 import ( + "encoding/json" "testing" "time" + "github.com/prometheus/common/model" + "github.com/stretchr/testify/require" + + "github.com/prometheus/alertmanager/provider" + "github.com/prometheus/alertmanager/types" + "github.com/go-openapi/strfmt" open_api_models "github.com/prometheus/alertmanager/api/v2/models" @@ -63,3 +70,87 @@ func createLabelMatcher(t *testing.T, name, value string, matchType labels.Match matcher, _ := labels.NewMatcher(matchType, name, value) return matcher } + +// fakeAlerts is a struct implementing the provider.Alerts interface for tests. +type fakeAlerts struct { + fps map[model.Fingerprint]int + alerts []*types.Alert + err error +} + +func newFakeAlerts(alerts []*types.Alert) *fakeAlerts { + fps := make(map[model.Fingerprint]int) + for i, a := range alerts { + fps[a.Fingerprint()] = i + } + f := &fakeAlerts{ + alerts: alerts, + fps: fps, + } + return f +} + +func (f *fakeAlerts) Subscribe() provider.AlertIterator { return nil } +func (f *fakeAlerts) Get(model.Fingerprint) (*types.Alert, error) { return nil, nil } +func (f *fakeAlerts) Put(alerts ...*types.Alert) error { + return f.err +} + +func (f *fakeAlerts) GetPending() provider.AlertIterator { + ch := make(chan *types.Alert) + done := make(chan struct{}) + go func() { + defer close(ch) + for _, a := range f.alerts { + ch <- a + } + }() + return provider.NewAlertIterator(ch, done, f.err) +} + +func newGetAlertStatus(f *fakeAlerts) func(model.Fingerprint) types.AlertStatus { + return func(fp model.Fingerprint) types.AlertStatus { + status := types.AlertStatus{SilencedBy: []string{}, InhibitedBy: []string{}} + + i, ok := f.fps[fp] + if !ok { + return status + } + alert := f.alerts[i] + switch alert.Labels["state"] { + case "active": + status.State = types.AlertStateActive + case "unprocessed": + status.State = types.AlertStateUnprocessed + case "suppressed": + status.State = types.AlertStateSuppressed + } + if alert.Labels["silenced_by"] != "" { + status.SilencedBy = append(status.SilencedBy, string(alert.Labels["silenced_by"])) + } + if alert.Labels["inhibited_by"] != "" { + status.InhibitedBy = append(status.InhibitedBy, string(alert.Labels["inhibited_by"])) + } + return status + } +} + +func createAlert(t *testing.T, start, ends time.Time) (open_api_models.PostableAlerts, []byte) { + startsAt := strfmt.DateTime(start) + endsAt := strfmt.DateTime(ends) + + alert := open_api_models.PostableAlert{ + StartsAt: startsAt, + EndsAt: endsAt, + Annotations: open_api_models.LabelSet{"annotation1": "some text"}, + Alert: open_api_models.Alert{ + Labels: open_api_models.LabelSet{"label1": "test1"}, + GeneratorURL: "http://localhost:3000", + }, + } + alerts := open_api_models.PostableAlerts{} + alerts = append(alerts, &alert) + b, err := json.Marshal(alerts) + require.NoError(t, err) + return alerts, b +} diff --git a/cmd/alertmanager/main.go b/cmd/alertmanager/main.go index 87cdab8a09..c394673060 100644 --- a/cmd/alertmanager/main.go +++ b/cmd/alertmanager/main.go @@ -17,6 +17,8 @@ import ( "context" "errors" "fmt" + "github.com/prometheus/alertmanager/secrets" + "github.com/prometheus/alertmanager/secrets/providers" "log/slog" "net" "net/http" @@ -158,10 +160,10 @@ func run() int { httpTimeout = kingpin.Flag("web.timeout", "Timeout for HTTP requests. If negative or zero, no timeout is set.").Default("0").Duration() memlimitRatio = kingpin.Flag("auto-gomemlimit.ratio", "The ratio of reserved GOMEMLIMIT memory to the detected maximum container or system memory. The value must be greater than 0 and less than or equal to 1."). - Default("0.9").Float64() + Default("0.9").Float64() clusterBindAddr = kingpin.Flag("cluster.listen-address", "Listen address for cluster. Set to empty string to disable HA mode."). - Default(defaultClusterAddr).String() + Default(defaultClusterAddr).String() clusterAdvertiseAddr = kingpin.Flag("cluster.advertise-address", "Explicit address to advertise in cluster.").String() peers = kingpin.Flag("cluster.peer", "Initial peers (may be repeated).").Strings() peerTimeout = kingpin.Flag("cluster.peer-timeout", "Time to wait between peers to send notifications.").Default("15s").Duration() @@ -354,8 +356,8 @@ func run() int { disp.Stop() }() - groupFn := func(routeFilter func(*dispatch.Route) bool, alertFilter func(*types.Alert, time.Time) bool) (dispatch.AlertGroups, map[model.Fingerprint][]string) { - return disp.Groups(routeFilter, alertFilter) + groupFn := func(routeFilter func(*dispatch.Route) bool, alertFilter func(*types.Alert, time.Time) bool, groupIdsFilter func(string) bool) (dispatch.AlertGroups, map[model.Fingerprint][]string) { + return disp.Groups(routeFilter, alertFilter, groupIdsFilter) } // An interface value that holds a nil concrete value is non-nil. @@ -402,8 +404,9 @@ func run() int { } var ( - inhibitor *inhibit.Inhibitor - tmpl *template.Template + inhibitor *inhibit.Inhibitor + tmpl *template.Template + secretsProviderRegistry *secrets.SecretsProviderRegistry ) dispMetrics := dispatch.NewDispatcherMetrics(false, prometheus.DefaultRegisterer) @@ -414,6 +417,9 @@ func run() int { prometheus.DefaultRegisterer, configLogger, ) + defer func() { + secretsProviderRegistry.Stop() + }() configCoordinator.Subscribe(func(conf *config.Config) error { tmpl, err = template.FromGlobs(conf.Templates) if err != nil { @@ -428,6 +434,10 @@ func run() int { activeReceivers[r.RouteOpts.Receiver] = struct{}{} }) + spRegistry := secrets.NewSecretsProviderRegistry(logger, prometheus.NewRegistry()) + // currently only one secrets providers is supported + spRegistry.Register(providers.AWSSecretsManagerSecretProviderDiscoveryConfig{}) + spRegistry.Init() // Build the map of receiver to integrations. receivers := make(map[string][]notify.Integration, len(activeReceivers)) var integrationsNum int @@ -437,7 +447,7 @@ func run() int { configLogger.Info("skipping creation of receiver not referenced by any route", "receiver", rcv.Name) continue } - integrations, err := receiver.BuildReceiverIntegrations(rcv, tmpl, logger) + integrations, err := receiver.BuildReceiverIntegrations(rcv, tmpl, logger, spRegistry) if err != nil { return err } @@ -460,10 +470,13 @@ func run() int { inhibitor.Stop() disp.Stop() + if secretsProviderRegistry != nil { + secretsProviderRegistry.Stop() + } inhibitor = inhibit.NewInhibitor(alerts, conf.InhibitRules, marker, logger) silencer := silence.NewSilencer(silences, marker, logger) - + secretsProviderRegistry = spRegistry // An interface value that holds a nil concrete value is non-nil. // Therefore we explicly pass an empty interface, to detect if the // cluster is not enabled in notify. @@ -481,6 +494,7 @@ func run() int { marker, notificationLog, pipelinePeer, + nil, ) configuredReceivers.Set(float64(len(activeReceivers))) @@ -492,7 +506,7 @@ func run() int { silencer.Mutes(labels) }) - disp = dispatch.NewDispatcher(alerts, routes, pipeline, marker, timeoutFunc, nil, logger, dispMetrics) + disp = dispatch.NewDispatcher(alerts, routes, pipeline, marker, timeoutFunc, nil, logger, dispMetrics, nil) routes.Walk(func(r *dispatch.Route) { if r.RouteOpts.RepeatInterval > *retention { configLogger.Warn( diff --git a/config/notifiers.go b/config/notifiers.go index 87f806aa27..ff3a0d1394 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -16,6 +16,7 @@ package config import ( "errors" "fmt" + "github.com/prometheus/alertmanager/secrets" "net/textproto" "regexp" "strings" @@ -328,22 +329,22 @@ type PagerdutyConfig struct { HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"` - ServiceKey Secret `yaml:"service_key,omitempty" json:"service_key,omitempty"` - ServiceKeyFile string `yaml:"service_key_file,omitempty" json:"service_key_file,omitempty"` - RoutingKey Secret `yaml:"routing_key,omitempty" json:"routing_key,omitempty"` - RoutingKeyFile string `yaml:"routing_key_file,omitempty" json:"routing_key_file,omitempty"` - URL *URL `yaml:"url,omitempty" json:"url,omitempty"` - Client string `yaml:"client,omitempty" json:"client,omitempty"` - ClientURL string `yaml:"client_url,omitempty" json:"client_url,omitempty"` - Description string `yaml:"description,omitempty" json:"description,omitempty"` - Details map[string]string `yaml:"details,omitempty" json:"details,omitempty"` - Images []PagerdutyImage `yaml:"images,omitempty" json:"images,omitempty"` - Links []PagerdutyLink `yaml:"links,omitempty" json:"links,omitempty"` - Source string `yaml:"source,omitempty" json:"source,omitempty"` - Severity string `yaml:"severity,omitempty" json:"severity,omitempty"` - Class string `yaml:"class,omitempty" json:"class,omitempty"` - Component string `yaml:"component,omitempty" json:"component,omitempty"` - Group string `yaml:"group,omitempty" json:"group,omitempty"` + ServiceKey *secrets.GenericSecret `yaml:"service_key,omitempty" json:"service_key,omitempty"` + ServiceKeyFile string `yaml:"service_key_file,omitempty" json:"service_key_file,omitempty"` + RoutingKey *secrets.GenericSecret `yaml:"routing_key,omitempty" json:"routing_key,omitempty"` + RoutingKeyFile string `yaml:"routing_key_file,omitempty" json:"routing_key_file,omitempty"` + URL *URL `yaml:"url,omitempty" json:"url,omitempty"` + Client string `yaml:"client,omitempty" json:"client,omitempty"` + ClientURL string `yaml:"client_url,omitempty" json:"client_url,omitempty"` + Description string `yaml:"description,omitempty" json:"description,omitempty"` + Details map[string]string `yaml:"details,omitempty" json:"details,omitempty"` + Images []PagerdutyImage `yaml:"images,omitempty" json:"images,omitempty"` + Links []PagerdutyLink `yaml:"links,omitempty" json:"links,omitempty"` + Source string `yaml:"source,omitempty" json:"source,omitempty"` + Severity string `yaml:"severity,omitempty" json:"severity,omitempty"` + Class string `yaml:"class,omitempty" json:"class,omitempty"` + Component string `yaml:"component,omitempty" json:"component,omitempty"` + Group string `yaml:"group,omitempty" json:"group,omitempty"` } // PagerdutyLink is a link. @@ -366,13 +367,13 @@ func (c *PagerdutyConfig) UnmarshalYAML(unmarshal func(interface{}) error) error if err := unmarshal((*plain)(c)); err != nil { return err } - if c.RoutingKey == "" && c.ServiceKey == "" && c.RoutingKeyFile == "" && c.ServiceKeyFile == "" { + if c.RoutingKey == nil && c.ServiceKey == nil && c.RoutingKeyFile == "" && c.ServiceKeyFile == "" { return errors.New("missing service or routing key in PagerDuty config") } - if len(c.RoutingKey) > 0 && len(c.RoutingKeyFile) > 0 { + if c.RoutingKey != nil && len(c.RoutingKeyFile) > 0 { return errors.New("at most one of routing_key & routing_key_file must be configured") } - if len(c.ServiceKey) > 0 && len(c.ServiceKeyFile) > 0 { + if c.ServiceKey != nil && len(c.ServiceKeyFile) > 0 { return errors.New("at most one of service_key & service_key_file must be configured") } if c.Details == nil { diff --git a/config/receiver/receiver.go b/config/receiver/receiver.go index d92a19a4c5..33a85850a3 100644 --- a/config/receiver/receiver.go +++ b/config/receiver/receiver.go @@ -14,6 +14,7 @@ package receiver import ( + "github.com/prometheus/alertmanager/secrets" "log/slog" commoncfg "github.com/prometheus/common/config" @@ -43,7 +44,7 @@ import ( // BuildReceiverIntegrations builds a list of integration notifiers off of a // receiver config. -func BuildReceiverIntegrations(nc config.Receiver, tmpl *template.Template, logger *slog.Logger, httpOpts ...commoncfg.HTTPClientOption) ([]notify.Integration, error) { +func BuildReceiverIntegrations(nc config.Receiver, tmpl *template.Template, logger *slog.Logger, spRegistry *secrets.SecretsProviderRegistry, httpOpts ...commoncfg.HTTPClientOption) ([]notify.Integration, error) { if logger == nil { logger = promslog.NewNopLogger() } @@ -68,7 +69,9 @@ func BuildReceiverIntegrations(nc config.Receiver, tmpl *template.Template, logg add("email", i, c, func(l *slog.Logger) (notify.Notifier, error) { return email.New(c, tmpl, l), nil }) } for i, c := range nc.PagerdutyConfigs { - add("pagerduty", i, c, func(l *slog.Logger) (notify.Notifier, error) { return pagerduty.New(c, tmpl, l, httpOpts...) }) + add("pagerduty", i, c, func(l *slog.Logger) (notify.Notifier, error) { + return pagerduty.New(c, tmpl, l, spRegistry, httpOpts...) + }) } for i, c := range nc.OpsGenieConfigs { add("opsgenie", i, c, func(l *slog.Logger) (notify.Notifier, error) { return opsgenie.New(c, tmpl, l, httpOpts...) }) diff --git a/dispatch/dispatch.go b/dispatch/dispatch.go index 6883786dca..455ecec652 100644 --- a/dispatch/dispatch.go +++ b/dispatch/dispatch.go @@ -15,7 +15,10 @@ package dispatch import ( "context" + "errors" + + "crypto/sha1" "fmt" "log/slog" "sort" @@ -25,6 +28,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" + "github.com/prometheus/alertmanager/alertobserver" "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/provider" "github.com/prometheus/alertmanager/store" @@ -91,7 +95,8 @@ type Dispatcher struct { ctx context.Context cancel func() - logger *slog.Logger + logger *slog.Logger + alertLCObserver alertobserver.LifeCycleObserver } // Limits describes limits used by Dispatcher. @@ -112,20 +117,22 @@ func NewDispatcher( lim Limits, l *slog.Logger, m *DispatcherMetrics, + o alertobserver.LifeCycleObserver, ) *Dispatcher { if lim == nil { lim = nilLimits{} } disp := &Dispatcher{ - alerts: ap, - stage: s, - route: r, - marker: mk, - timeout: to, - logger: l.With("component", "dispatcher"), - metrics: m, - limits: lim, + alerts: ap, + stage: s, + route: r, + marker: mk, + timeout: to, + logger: l.With("component", "dispatcher"), + metrics: m, + limits: lim, + alertLCObserver: o, } return disp } @@ -221,7 +228,7 @@ func (ag AlertGroups) Less(i, j int) bool { func (ag AlertGroups) Len() int { return len(ag) } // Groups returns a slice of AlertGroups from the dispatcher's internal state. -func (d *Dispatcher) Groups(routeFilter func(*Route) bool, alertFilter func(*types.Alert, time.Time) bool) (AlertGroups, map[model.Fingerprint][]string) { +func (d *Dispatcher) Groups(routeFilter func(*Route) bool, alertFilter func(*types.Alert, time.Time) bool, groupIDFilter func(groupId string) bool) (AlertGroups, map[model.Fingerprint][]string) { groups := AlertGroups{} d.mtx.RLock() @@ -239,6 +246,9 @@ func (d *Dispatcher) Groups(routeFilter func(*Route) bool, alertFilter func(*typ } for _, ag := range ags { + if !groupIDFilter(ag.GroupID()) { + continue + } receiver := route.RouteOpts.Receiver alertGroup := &AlertGroup{ Labels: ag.labels, @@ -286,6 +296,48 @@ func (d *Dispatcher) Groups(routeFilter func(*Route) bool, alertFilter func(*typ return groups, receivers } +// AlertGroupInfo represents the aggrGroup information. +type AlertGroupInfo struct { + Labels model.LabelSet + Receiver string + ID string +} + +type AlertGroupInfos []*AlertGroupInfo + +func (ag AlertGroupInfos) Swap(i, j int) { ag[i], ag[j] = ag[j], ag[i] } +func (ag AlertGroupInfos) Less(i, j int) bool { + return ag[i].ID < ag[j].ID +} +func (ag AlertGroupInfos) Len() int { return len(ag) } + +func (d *Dispatcher) GroupInfos(routeFilter func(*Route) bool) AlertGroupInfos { + groups := AlertGroupInfos{} + + d.mtx.RLock() + defer d.mtx.RUnlock() + + for route, ags := range d.aggrGroupsPerRoute { + if !routeFilter(route) { + continue + } + + for _, ag := range ags { + receiver := route.RouteOpts.Receiver + alertGroup := &AlertGroupInfo{ + Labels: ag.labels, + Receiver: receiver, + ID: ag.GroupID(), + } + + groups = append(groups, alertGroup) + } + } + sort.Sort(groups) + + return groups +} + // Stop the dispatcher. func (d *Dispatcher) Stop() { if d == nil { @@ -327,13 +379,25 @@ func (d *Dispatcher) processAlert(alert *types.Alert, route *Route) { ag, ok := routeGroups[fp] if ok { ag.insert(alert) + if d.alertLCObserver != nil { + m := alertobserver.AlertEventMeta{ + "groupKey": ag.GroupKey(), + "routeId": ag.routeID, + "groupId": ag.GroupID(), + } + d.alertLCObserver.Observe(alertobserver.EventAlertAddedToAggrGroup, []*types.Alert{alert}, m) + } return } // If the group does not exist, create it. But check the limit first. if limit := d.limits.MaxNumberOfAggregationGroups(); limit > 0 && d.aggrGroupsNum >= limit { d.metrics.aggrGroupLimitReached.Inc() - d.logger.Error("Too many aggregation groups, cannot create new group for alert", "groups", d.aggrGroupsNum, "limit", limit, "alert", alert.Name()) + errMsg := "Too many aggregation groups, cannot create new group for alert" + d.logger.Error(errMsg, "groups", d.aggrGroupsNum, "limit", limit, "alert", alert.Name()) + if d.alertLCObserver != nil { + d.alertLCObserver.Observe(alertobserver.EventAlertFailedAddToAggrGroup, []*types.Alert{alert}, alertobserver.AlertEventMeta{"msg": errMsg}) + } return } @@ -341,6 +405,14 @@ func (d *Dispatcher) processAlert(alert *types.Alert, route *Route) { routeGroups[fp] = ag d.aggrGroupsNum++ d.metrics.aggrGroups.Inc() + if d.alertLCObserver != nil { + m := alertobserver.AlertEventMeta{ + "groupKey": ag.GroupKey(), + "routeId": ag.routeID, + "groupId": ag.GroupID(), + } + d.alertLCObserver.Observe(alertobserver.EventAlertAddedToAggrGroup, []*types.Alert{alert}, m) + } // Insert the 1st alert in the group before starting the group's run() // function, to make sure that when the run() will be executed the 1st @@ -425,6 +497,12 @@ func (ag *aggrGroup) fingerprint() model.Fingerprint { return ag.labels.Fingerprint() } +func (ag *aggrGroup) GroupID() string { + h := sha1.New() + h.Write([]byte(fmt.Sprintf("%s:%s", ag.routeID, ag.labels))) + return fmt.Sprintf("%x", h.Sum(nil)) +} + func (ag *aggrGroup) GroupKey() string { return fmt.Sprintf("%s:%s", ag.routeKey, ag.labels) } @@ -452,6 +530,7 @@ func (ag *aggrGroup) run(nf notifyFunc) { // Populate context with information needed along the pipeline. ctx = notify.WithGroupKey(ctx, ag.GroupKey()) + ctx = notify.WithGroupId(ctx, ag.GroupID()) ctx = notify.WithGroupLabels(ctx, ag.labels) ctx = notify.WithReceiverName(ctx, ag.opts.Receiver) ctx = notify.WithRepeatInterval(ctx, ag.opts.RepeatInterval) diff --git a/dispatch/dispatch_test.go b/dispatch/dispatch_test.go index 913032c6ce..0791b6d887 100644 --- a/dispatch/dispatch_test.go +++ b/dispatch/dispatch_test.go @@ -29,6 +29,7 @@ import ( "github.com/prometheus/common/promslog" "github.com/stretchr/testify/require" + "github.com/prometheus/alertmanager/alertobserver" "github.com/prometheus/alertmanager/config" "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/provider/mem" @@ -108,6 +109,9 @@ func TestAggrGroup(t *testing.T) { if _, ok := notify.GroupKey(ctx); !ok { t.Errorf("group key missing") } + if _, ok := notify.GroupId(ctx); !ok { + t.Errorf("group id missing") + } if lbls, ok := notify.GroupLabels(ctx); !ok || !reflect.DeepEqual(lbls, lset) { t.Errorf("wrong group labels: %q", lbls) } @@ -399,7 +403,7 @@ route: timeout := func(d time.Duration) time.Duration { return time.Duration(0) } recorder := &recordStage{alerts: make(map[string]map[model.Fingerprint]*types.Alert)} - dispatcher := NewDispatcher(alerts, route, recorder, marker, timeout, nil, logger, NewDispatcherMetrics(false, prometheus.NewRegistry())) + dispatcher := NewDispatcher(alerts, route, recorder, marker, timeout, nil, logger, NewDispatcherMetrics(false, prometheus.NewRegistry()), nil) go dispatcher.Run() defer dispatcher.Stop() @@ -430,6 +434,8 @@ route: return true }, func(*types.Alert, time.Time) bool { return true + }, func(string) bool { + return true }, ) @@ -551,7 +557,7 @@ route: recorder := &recordStage{alerts: make(map[string]map[model.Fingerprint]*types.Alert)} lim := limits{groups: 6} m := NewDispatcherMetrics(true, prometheus.NewRegistry()) - dispatcher := NewDispatcher(alerts, route, recorder, marker, timeout, lim, logger, m) + dispatcher := NewDispatcher(alerts, route, recorder, marker, timeout, lim, logger, m, nil) go dispatcher.Run() defer dispatcher.Stop() @@ -582,8 +588,9 @@ route: routeFilter := func(*Route) bool { return true } alertFilter := func(*types.Alert, time.Time) bool { return true } + groupFilter := func(string) bool { return true } - alertGroups, _ := dispatcher.Groups(routeFilter, alertFilter) + alertGroups, _ := dispatcher.Groups(routeFilter, alertFilter, groupFilter) require.Len(t, alertGroups, 6) require.Equal(t, 0.0, testutil.ToFloat64(m.aggrGroupLimitReached)) @@ -601,10 +608,233 @@ route: require.Equal(t, 1.0, testutil.ToFloat64(m.aggrGroupLimitReached)) // Verify there are still only 6 groups. - alertGroups, _ = dispatcher.Groups(routeFilter, alertFilter) + alertGroups, _ = dispatcher.Groups(routeFilter, alertFilter, groupFilter) require.Len(t, alertGroups, 6) } +func TestGroupInfos(t *testing.T) { + confData := `receivers: +- name: 'kafka' +- name: 'prod' +- name: 'testing' + +route: + group_by: ['alertname'] + group_wait: 10ms + group_interval: 10ms + receiver: 'prod' + routes: + - match: + env: 'testing' + receiver: 'testing' + group_by: ['alertname', 'service'] + - match: + env: 'prod' + receiver: 'prod' + group_by: ['alertname', 'service', 'cluster'] + continue: true + - match: + kafka: 'yes' + receiver: 'kafka' + group_by: ['alertname', 'service', 'cluster']` + conf, err := config.Load(confData) + if err != nil { + t.Fatal(err) + } + + logger := promslog.NewNopLogger() + route := NewRoute(conf.Route, nil) + marker := types.NewMarker(prometheus.NewRegistry()) + alerts, err := mem.NewAlerts(context.Background(), marker, time.Hour, nil, logger, nil) + if err != nil { + t.Fatal(err) + } + defer alerts.Close() + + timeout := func(d time.Duration) time.Duration { return time.Duration(0) } + recorder := &recordStage{alerts: make(map[string]map[model.Fingerprint]*types.Alert)} + dispatcher := NewDispatcher(alerts, route, recorder, marker, timeout, nil, logger, NewDispatcherMetrics(false, prometheus.NewRegistry()), nil) + go dispatcher.Run() + defer dispatcher.Stop() + + // Create alerts. the dispatcher will automatically create the groups. + inputAlerts := []*types.Alert{ + // Matches the parent route. + newAlert(model.LabelSet{"alertname": "OtherAlert", "cluster": "cc", "service": "dd"}), + // Matches the first sub-route. + newAlert(model.LabelSet{"env": "testing", "alertname": "TestingAlert", "service": "api", "instance": "inst1"}), + // Matches the second sub-route. + newAlert(model.LabelSet{"env": "prod", "alertname": "HighErrorRate", "cluster": "aa", "service": "api", "instance": "inst1"}), + newAlert(model.LabelSet{"env": "prod", "alertname": "HighErrorRate", "cluster": "aa", "service": "api", "instance": "inst2"}), + // Matches the second sub-route. + newAlert(model.LabelSet{"env": "prod", "alertname": "HighErrorRate", "cluster": "bb", "service": "api", "instance": "inst1"}), + // Matches the second and third sub-route. + newAlert(model.LabelSet{"env": "prod", "alertname": "HighLatency", "cluster": "bb", "service": "db", "kafka": "yes", "instance": "inst3"}), + } + alerts.Put(inputAlerts...) + + // Let alerts get processed. + for i := 0; len(recorder.Alerts()) != 7 && i < 10; i++ { + time.Sleep(200 * time.Millisecond) + } + require.Equal(t, 7, len(recorder.Alerts())) + + alertGroupInfos := dispatcher.GroupInfos( + func(*Route) bool { + return true + }, + ) + + require.Equal(t, AlertGroupInfos{ + &AlertGroupInfo{ + Labels: model.LabelSet{ + "alertname": "TestingAlert", + "service": "api", + }, + Receiver: "testing", + // Matches the first sub-route. + ID: dispatcher.aggrGroupsPerRoute[dispatcher.route.Routes[0]][model.LabelSet{ + "alertname": "TestingAlert", + "service": "api", + }.Fingerprint()].GroupID(), + }, + &AlertGroupInfo{ + Labels: model.LabelSet{ + "alertname": "HighLatency", + "service": "db", + "cluster": "bb", + }, + Receiver: "kafka", + // Matches the third sub-route. + ID: dispatcher.aggrGroupsPerRoute[dispatcher.route.Routes[2]][model.LabelSet{ + "alertname": "HighLatency", + "service": "db", + "cluster": "bb", + }.Fingerprint()].GroupID(), + }, + &AlertGroupInfo{ + Labels: model.LabelSet{ + "alertname": "HighErrorRate", + "service": "api", + "cluster": "bb", + }, + Receiver: "prod", + // Matches the second sub-route. + ID: dispatcher.aggrGroupsPerRoute[dispatcher.route.Routes[1]][model.LabelSet{ + "alertname": "HighErrorRate", + "service": "api", + "cluster": "bb", + }.Fingerprint()].GroupID(), + }, + &AlertGroupInfo{ + Labels: model.LabelSet{ + "alertname": "HighLatency", + "service": "db", + "cluster": "bb", + }, + Receiver: "prod", + // Matches the second sub-route. + ID: dispatcher.aggrGroupsPerRoute[dispatcher.route.Routes[1]][model.LabelSet{ + "alertname": "HighLatency", + "service": "db", + "cluster": "bb", + }.Fingerprint()].GroupID(), + }, + &AlertGroupInfo{ + Labels: model.LabelSet{ + "alertname": "OtherAlert", + }, + Receiver: "prod", + // Matches the parent route. + ID: dispatcher.aggrGroupsPerRoute[dispatcher.route][model.LabelSet{ + "alertname": "OtherAlert", + }.Fingerprint()].GroupID(), + }, + &AlertGroupInfo{ + Labels: model.LabelSet{ + "alertname": "HighErrorRate", + "service": "api", + "cluster": "aa", + }, + Receiver: "prod", + // Matches the second sub-route. + ID: dispatcher.aggrGroupsPerRoute[dispatcher.route.Routes[1]][model.LabelSet{ + "alertname": "HighErrorRate", + "service": "api", + "cluster": "aa", + }.Fingerprint()].GroupID(), + }, + }, alertGroupInfos) +} + +func TestGroupsAlertLCObserver(t *testing.T) { + confData := `receivers: +- name: 'testing' + +route: + group_by: ['alertname'] + group_wait: 10ms + group_interval: 10ms + receiver: 'testing'` + conf, err := config.Load(confData) + if err != nil { + t.Fatal(err) + } + + logger := promslog.NewNopLogger() + route := NewRoute(conf.Route, nil) + marker := types.NewMarker(prometheus.NewRegistry()) + alerts, err := mem.NewAlerts(context.Background(), marker, time.Hour, nil, logger, nil) + if err != nil { + t.Fatal(err) + } + defer alerts.Close() + + timeout := func(d time.Duration) time.Duration { return time.Duration(0) } + recorder := &recordStage{alerts: make(map[string]map[model.Fingerprint]*types.Alert)} + m := NewDispatcherMetrics(true, prometheus.NewRegistry()) + observer := alertobserver.NewFakeLifeCycleObserver() + lim := limits{groups: 1} + dispatcher := NewDispatcher(alerts, route, recorder, marker, timeout, lim, logger, m, observer) + go dispatcher.Run() + defer dispatcher.Stop() + + // Create alerts. the dispatcher will automatically create the groups. + alert1 := newAlert(model.LabelSet{"alertname": "OtherAlert", "cluster": "cc", "service": "dd"}) + alert2 := newAlert(model.LabelSet{"alertname": "YetAnotherAlert", "cluster": "cc", "service": "db"}) + err = alerts.Put(alert1) + if err != nil { + t.Fatal(err) + } + // Let alerts get processed. + for i := 0; len(recorder.Alerts()) != 1 && i < 10; i++ { + time.Sleep(200 * time.Millisecond) + } + err = alerts.Put(alert2) + if err != nil { + t.Fatal(err) + } + // Let alert get processed. + for i := 0; testutil.ToFloat64(m.aggrGroupLimitReached) == 0 && i < 10; i++ { + time.Sleep(200 * time.Millisecond) + } + observer.Mtx.RLock() + defer observer.Mtx.RUnlock() + require.Equal(t, 1, len(recorder.Alerts())) + require.Equal(t, alert1.Fingerprint(), observer.AlertsPerEvent[alertobserver.EventAlertAddedToAggrGroup][0].Fingerprint()) + groupFp := getGroupLabels(alert1, route).Fingerprint() + group := dispatcher.aggrGroupsPerRoute[route][groupFp] + groupKey := group.GroupKey() + groupId := group.GroupID() + routeId := group.routeID + require.Equal(t, groupKey, observer.MetaPerEvent[alertobserver.EventAlertAddedToAggrGroup][0]["groupKey"].(string)) + require.Equal(t, groupId, observer.MetaPerEvent[alertobserver.EventAlertAddedToAggrGroup][0]["groupId"].(string)) + require.Equal(t, routeId, observer.MetaPerEvent[alertobserver.EventAlertAddedToAggrGroup][0]["routeId"].(string)) + + require.Equal(t, 1, len(observer.AlertsPerEvent[alertobserver.EventAlertFailedAddToAggrGroup])) + require.Equal(t, alert2.Fingerprint(), observer.AlertsPerEvent[alertobserver.EventAlertFailedAddToAggrGroup][0].Fingerprint()) +} + type recordStage struct { mtx sync.RWMutex alerts map[string]map[model.Fingerprint]*types.Alert @@ -669,7 +899,7 @@ func TestDispatcherRace(t *testing.T) { defer alerts.Close() timeout := func(d time.Duration) time.Duration { return time.Duration(0) } - dispatcher := NewDispatcher(alerts, nil, nil, marker, timeout, nil, logger, NewDispatcherMetrics(false, prometheus.NewRegistry())) + dispatcher := NewDispatcher(alerts, nil, nil, marker, timeout, nil, logger, NewDispatcherMetrics(false, prometheus.NewRegistry()), nil) go dispatcher.Run() dispatcher.Stop() } @@ -697,7 +927,7 @@ func TestDispatcherRaceOnFirstAlertNotDeliveredWhenGroupWaitIsZero(t *testing.T) timeout := func(d time.Duration) time.Duration { return d } recorder := &recordStage{alerts: make(map[string]map[model.Fingerprint]*types.Alert)} - dispatcher := NewDispatcher(alerts, route, recorder, marker, timeout, nil, logger, NewDispatcherMetrics(false, prometheus.NewRegistry())) + dispatcher := NewDispatcher(alerts, route, recorder, marker, timeout, nil, logger, NewDispatcherMetrics(false, prometheus.NewRegistry()), nil) go dispatcher.Run() defer dispatcher.Stop() @@ -749,7 +979,7 @@ func TestDispatcher_DoMaintenance(t *testing.T) { recorder := &recordStage{alerts: make(map[string]map[model.Fingerprint]*types.Alert)} ctx := context.Background() - dispatcher := NewDispatcher(alerts, route, recorder, marker, timeout, nil, promslog.NewNopLogger(), NewDispatcherMetrics(false, r)) + dispatcher := NewDispatcher(alerts, route, recorder, marker, timeout, nil, promslog.NewNopLogger(), NewDispatcherMetrics(false, r), nil) aggrGroups := make(map[*Route]map[model.Fingerprint]*aggrGroup) aggrGroups[route] = make(map[model.Fingerprint]*aggrGroup) diff --git a/go.mod b/go.mod index 886f16f31d..3b2fcf7f74 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,9 @@ require ( github.com/alecthomas/kingpin/v2 v2.4.0 github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b github.com/aws/aws-sdk-go v1.55.5 + github.com/aws/aws-sdk-go-v2 v1.36.3 + github.com/aws/aws-sdk-go-v2/config v1.29.14 + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.35.4 github.com/cenkalti/backoff/v4 v4.3.0 github.com/cespare/xxhash/v2 v2.3.0 github.com/coder/quartz v0.1.2 @@ -53,6 +56,17 @@ require ( require ( github.com/armon/go-metrics v0.3.10 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.67 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 // indirect + github.com/aws/smithy-go v1.22.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/go.sum b/go.sum index 8b90e15442..60511aaa2b 100644 --- a/go.sum +++ b/go.sum @@ -80,6 +80,34 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM= +github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg= +github.com/aws/aws-sdk-go-v2/config v1.29.14 h1:f+eEi/2cKCg9pqKBoAIwRGzVb70MRKqWX4dg1BDcSJM= +github.com/aws/aws-sdk-go-v2/config v1.29.14/go.mod h1:wVPHWcIFv3WO89w0rE10gzf17ZYy+UVS1Geq8Iei34g= +github.com/aws/aws-sdk-go-v2/credentials v1.17.67 h1:9KxtdcIA/5xPNQyZRgUSpYOE6j9Bc4+D7nZua0KGYOM= +github.com/aws/aws-sdk-go-v2/credentials v1.17.67/go.mod h1:p3C44m+cfnbv763s52gCqrjaqyPikj9Sg47kUVaNZQQ= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.35.4 h1:EKXYJ8kgz4fiqef8xApu7eH0eae2SrVG+oHCLFybMRI= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.35.4/go.mod h1:yGhDiLKguA3iFJYxbrQkQiNzuy+ddxesSZYWVeeEH5Q= +github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 h1:1Gw+9ajCV1jogloEv1RRnvfRFia2cL6c9cuKV2Ps+G8= +github.com/aws/aws-sdk-go-v2/service/sso v1.25.3/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 h1:hXmVKytPfTy5axZ+fYbR5d0cFmC3JvwLm5kM83luako= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 h1:1XuUZ8mYJw9B6lzAkXhqHlJd/XvaX32evhproijJEZY= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.19/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4= +github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= +github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= diff --git a/notify/notify.go b/notify/notify.go index 8fa85c0a21..b1e3dc94d9 100644 --- a/notify/notify.go +++ b/notify/notify.go @@ -19,6 +19,7 @@ import ( "fmt" "log/slog" "sort" + "strings" "sync" "time" @@ -27,6 +28,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" + "github.com/prometheus/alertmanager/alertobserver" "github.com/prometheus/alertmanager/featurecontrol" "github.com/prometheus/alertmanager/inhibit" "github.com/prometheus/alertmanager/nflog" @@ -119,6 +121,7 @@ const ( keyMuteTimeIntervals keyActiveTimeIntervals keyRouteID + keyGroupId ) // WithReceiverName populates a context with a receiver name. @@ -131,6 +134,11 @@ func WithGroupKey(ctx context.Context, s string) context.Context { return context.WithValue(ctx, keyGroupKey, s) } +// WithGroupId populates a context with a group id. +func WithGroupId(ctx context.Context, s string) context.Context { + return context.WithValue(ctx, keyGroupId, s) +} + // WithFiringAlerts populates a context with a slice of firing alerts. func WithFiringAlerts(ctx context.Context, alerts []uint64) context.Context { return context.WithValue(ctx, keyFiringAlerts, alerts) @@ -190,6 +198,13 @@ func GroupKey(ctx context.Context) (string, bool) { return v, ok } +// GroupId extracts a group id from the context. Iff none exists, the +// second argument is false. +func GroupId(ctx context.Context) (string, bool) { + v, ok := ctx.Value(keyGroupId).(string) + return v, ok +} + // GroupLabels extracts grouping label set from the context. Iff none exists, the // second argument is false. func GroupLabels(ctx context.Context) (model.LabelSet, bool) { @@ -401,18 +416,25 @@ func (pb *PipelineBuilder) New( marker types.GroupMarker, notificationLog NotificationLog, peer Peer, + o alertobserver.LifeCycleObserver, ) RoutingStage { - rs := make(RoutingStage, len(receivers)) + rs := RoutingStage{ + stages: make(map[string]Stage, len(receivers)), + alertLCObserver: o, + } ms := NewGossipSettleStage(peer) - is := NewMuteStage(inhibitor, pb.metrics) + is := NewMuteStage(inhibitor, pb.metrics, o) tas := NewTimeActiveStage(intervener, marker, pb.metrics) tms := NewTimeMuteStage(intervener, marker, pb.metrics) - ss := NewMuteStage(silencer, pb.metrics) + ss := NewMuteStage(silencer, pb.metrics, o) for name := range receivers { - st := createReceiverStage(name, receivers[name], wait, notificationLog, pb.metrics) - rs[name] = MultiStage{ms, is, tas, tms, ss, st} + st := createReceiverStage(name, receivers[name], wait, notificationLog, pb.metrics, o) + rs.stages[name] = MultiStage{ + alertLCObserver: o, + stages: []Stage{ms, is, tas, tms, ss, st}, + } } pb.metrics.InitializeFor(receivers) @@ -427,6 +449,7 @@ func createReceiverStage( wait func() time.Duration, notificationLog NotificationLog, metrics *Metrics, + o alertobserver.LifeCycleObserver, ) Stage { var fs FanoutStage for i := range integrations { @@ -435,20 +458,23 @@ func createReceiverStage( Integration: integrations[i].Name(), Idx: uint32(integrations[i].Index()), } - var s MultiStage + var s []Stage s = append(s, NewWaitStage(wait)) s = append(s, NewDedupStage(&integrations[i], notificationLog, recv)) - s = append(s, NewRetryStage(integrations[i], name, metrics)) + s = append(s, NewRetryStage(integrations[i], name, metrics, o)) s = append(s, NewSetNotifiesStage(notificationLog, recv)) - fs = append(fs, s) + fs = append(fs, MultiStage{stages: s, alertLCObserver: o}) } return fs } // RoutingStage executes the inner stages based on the receiver specified in // the context. -type RoutingStage map[string]Stage +type RoutingStage struct { + stages map[string]Stage + alertLCObserver alertobserver.LifeCycleObserver +} // Exec implements the Stage interface. func (rs RoutingStage) Exec(ctx context.Context, l *slog.Logger, alerts ...*types.Alert) (context.Context, []*types.Alert, error) { @@ -457,21 +483,28 @@ func (rs RoutingStage) Exec(ctx context.Context, l *slog.Logger, alerts ...*type return ctx, nil, errors.New("receiver missing") } - s, ok := rs[receiver] + s, ok := rs.stages[receiver] if !ok { return ctx, nil, errors.New("stage for receiver missing") } + if rs.alertLCObserver != nil { + rs.alertLCObserver.Observe(alertobserver.EventAlertPipelineStart, alerts, alertobserver.AlertEventMeta{"ctx": ctx}) + } + return s.Exec(ctx, l, alerts...) } // A MultiStage executes a series of stages sequentially. -type MultiStage []Stage +type MultiStage struct { + stages []Stage + alertLCObserver alertobserver.LifeCycleObserver +} // Exec implements the Stage interface. func (ms MultiStage) Exec(ctx context.Context, l *slog.Logger, alerts ...*types.Alert) (context.Context, []*types.Alert, error) { var err error - for _, s := range ms { + for _, s := range ms.stages { if len(alerts) == 0 { return ctx, nil, nil } @@ -480,6 +513,10 @@ func (ms MultiStage) Exec(ctx context.Context, l *slog.Logger, alerts ...*types. if err != nil { return ctx, nil, err } + if ms.alertLCObserver != nil { + p := strings.Split(fmt.Sprintf("%T", s), ".") + ms.alertLCObserver.Observe(alertobserver.EventAlertPipelinePassStage, alerts, alertobserver.AlertEventMeta{"ctx": ctx, "stageName": p[len(p)-1]}) + } } return ctx, alerts, nil } @@ -540,13 +577,14 @@ const ( // MuteStage filters alerts through a Muter. type MuteStage struct { - muter types.Muter - metrics *Metrics + muter types.Muter + metrics *Metrics + alertLCObserver alertobserver.LifeCycleObserver } // NewMuteStage return a new MuteStage. -func NewMuteStage(m types.Muter, metrics *Metrics) *MuteStage { - return &MuteStage{muter: m, metrics: metrics} +func NewMuteStage(m types.Muter, metrics *Metrics, o alertobserver.LifeCycleObserver) *MuteStage { + return &MuteStage{muter: m, metrics: metrics, alertLCObserver: o} } // Exec implements the Stage interface. @@ -565,6 +603,7 @@ func (n *MuteStage) Exec(ctx context.Context, logger *slog.Logger, alerts ...*ty } // TODO(fabxc): increment muted alerts counter if muted. } + if len(muted) > 0 { var reason string @@ -579,6 +618,9 @@ func (n *MuteStage) Exec(ctx context.Context, logger *slog.Logger, alerts ...*ty logger.Debug("Notifications will not be sent for muted alerts", "alerts", fmt.Sprintf("%v", muted), "reason", reason) } + if n.alertLCObserver != nil { + n.alertLCObserver.Observe(alertobserver.EventAlertMuted, muted, alertobserver.AlertEventMeta{"ctx": ctx}) + } return ctx, filtered, nil } @@ -751,14 +793,15 @@ func (n *DedupStage) Exec(ctx context.Context, _ *slog.Logger, alerts ...*types. // RetryStage notifies via passed integration with exponential backoff until it // succeeds. It aborts if the context is canceled or timed out. type RetryStage struct { - integration Integration - groupName string - metrics *Metrics - labelValues []string + integration Integration + groupName string + metrics *Metrics + labelValues []string + alertLCObserver alertobserver.LifeCycleObserver } // NewRetryStage returns a new instance of a RetryStage. -func NewRetryStage(i Integration, groupName string, metrics *Metrics) *RetryStage { +func NewRetryStage(i Integration, groupName string, metrics *Metrics, o alertobserver.LifeCycleObserver) *RetryStage { labelValues := []string{i.Name()} if metrics.ff.EnableReceiverNamesInMetrics() { @@ -766,16 +809,17 @@ func NewRetryStage(i Integration, groupName string, metrics *Metrics) *RetryStag } return &RetryStage{ - integration: i, - groupName: groupName, - metrics: metrics, - labelValues: labelValues, + integration: i, + groupName: groupName, + metrics: metrics, + labelValues: labelValues, + alertLCObserver: o, } } func (r RetryStage) Exec(ctx context.Context, l *slog.Logger, alerts ...*types.Alert) (context.Context, []*types.Alert, error) { r.metrics.numNotifications.WithLabelValues(r.labelValues...).Inc() - ctx, alerts, err := r.exec(ctx, l, alerts...) + ctx, alerts, sent, err := r.exec(ctx, l, alerts...) failureReason := DefaultReason.String() if err != nil { @@ -784,11 +828,26 @@ func (r RetryStage) Exec(ctx context.Context, l *slog.Logger, alerts ...*types.A failureReason = e.Reason.String() } r.metrics.numTotalFailedNotifications.WithLabelValues(append(r.labelValues, failureReason)...).Inc() + if r.alertLCObserver != nil { + m := alertobserver.AlertEventMeta{ + "ctx": ctx, + "integration": r.integration.Name(), + "stageName": "RetryStage", + } + r.alertLCObserver.Observe(alertobserver.EventAlertSendFailed, sent, m) + } + } else if r.alertLCObserver != nil { + m := alertobserver.AlertEventMeta{ + "ctx": ctx, + "integration": r.integration.Name(), + "stageName": "RetryStage", + } + r.alertLCObserver.Observe(alertobserver.EventAlertSent, sent, m) } return ctx, alerts, err } -func (r RetryStage) exec(ctx context.Context, l *slog.Logger, alerts ...*types.Alert) (context.Context, []*types.Alert, error) { +func (r RetryStage) exec(ctx context.Context, l *slog.Logger, alerts ...*types.Alert) (context.Context, []*types.Alert, []*types.Alert, error) { var sent []*types.Alert // If we shouldn't send notifications for resolved alerts, but there are only @@ -797,10 +856,10 @@ func (r RetryStage) exec(ctx context.Context, l *slog.Logger, alerts ...*types.A if !r.integration.SendResolved() { firing, ok := FiringAlerts(ctx) if !ok { - return ctx, nil, errors.New("firing alerts missing") + return ctx, nil, nil, errors.New("firing alerts missing") } if len(firing) == 0 { - return ctx, alerts, nil + return ctx, alerts, sent, nil } for _, a := range alerts { if a.Status() != model.AlertResolved { @@ -842,9 +901,9 @@ func (r RetryStage) exec(ctx context.Context, l *slog.Logger, alerts ...*types.A } if iErr != nil { - return ctx, nil, fmt.Errorf("%s/%s: notify retry canceled after %d attempts: %w", r.groupName, r.integration.String(), i, iErr) + return ctx, nil, sent, fmt.Errorf("%s/%s: notify retry canceled after %d attempts: %w", r.groupName, r.integration.String(), i, iErr) } - return ctx, nil, nil + return ctx, nil, sent, nil default: } @@ -858,7 +917,9 @@ func (r RetryStage) exec(ctx context.Context, l *slog.Logger, alerts ...*types.A if err != nil { r.metrics.numNotificationRequestsFailedTotal.WithLabelValues(r.labelValues...).Inc() if !retry { - return ctx, alerts, fmt.Errorf("%s/%s: notify retry canceled due to unrecoverable error after %d attempts: %w", r.groupName, r.integration.String(), i, err) + + return ctx, alerts, sent, fmt.Errorf("%s/%s: notify retry canceled due to unrecoverable error after %d attempts: %w", r.groupName, r.integration.String(), i, err) + } if ctx.Err() == nil { if iErr == nil || err.Error() != iErr.Error() { @@ -878,7 +939,7 @@ func (r RetryStage) exec(ctx context.Context, l *slog.Logger, alerts ...*types.A l.Info("Notify success") } - return ctx, alerts, nil + return ctx, alerts, sent, nil } case <-ctx.Done(): if iErr == nil { @@ -890,9 +951,9 @@ func (r RetryStage) exec(ctx context.Context, l *slog.Logger, alerts ...*types.A } } if iErr != nil { - return ctx, nil, fmt.Errorf("%s/%s: notify retry canceled after %d attempts: %w", r.groupName, r.integration.String(), i, iErr) + return ctx, nil, sent, fmt.Errorf("%s/%s: notify retry canceled after %d attempts: %w", r.groupName, r.integration.String(), i, iErr) } - return ctx, nil, nil + return ctx, nil, sent, nil } } } diff --git a/notify/notify_test.go b/notify/notify_test.go index 9d2fad5a17..3431997092 100644 --- a/notify/notify_test.go +++ b/notify/notify_test.go @@ -25,6 +25,8 @@ import ( "testing" "time" + "github.com/prometheus/alertmanager/alertobserver" + "github.com/prometheus/client_golang/prometheus" prom_testutil "github.com/prometheus/client_golang/prometheus/testutil" "github.com/prometheus/common/model" @@ -308,7 +310,7 @@ func TestMultiStage(t *testing.T) { alerts3 = []*types.Alert{{}, {}, {}} ) - stage := MultiStage{ + stages := []Stage{ StageFunc(func(ctx context.Context, l *slog.Logger, alerts ...*types.Alert) (context.Context, []*types.Alert, error) { if !reflect.DeepEqual(alerts, alerts1) { t.Fatal("Input not equal to input of MultiStage") @@ -328,7 +330,9 @@ func TestMultiStage(t *testing.T) { return ctx, alerts3, nil }), } - + stage := MultiStage{ + stages: stages, + } _, alerts, err := stage.Exec(context.Background(), promslog.NewNopLogger(), alerts1...) if err != nil { t.Fatalf("Exec failed: %s", err) @@ -337,13 +341,28 @@ func TestMultiStage(t *testing.T) { if !reflect.DeepEqual(alerts, alerts3) { t.Fatal("Output of MultiStage is not equal to the output of the last stage") } + + // Rerun multistage but with alert life cycle observer + observer := alertobserver.NewFakeLifeCycleObserver() + ctx := WithGroupKey(context.Background(), "test") + stage.alertLCObserver = observer + _, _, err = stage.Exec(ctx, promslog.NewNopLogger(), alerts1...) + if err != nil { + t.Fatalf("Exec failed: %s", err) + } + + require.Equal(t, 1, len(observer.PipelineStageAlerts)) + require.Equal(t, 5, len(observer.PipelineStageAlerts["StageFunc"])) + metaCtx := observer.MetaPerEvent[alertobserver.EventAlertPipelinePassStage][0]["ctx"].(context.Context) + _, ok := GroupKey(metaCtx) + require.True(t, ok) } func TestMultiStageFailure(t *testing.T) { var ( ctx = context.Background() s1 = failStage{} - stage = MultiStage{s1} + stage = MultiStage{stages: []Stage{s1}} ) _, _, err := stage.Exec(ctx, promslog.NewNopLogger(), nil) @@ -358,7 +377,7 @@ func TestRoutingStage(t *testing.T) { alerts2 = []*types.Alert{{}, {}} ) - stage := RoutingStage{ + s := map[string]Stage{ "name": StageFunc(func(ctx context.Context, l *slog.Logger, alerts ...*types.Alert) (context.Context, []*types.Alert, error) { if !reflect.DeepEqual(alerts, alerts1) { t.Fatal("Input not equal to input of RoutingStage") @@ -367,6 +386,9 @@ func TestRoutingStage(t *testing.T) { }), "not": failStage{}, } + stage := RoutingStage{ + stages: s, + } ctx := WithReceiverName(context.Background(), "name") @@ -378,6 +400,20 @@ func TestRoutingStage(t *testing.T) { if !reflect.DeepEqual(alerts, alerts2) { t.Fatal("Output of RoutingStage is not equal to the output of the inner stage") } + + // Rerun RoutingStage but with alert life cycle observer + observer := alertobserver.NewFakeLifeCycleObserver() + stage.alertLCObserver = observer + _, _, err = stage.Exec(ctx, promslog.NewNopLogger(), alerts1...) + if err != nil { + t.Fatalf("Exec failed: %s", err) + } + require.Equal(t, len(alerts1), len(observer.AlertsPerEvent[alertobserver.EventAlertPipelineStart])) + metaCtx := observer.MetaPerEvent[alertobserver.EventAlertPipelineStart][0]["ctx"].(context.Context) + + _, ok := ReceiverName(metaCtx) + require.True(t, ok) + } func TestRetryStageWithError(t *testing.T) { @@ -394,7 +430,7 @@ func TestRetryStageWithError(t *testing.T) { }), rs: sendResolved(false), } - r := NewRetryStage(i, "", NewMetrics(prometheus.NewRegistry(), featurecontrol.NoopFlags{})) + r := NewRetryStage(i, "", NewMetrics(prometheus.NewRegistry(), featurecontrol.NoopFlags{}), nil) alerts := []*types.Alert{ { @@ -406,6 +442,7 @@ func TestRetryStageWithError(t *testing.T) { ctx := context.Background() ctx = WithFiringAlerts(ctx, []uint64{0}) + ctx = WithGroupKey(ctx, "test") // Notify with a recoverable error should retry and succeed. resctx, res, err := r.Exec(ctx, promslog.NewNopLogger(), alerts...) @@ -414,13 +451,40 @@ func TestRetryStageWithError(t *testing.T) { require.Equal(t, alerts, sent) require.NotNil(t, resctx) + // Rerun recoverable error but with alert life cycle observer + observer := alertobserver.NewFakeLifeCycleObserver() + r.alertLCObserver = observer + _, _, err = r.Exec(ctx, promslog.NewNopLogger(), alerts...) + require.Nil(t, err) + require.Equal(t, len(alerts), len(observer.AlertsPerEvent[alertobserver.EventAlertSent])) + meta := observer.MetaPerEvent[alertobserver.EventAlertSent][0] + require.Equal(t, "RetryStage", meta["stageName"].(string)) + require.Equal(t, i.Name(), meta["integration"].(string)) + metaCtx := meta["ctx"].(context.Context) + _, ok := GroupKey(metaCtx) + require.True(t, ok) + // Notify with an unrecoverable error should fail. sent = sent[:0] fail = true retry = false + r.alertLCObserver = nil resctx, _, err = r.Exec(ctx, promslog.NewNopLogger(), alerts...) require.Error(t, err) require.NotNil(t, resctx) + + // Rerun the unrecoverable error but with alert life cycle observer + fail = true + r.alertLCObserver = observer + _, _, err = r.Exec(ctx, promslog.NewNopLogger(), alerts...) + require.NotNil(t, err) + require.Equal(t, len(alerts), len(observer.AlertsPerEvent[alertobserver.EventAlertSendFailed])) + meta = observer.MetaPerEvent[alertobserver.EventAlertSendFailed][0] + require.Equal(t, "RetryStage", meta["stageName"].(string)) + require.Equal(t, i.Name(), meta["integration"].(string)) + metaCtx = meta["ctx"].(context.Context) + _, ok = GroupKey(metaCtx) + require.True(t, ok) } func TestRetryStageWithErrorCode(t *testing.T) { @@ -447,7 +511,7 @@ func TestRetryStageWithErrorCode(t *testing.T) { }), rs: sendResolved(false), } - r := NewRetryStage(i, "", NewMetrics(prometheus.NewRegistry(), featurecontrol.NoopFlags{})) + r := NewRetryStage(i, "", NewMetrics(prometheus.NewRegistry(), featurecontrol.NoopFlags{}), nil) alerts := []*types.Alert{ { @@ -482,7 +546,7 @@ func TestRetryStageWithContextCanceled(t *testing.T) { }), rs: sendResolved(false), } - r := NewRetryStage(i, "", NewMetrics(prometheus.NewRegistry(), featurecontrol.NoopFlags{})) + r := NewRetryStage(i, "", NewMetrics(prometheus.NewRegistry(), featurecontrol.NoopFlags{}), nil) alerts := []*types.Alert{ { @@ -513,7 +577,7 @@ func TestRetryStageNoResolved(t *testing.T) { }), rs: sendResolved(false), } - r := NewRetryStage(i, "", NewMetrics(prometheus.NewRegistry(), featurecontrol.NoopFlags{})) + r := NewRetryStage(i, "", NewMetrics(prometheus.NewRegistry(), featurecontrol.NoopFlags{}), nil) alerts := []*types.Alert{ { @@ -564,7 +628,7 @@ func TestRetryStageSendResolved(t *testing.T) { }), rs: sendResolved(true), } - r := NewRetryStage(i, "", NewMetrics(prometheus.NewRegistry(), featurecontrol.NoopFlags{})) + r := NewRetryStage(i, "", NewMetrics(prometheus.NewRegistry(), featurecontrol.NoopFlags{}), nil) alerts := []*types.Alert{ { @@ -669,7 +733,7 @@ func TestMuteStage(t *testing.T) { }) metrics := NewMetrics(prometheus.NewRegistry(), featurecontrol.NoopFlags{}) - stage := NewMuteStage(muter, metrics) + stage := NewMuteStage(muter, metrics, nil) in := []model.LabelSet{ {}, @@ -731,7 +795,7 @@ func TestMuteStageWithSilences(t *testing.T) { marker := types.NewMarker(reg) silencer := silence.NewSilencer(silences, marker, promslog.NewNopLogger()) metrics := NewMetrics(reg, featurecontrol.NoopFlags{}) - stage := NewMuteStage(silencer, metrics) + stage := NewMuteStage(silencer, metrics, nil) in := []model.LabelSet{ {}, @@ -822,6 +886,46 @@ func TestMuteStageWithSilences(t *testing.T) { } } +func TestMuteStageWithAlertObserver(t *testing.T) { + silences, err := silence.New(silence.Options{Retention: time.Hour}) + if err != nil { + t.Fatal(err) + } + err = silences.Set(&silencepb.Silence{ + EndsAt: utcNow().Add(time.Hour), + Matchers: []*silencepb.Matcher{{Name: "mute", Pattern: "me"}}, + }) + if err != nil { + t.Fatal(err) + } + + marker := types.NewMarker(prometheus.NewRegistry()) + silencer := silence.NewSilencer(silences, marker, promslog.NewNopLogger()) + observer := alertobserver.NewFakeLifeCycleObserver() + metrics := NewMetrics(prometheus.NewRegistry(), featurecontrol.NoopFlags{}) + stage := NewMuteStage(silencer, metrics, observer) + + in := []model.LabelSet{ + {"test": "set"}, + {"mute": "me"}, + {"foo": "bar", "test": "set"}, + } + + var inAlerts []*types.Alert + for _, lset := range in { + inAlerts = append(inAlerts, &types.Alert{ + Alert: model.Alert{Labels: lset}, + }) + } + + _, _, err = stage.Exec(context.Background(), promslog.NewNopLogger(), inAlerts...) + if err != nil { + t.Fatalf("Exec failed: %s", err) + } + require.Equal(t, 1, len(observer.AlertsPerEvent[alertobserver.EventAlertMuted])) + require.Equal(t, inAlerts[1], observer.AlertsPerEvent[alertobserver.EventAlertMuted][0]) +} + func TestTimeMuteStage(t *testing.T) { sydney, err := time.LoadLocation("Australia/Sydney") if err != nil { diff --git a/notify/pagerduty/pagerduty.go b/notify/pagerduty/pagerduty.go index abab5a70be..6a2ba01be2 100644 --- a/notify/pagerduty/pagerduty.go +++ b/notify/pagerduty/pagerduty.go @@ -19,6 +19,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/prometheus/alertmanager/secrets" "io" "log/slog" "net/http" @@ -45,27 +46,30 @@ const ( // Notifier implements a Notifier for PagerDuty notifications. type Notifier struct { - conf *config.PagerdutyConfig - tmpl *template.Template - logger *slog.Logger - apiV1 string // for tests. - client *http.Client - retrier *notify.Retrier + conf *config.PagerdutyConfig + tmpl *template.Template + logger *slog.Logger + apiV1 string // for tests. + client *http.Client + retrier *notify.Retrier + secretsFetcher secrets.SecretsFetcher } // New returns a new PagerDuty notifier. -func New(c *config.PagerdutyConfig, t *template.Template, l *slog.Logger, httpOpts ...commoncfg.HTTPClientOption) (*Notifier, error) { +func New(c *config.PagerdutyConfig, t *template.Template, l *slog.Logger, spRegistry *secrets.SecretsProviderRegistry, httpOpts ...commoncfg.HTTPClientOption) (*Notifier, error) { client, err := commoncfg.NewClientFromConfig(*c.HTTPConfig, "pagerduty", httpOpts...) if err != nil { return nil, err } n := &Notifier{conf: c, tmpl: t, logger: l, client: client} - if c.ServiceKey != "" || c.ServiceKeyFile != "" { + if c.ServiceKey != nil || c.ServiceKeyFile != "" { + n.secretsFetcher, err = spRegistry.RegisterSecret(c.ServiceKey) n.apiV1 = "https://events.pagerduty.com/generic/2010-04-15/create_event.json" // Retrying can solve the issue on 403 (rate limiting) and 5xx response codes. // https://v2.developer.pagerduty.com/docs/trigger-events n.retrier = ¬ify.Retrier{RetryCodes: []int{http.StatusForbidden}, CustomDetailsFunc: errDetails} } else { + n.secretsFetcher, err = spRegistry.RegisterSecret(c.RoutingKey) // Retrying can solve the issue on 429 (rate limiting) and 5xx response codes. // https://v2.developer.pagerduty.com/docs/events-api-v2#api-response-codes--retry-logic n.retrier = ¬ify.Retrier{RetryCodes: []int{http.StatusTooManyRequests}, CustomDetailsFunc: errDetails} @@ -143,6 +147,22 @@ func (n *Notifier) encodeMessage(msg *pagerDutyMessage) (bytes.Buffer, error) { return buf, nil } +func (n *Notifier) getSecret(ctx context.Context) string { + var secret *secrets.GenericSecret + if n.conf.ServiceKey != nil { + secret = n.conf.ServiceKey + } else { + secret = n.conf.RoutingKey + } + + if sec, err := n.secretsFetcher.FetchSecret(ctx, secret); err != nil { + n.logger.Error("unable to fetch secret", err) + return "" + } else { + return sec + } +} + func (n *Notifier) notifyV1( ctx context.Context, eventType string, @@ -159,7 +179,8 @@ func (n *Notifier) notifyV1( n.logger.Warn("Truncated description", "key", key, "max_runes", maxV1DescriptionLenRunes) } - serviceKey := string(n.conf.ServiceKey) + //serviceKey := string(n.conf.ServiceKey) + serviceKey := n.getSecret(ctx) if serviceKey == "" { content, fileErr := os.ReadFile(n.conf.ServiceKeyFile) if fileErr != nil { @@ -224,7 +245,8 @@ func (n *Notifier) notifyV2( n.logger.Warn("Truncated summary", "key", key, "max_runes", maxV2SummaryLenRunes) } - routingKey := string(n.conf.RoutingKey) + //routingKey := string(n.conf.RoutingKey) + routingKey := n.getSecret(ctx) if routingKey == "" { content, fileErr := os.ReadFile(n.conf.RoutingKeyFile) if fileErr != nil { diff --git a/secrets/generic_secret.go b/secrets/generic_secret.go new file mode 100644 index 0000000000..8742a705af --- /dev/null +++ b/secrets/generic_secret.go @@ -0,0 +1,32 @@ +package secrets + +import ( + "errors" + "time" +) + +type GenericSecret struct { + AWSSecretsManagerConfig *AWSSecretsManagerConfig `yaml:"aws_secrets_manager" json:"aws_secrets_manager_config"` +} + +// TODO implement this correctly +func (gs *GenericSecret) String() string { + return "" +} + +// TODO implement Marshal and JSON equivalent methods +func (gs *GenericSecret) UnmarshalYAML(unmarshalFn func(any) error) error { + var inlineForm string + if err := unmarshalFn(&inlineForm); err == nil { + return errors.New("inline form is not supported") + } + type plain GenericSecret + // We need to do this to avoid infinite recursion. + return unmarshalFn((*plain)(gs)) +} + +type AWSSecretsManagerConfig struct { + SecretARN string `yaml:"secret_arn"` + SecretKey string `yaml:"secret_key"` + RefreshInterval time.Duration `yaml:"refresh_interval"` +} diff --git a/secrets/providers/aws_secrets_manager.go b/secrets/providers/aws_secrets_manager.go new file mode 100644 index 0000000000..d569ad8405 --- /dev/null +++ b/secrets/providers/aws_secrets_manager.go @@ -0,0 +1,185 @@ +package providers + +import ( + "context" + "encoding/json" + "errors" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/aws/arn" + awsconfig "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/secretsmanager" + "github.com/prometheus/alertmanager/secrets" + "github.com/prometheus/client_golang/prometheus" + "log/slog" + "sync" + "time" +) + +//TODO metrics + +type AWSSecretsManagerProvider struct { + mtx sync.RWMutex + fetchers map[string]*secretFetcher + logger *slog.Logger + reg prometheus.Registerer + ctx context.Context +} + +func (a *AWSSecretsManagerProvider) Register(secret *secrets.GenericSecret) secrets.SecretsFetcher { + s := secret.AWSSecretsManagerConfig + if s == nil { + a.logger.Error("secret is nil. nothing to register") + return nil + } + a.logger.Info("registering secret") + a.mtx.Lock() + defer a.mtx.Unlock() + if f, OK := a.fetchers[s.SecretARN]; OK { + a.logger.Info("found an existing secret fetcher") + f.update(s.RefreshInterval) + return f + } + a.logger.Info("no secret fetcher found. creating a new one") + a.fetchers[s.SecretARN] = newSecretFetcher(a.ctx, a.logger, a.reg, s.SecretARN, s.RefreshInterval) + return a.fetchers[s.SecretARN] +} + +func (a *AWSSecretsManagerProvider) Stop() { + a.mtx.Lock() + defer a.mtx.Unlock() + for name, fetcher := range a.fetchers { + a.logger.Info("stopping secrets fetcher", "name", name) + fetcher.Stop() + } + a.logger.Info("aws secrets manager providers stopped") +} + +type secretFetcher struct { + secrets map[string]string + mtx sync.RWMutex + logger *slog.Logger + reg prometheus.Registerer + arn string + interval time.Duration + ctx context.Context + client *secretsmanager.Client + done chan struct{} + ticker *time.Ticker + initialFetch bool +} + +func newSecretFetcher(ctx context.Context, logger *slog.Logger, reg prometheus.Registerer, arn string, interval time.Duration) *secretFetcher { + sf := &secretFetcher{ + secrets: make(map[string]string), + logger: logger, + reg: reg, + arn: arn, + interval: interval, + ctx: ctx, + done: make(chan struct{}), + ticker: time.NewTicker(interval), + } + sf.createSecretsManagerClient() + go sf.run() + return sf +} + +func (s *secretFetcher) createSecretsManagerClient() { + parsedARN, err := arn.Parse(s.arn) + if err != nil { + s.logger.Error("unable to create secret manager client", err) + return + } + config, err := awsconfig.LoadDefaultConfig(s.ctx, awsconfig.WithRegion(parsedARN.Region)) + if err != nil { + s.logger.Error("unable to load config", err) + return + } + s.client = secretsmanager.NewFromConfig(config) +} + +func (s *secretFetcher) Stop() { + <-s.done + s.logger.Info("secret fetcher stopped") +} + +func (s *secretFetcher) run() { + defer close(s.done) + defer s.ticker.Stop() + input := &secretsmanager.GetSecretValueInput{ + SecretId: aws.String(s.arn), + } + s.logger.Debug("fetch secret", "reason", "initial") + s.retrieveSecret(input) + s.initialFetch = true + for { + select { + case <-s.ticker.C: + s.logger.Debug("fetching secret", "reason", "periodic") + s.retrieveSecret(input) + s.initialFetch = true + case <-s.ctx.Done(): + s.logger.Info("stopping secrets fetcher") + return + } + } +} + +func (s *secretFetcher) retrieveSecret(input *secretsmanager.GetSecretValueInput) { + result, err := s.client.GetSecretValue(s.ctx, input) + if err != nil { + s.logger.Error("unable to fetch secret for ARN", "arn", s.arn, "error", err) + return + } + secretString := *result.SecretString + var m map[string]string + if err = json.Unmarshal([]byte(secretString), &m); err != nil { + s.logger.Error("unable to unmarshal payload", "arn", s.arn, "error", err) + return + } + s.logger.Debug("retrieved keys", "key count", len(m)) + s.mtx.Lock() + defer s.mtx.Unlock() + s.secrets = nil + s.secrets = m +} + +func (s *secretFetcher) update(interval time.Duration) { + s.mtx.Lock() + defer s.mtx.Unlock() + if s.interval > interval { + s.interval = interval + s.ticker.Reset(s.interval) + } +} + +func (s *secretFetcher) FetchSecret(_ context.Context, secret *secrets.GenericSecret) (string, error) { + sec := secret.AWSSecretsManagerConfig + if sec == nil { + return "", errors.New("cannot fetch empty secret") + } + + s.mtx.RLock() + value, exists := s.secrets[sec.SecretKey] + s.mtx.RUnlock() + if !exists { + return "", errors.New("secret not found") + } + return value, nil +} + +type AWSSecretsManagerSecretProviderDiscoveryConfig struct { +} + +func (a AWSSecretsManagerSecretProviderDiscoveryConfig) Name() string { + return "aws_secrets_manager" +} + +func (a AWSSecretsManagerSecretProviderDiscoveryConfig) NewSecretsProvider(options secrets.SecretProviderOptions) (secrets.SecretsProvider, error) { + return &AWSSecretsManagerProvider{ + fetchers: make(map[string]*secretFetcher), + logger: options.Logger, + reg: options.Registerer, + ctx: options.Context, + }, nil +} diff --git a/secrets/secrets_provider.go b/secrets/secrets_provider.go new file mode 100644 index 0000000000..407a9505e0 --- /dev/null +++ b/secrets/secrets_provider.go @@ -0,0 +1,118 @@ +package secrets + +import ( + "context" + "errors" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/common/config" + "log/slog" + "sync" +) + +var ( + AWS_SECRETS_MANAGER_PROVIDER = "aws_secrets_manager" +) + +type SecretsFetcher interface { + FetchSecret(ctx context.Context, secret *GenericSecret) (string, error) + Stop() +} + +type SecretsProvider interface { + Register(secret *GenericSecret) SecretsFetcher + Stop() +} + +type SecretsProviderRegistry struct { + mtx sync.RWMutex + providers map[string]SecretsProvider + logger *slog.Logger + reg prometheus.Registerer + configs map[string]SecretProviderDiscoveryConfig + ctx context.Context + cancel context.CancelFunc +} + +func NewSecretsProviderRegistry(logger *slog.Logger, reg prometheus.Registerer) *SecretsProviderRegistry { + registry := &SecretsProviderRegistry{ + providers: make(map[string]SecretsProvider), + configs: make(map[string]SecretProviderDiscoveryConfig), + logger: logger, + reg: reg, + } + return registry +} + +func (s *SecretsProviderRegistry) Register(config SecretProviderDiscoveryConfig) { + s.mtx.Lock() + defer s.mtx.Unlock() + s.logger.Info("registering secret providers", "name", config.Name()) + s.configs[config.Name()] = config +} + +func (s *SecretsProviderRegistry) Init() { + s.mtx.Lock() + defer s.mtx.Unlock() + s.ctx, s.cancel = context.WithCancel(context.Background()) + for name, providerConfig := range s.configs { + s.logger.Info("initializing secret providers", "name", name) + provider, err := providerConfig.NewSecretsProvider(SecretProviderOptions{ + Logger: s.logger, + Registerer: s.reg, + Context: s.ctx, + }) + if err != nil { + s.logger.Error("unable to initialize secrets provider", "name", name, "error", err.Error()) + continue + } + s.providers[name] = provider + } +} + +func (s *SecretsProviderRegistry) Stop() { + if s == nil { + return + } + s.mtx.Lock() + defer s.mtx.Unlock() + if s.cancel == nil { + return + } + s.cancel() + s.cancel = nil + for name, provider := range s.providers { + s.logger.Info("stopping secrets providers", "name", name) + provider.Stop() + } + s.logger.Info("stopped secrets providers registry") +} + +func (s *SecretsProviderRegistry) RegisterSecret(secret *GenericSecret) (SecretsFetcher, error) { + s.mtx.RLock() + defer s.mtx.RUnlock() + + s.logger.Info("registering secret") + if secret.AWSSecretsManagerConfig != nil { + s.logger.Info("registering aws_secret_manager secret") + return s.providers[AWS_SECRETS_MANAGER_PROVIDER].Register(secret), nil + } + return nil, errors.New("no secrets fetcher found for the given secret") +} + +type SecretProviderDiscoveryConfig interface { + // Name returns the name of the discovery mechanism. + Name() string + + NewSecretsProvider(SecretProviderOptions) (SecretsProvider, error) +} + +type SecretProviderOptions struct { + Logger *slog.Logger + + // A registerer for the SecretProvider's metrics. + Registerer prometheus.Registerer + + HTTPClientOptions []config.HTTPClientOption + + Context context.Context +} diff --git a/util/callback/callback.go b/util/callback/callback.go new file mode 100644 index 0000000000..a7f2c3a71e --- /dev/null +++ b/util/callback/callback.go @@ -0,0 +1,36 @@ +// Copyright 2019 Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package callback + +import ( + open_api_models "github.com/prometheus/alertmanager/api/v2/models" +) + +type Callback interface { + // V2GetAlertsCallback is called before v2 getAlerts api returned. + V2GetAlertsCallback(alerts open_api_models.GettableAlerts) (open_api_models.GettableAlerts, error) + + // V2GetAlertGroupsCallback is called before v2 GetAlertGroups api returned. + V2GetAlertGroupsCallback(alertgroups open_api_models.AlertGroups) (open_api_models.AlertGroups, error) +} + +type NoopAPICallback struct{} + +func (n NoopAPICallback) V2GetAlertsCallback(alerts open_api_models.GettableAlerts) (open_api_models.GettableAlerts, error) { + return alerts, nil +} + +func (n NoopAPICallback) V2GetAlertGroupsCallback(alertgroups open_api_models.AlertGroups) (open_api_models.AlertGroups, error) { + return alertgroups, nil +}