diff --git a/api/api.go b/api/api.go index 449b51c8..eeb6f6a8 100644 --- a/api/api.go +++ b/api/api.go @@ -24,6 +24,7 @@ import ( apiv2 "github.com/prometheus/alertmanager/api/v2" "github.com/prometheus/alertmanager/cluster" "github.com/prometheus/alertmanager/config" + "github.com/prometheus/alertmanager/dispatch" "github.com/prometheus/alertmanager/provider" "github.com/prometheus/alertmanager/silence" "github.com/prometheus/alertmanager/types" @@ -69,6 +70,10 @@ type Options struct { // Registry is used to register Prometheus metrics. If nil, no metrics // registration will happen. Registry prometheus.Registerer + // 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) } func (o Options) validate() error { @@ -81,6 +86,9 @@ func (o Options) validate() error { if o.StatusFunc == nil { return errors.New("mandatory field StatusFunc not set") } + if o.GroupFunc == nil { + return errors.New("mandatory field GroupFunc not set") + } return nil } @@ -113,6 +121,7 @@ func New(opts Options) (*API, error) { v2, err := apiv2.NewAPI( opts.Alerts, + opts.GroupFunc, opts.StatusFunc, opts.Silences, opts.Peer, diff --git a/api/v2/api.go b/api/v2/api.go index aae4f26a..69e294e0 100644 --- a/api/v2/api.go +++ b/api/v2/api.go @@ -35,6 +35,7 @@ import ( "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" 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" @@ -53,6 +54,7 @@ type API struct { peer *cluster.Peer silences *silence.Silences alerts provider.Alerts + alertGroups groupsFn getAlertStatus getAlertStatusFn uptime time.Time @@ -69,12 +71,14 @@ type API struct { Handler http.Handler } +type groupsFn func(func(*dispatch.Route) bool, func(*types.Alert, time.Time) bool) (dispatch.AlertGroups, map[prometheus_model.Fingerprint][]string) type getAlertStatusFn func(prometheus_model.Fingerprint) types.AlertStatus type setAlertStatusFn func(prometheus_model.LabelSet) // NewAPI returns a new Alertmanager API v2 func NewAPI( alerts provider.Alerts, + gf groupsFn, sf getAlertStatusFn, silences *silence.Silences, peer *cluster.Peer, @@ -83,6 +87,7 @@ func NewAPI( api := API{ alerts: alerts, getAlertStatus: sf, + alertGroups: gf, peer: peer, silences: silences, logger: l, @@ -107,6 +112,7 @@ func NewAPI( openAPI.AlertGetAlertsHandler = alert_ops.GetAlertsHandlerFunc(api.getAlertsHandler) openAPI.AlertPostAlertsHandler = alert_ops.PostAlertsHandlerFunc(api.postAlertsHandler) + openAPI.AlertgroupGetAlertGroupsHandler = alertgroup_ops.GetAlertGroupsHandlerFunc(api.getAlertGroupsHandler) openAPI.GeneralGetStatusHandler = general_ops.GetStatusHandlerFunc(api.getStatusHandler) openAPI.ReceiverGetReceiversHandler = receiver_ops.GetReceiversHandlerFunc(api.getReceiversHandler) openAPI.SilenceDeleteSilenceHandler = silence_ops.DeleteSilenceHandlerFunc(api.deleteSilenceHandler) @@ -224,6 +230,7 @@ func (api *API) getAlertsHandler(params alert_ops.GetAlertsParams) middleware.Re if params.Receiver != nil { receiverFilter, err = regexp.Compile("^(?:" + *params.Receiver + ")$") if err != nil { + level.Error(api.logger).Log("msg", "failed to compile receiver regex", "err", err) return alert_ops. NewGetAlertsBadRequest(). WithPayload( @@ -235,6 +242,9 @@ 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() + api.mtx.RLock() for a := range alerts.Next() { if err = alerts.Err(); err != nil { @@ -245,79 +255,22 @@ func (api *API) getAlertsHandler(params alert_ops.GetAlertsParams) middleware.Re } routes := api.route.Match(a.Labels) - receivers := make([]*open_api_models.Receiver, 0, len(routes)) + receivers := make([]string, 0, len(routes)) for _, r := range routes { - receivers = append(receivers, &open_api_models.Receiver{Name: &r.RouteOpts.Receiver}) + receivers = append(receivers, r.RouteOpts.Receiver) } if receiverFilter != nil && !receiversMatchFilter(receivers, receiverFilter) { continue } - if !alertMatchesFilterLabels(&a.Alert, matchers) { + if !alertFilter(a, now) { continue } - // Continue if the alert is resolved. - if !a.Alert.EndsAt.IsZero() && a.Alert.EndsAt.Before(time.Now()) { - continue - } + alert := alertToOpenAPIAlert(a, api.getAlertStatus(a.Fingerprint()), receivers) - // Set alert's current status based on its label set. - api.setAlertStatus(a.Labels) - - // Get alert's current status after seeing if it is suppressed. - status := api.getAlertStatus(a.Fingerprint()) - - if !*params.Active && status.State == types.AlertStateActive { - continue - } - - if !*params.Unprocessed && status.State == types.AlertStateUnprocessed { - continue - } - - if !*params.Silenced && len(status.SilencedBy) != 0 { - continue - } - - if !*params.Inhibited && len(status.InhibitedBy) != 0 { - continue - } - - state := string(status.State) - startsAt := strfmt.DateTime(a.StartsAt) - updatedAt := strfmt.DateTime(a.UpdatedAt) - endsAt := strfmt.DateTime(a.EndsAt) - fingerprint := a.Fingerprint().String() - - alert := open_api_models.GettableAlert{ - Alert: open_api_models.Alert{ - GeneratorURL: strfmt.URI(a.GeneratorURL), - Labels: modelLabelSetToAPILabelSet(a.Labels), - }, - Annotations: modelLabelSetToAPILabelSet(a.Annotations), - StartsAt: &startsAt, - UpdatedAt: &updatedAt, - EndsAt: &endsAt, - Fingerprint: &fingerprint, - Receivers: receivers, - Status: &open_api_models.AlertStatus{ - State: &state, - SilencedBy: status.SilencedBy, - InhibitedBy: status.InhibitedBy, - }, - } - - if alert.Status.SilencedBy == nil { - alert.Status.SilencedBy = []string{} - } - - if alert.Status.InhibitedBy == nil { - alert.Status.InhibitedBy = []string{} - } - - res = append(res, &alert) + res = append(res, alert) } api.mtx.RUnlock() @@ -393,6 +346,139 @@ func (api *API) postAlertsHandler(params alert_ops.PostAlertsParams) middleware. return alert_ops.NewPostAlertsOK() } +func (api *API) getAlertGroupsHandler(params alertgroup_ops.GetAlertGroupsParams) middleware.Responder { + var ( + err error + receiverFilter *regexp.Regexp + matchers = []*labels.Matcher{} + ) + + for _, matcherString := range params.Filter { + matcher, err := parse.Matcher(matcherString) + if err != nil { + level.Error(api.logger).Log("msg", "failed to parse matchers", "err", err) + return alertgroup_ops.NewGetAlertGroupsBadRequest().WithPayload(err.Error()) + } + + matchers = append(matchers, matcher) + } + + if params.Receiver != nil { + receiverFilter, err = regexp.Compile("^(?:" + *params.Receiver + ")$") + if err != nil { + level.Error(api.logger).Log("msg", "failed to compile receiver regex", "err", err) + return alertgroup_ops. + NewGetAlertGroupsBadRequest(). + 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) + + af := api.alertFilter(matchers, *params.Silenced, *params.Inhibited, *params.Active) + alertGroups, allReceivers := api.alertGroups(rf, af) + + res := make(open_api_models.AlertGroups, 0, len(alertGroups)) + + for _, alertGroup := range alertGroups { + ag := &open_api_models.AlertGroup{ + Receiver: &open_api_models.Receiver{Name: &alertGroup.Receiver}, + Labels: modelLabelSetToAPILabelSet(alertGroup.Labels), + Alerts: make([]*open_api_models.GettableAlert, 0, len(alertGroup.Alerts)), + } + + for _, alert := range alertGroup.Alerts { + fp := alert.Fingerprint() + receivers := allReceivers[fp] + status := api.getAlertStatus(fp) + apiAlert := alertToOpenAPIAlert(alert, status, receivers) + ag.Alerts = append(ag.Alerts, apiAlert) + } + res = append(res, ag) + } + + return alertgroup_ops.NewGetAlertGroupsOK().WithPayload(res) +} + +func (api *API) alertFilter(matchers []*labels.Matcher, silenced, inhibited, active bool) func(a *types.Alert, now time.Time) bool { + return func(a *types.Alert, now time.Time) bool { + if !a.EndsAt.IsZero() && a.EndsAt.Before(now) { + return false + } + + // Set alert's current status based on its label set. + api.setAlertStatus(a.Labels) + + // Get alert's current status after seeing if it is suppressed. + status := api.getAlertStatus(a.Fingerprint()) + + if !active && status.State == types.AlertStateActive { + return false + } + + if !silenced && len(status.SilencedBy) != 0 { + return false + } + + if !inhibited && len(status.InhibitedBy) != 0 { + return false + } + + return alertMatchesFilterLabels(&a.Alert, matchers) + } +} + +func alertToOpenAPIAlert(alert *types.Alert, status types.AlertStatus, receivers []string) *open_api_models.GettableAlert { + startsAt := strfmt.DateTime(alert.StartsAt) + updatedAt := strfmt.DateTime(alert.UpdatedAt) + endsAt := strfmt.DateTime(alert.EndsAt) + + apiReceivers := make([]*open_api_models.Receiver, 0, len(receivers)) + for _, name := range receivers { + apiReceivers = append(apiReceivers, &open_api_models.Receiver{Name: &name}) + } + + fp := alert.Fingerprint().String() + state := string(status.State) + aa := &open_api_models.GettableAlert{ + Alert: open_api_models.Alert{ + GeneratorURL: strfmt.URI(alert.GeneratorURL), + Labels: modelLabelSetToAPILabelSet(alert.Labels), + }, + Annotations: modelLabelSetToAPILabelSet(alert.Annotations), + StartsAt: &startsAt, + UpdatedAt: &updatedAt, + EndsAt: &endsAt, + Fingerprint: &fp, + Receivers: apiReceivers, + Status: &open_api_models.AlertStatus{ + State: &state, + SilencedBy: status.SilencedBy, + InhibitedBy: status.InhibitedBy, + }, + } + + if aa.Status.SilencedBy == nil { + aa.Status.SilencedBy = []string{} + } + + if aa.Status.InhibitedBy == nil { + aa.Status.InhibitedBy = []string{} + } + + return aa +} + func openAPIAlertsToAlerts(apiAlerts open_api_models.PostableAlerts) []*types.Alert { alerts := []*types.Alert{} for _, apiAlert := range apiAlerts { @@ -437,9 +523,9 @@ func apiLabelSetToModelLabelSet(apiLabelSet open_api_models.LabelSet) prometheus return modelLabelSet } -func receiversMatchFilter(receivers []*open_api_models.Receiver, filter *regexp.Regexp) bool { +func receiversMatchFilter(receivers []string, filter *regexp.Regexp) bool { for _, r := range receivers { - if filter.MatchString(string(*r.Name)) { + if filter.MatchString(r) { return true } } @@ -564,6 +650,8 @@ func gettableSilenceMatchesFilterLabels(s open_api_models.GettableSilence, match return matchFilterLabels(matchers, sms) } +// func matchesFilterLabels(labels model.LabelSet, matchers []*labels.Matcher) bool { + func (api *API) getSilenceHandler(params silence_ops.GetSilenceParams) middleware.Responder { sils, _, err := api.silences.Query(silence.QIDs(params.SilenceID.String())) if err != nil { diff --git a/api/v2/client/alertgroup/alertgroup_client.go b/api/v2/client/alertgroup/alertgroup_client.go new file mode 100644 index 00000000..adae7542 --- /dev/null +++ b/api/v2/client/alertgroup/alertgroup_client.go @@ -0,0 +1,72 @@ +// 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 alertgroup + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "github.com/go-openapi/runtime" + + strfmt "github.com/go-openapi/strfmt" +) + +// New creates a new alertgroup API client. +func New(transport runtime.ClientTransport, formats strfmt.Registry) *Client { + return &Client{transport: transport, formats: formats} +} + +/* +Client for alertgroup API +*/ +type Client struct { + transport runtime.ClientTransport + formats strfmt.Registry +} + +/* +GetAlertGroups Get a list of alert groups +*/ +func (a *Client) GetAlertGroups(params *GetAlertGroupsParams) (*GetAlertGroupsOK, error) { + // TODO: Validate the params before sending + if params == nil { + params = NewGetAlertGroupsParams() + } + + result, err := a.transport.Submit(&runtime.ClientOperation{ + ID: "getAlertGroups", + Method: "GET", + PathPattern: "/alerts/groups", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http"}, + Params: params, + Reader: &GetAlertGroupsReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + }) + if err != nil { + return nil, err + } + return result.(*GetAlertGroupsOK), nil + +} + +// SetTransport changes the transport on the client +func (a *Client) SetTransport(transport runtime.ClientTransport) { + a.transport = transport +} diff --git a/api/v2/client/alertgroup/get_alert_groups_parameters.go b/api/v2/client/alertgroup/get_alert_groups_parameters.go new file mode 100644 index 00000000..2c59f416 --- /dev/null +++ b/api/v2/client/alertgroup/get_alert_groups_parameters.go @@ -0,0 +1,310 @@ +// 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 alertgroup + +// 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/swag" + + strfmt "github.com/go-openapi/strfmt" +) + +// NewGetAlertGroupsParams creates a new GetAlertGroupsParams object +// with the default values initialized. +func NewGetAlertGroupsParams() *GetAlertGroupsParams { + var ( + activeDefault = bool(true) + inhibitedDefault = bool(true) + silencedDefault = bool(true) + ) + return &GetAlertGroupsParams{ + Active: &activeDefault, + Inhibited: &inhibitedDefault, + Silenced: &silencedDefault, + + timeout: cr.DefaultTimeout, + } +} + +// NewGetAlertGroupsParamsWithTimeout creates a new GetAlertGroupsParams object +// with the default values initialized, and the ability to set a timeout on a request +func NewGetAlertGroupsParamsWithTimeout(timeout time.Duration) *GetAlertGroupsParams { + var ( + activeDefault = bool(true) + inhibitedDefault = bool(true) + silencedDefault = bool(true) + ) + return &GetAlertGroupsParams{ + Active: &activeDefault, + Inhibited: &inhibitedDefault, + Silenced: &silencedDefault, + + timeout: timeout, + } +} + +// NewGetAlertGroupsParamsWithContext creates a new GetAlertGroupsParams object +// with the default values initialized, and the ability to set a context for a request +func NewGetAlertGroupsParamsWithContext(ctx context.Context) *GetAlertGroupsParams { + var ( + activeDefault = bool(true) + inhibitedDefault = bool(true) + silencedDefault = bool(true) + ) + return &GetAlertGroupsParams{ + Active: &activeDefault, + Inhibited: &inhibitedDefault, + Silenced: &silencedDefault, + + Context: ctx, + } +} + +// NewGetAlertGroupsParamsWithHTTPClient creates a new GetAlertGroupsParams object +// with the default values initialized, and the ability to set a custom HTTPClient for a request +func NewGetAlertGroupsParamsWithHTTPClient(client *http.Client) *GetAlertGroupsParams { + var ( + activeDefault = bool(true) + inhibitedDefault = bool(true) + silencedDefault = bool(true) + ) + return &GetAlertGroupsParams{ + Active: &activeDefault, + Inhibited: &inhibitedDefault, + Silenced: &silencedDefault, + HTTPClient: client, + } +} + +/*GetAlertGroupsParams contains all the parameters to send to the API endpoint +for the get alert groups operation typically these are written to a http.Request +*/ +type GetAlertGroupsParams struct { + + /*Active + Show active alerts + + */ + Active *bool + /*Filter + A list of matchers to filter alerts by + + */ + Filter []string + /*Inhibited + Show inhibited alerts + + */ + Inhibited *bool + /*Receiver + A regex matching receivers to filter alerts by + + */ + Receiver *string + /*Silenced + Show silenced alerts + + */ + Silenced *bool + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithTimeout adds the timeout to the get alert groups params +func (o *GetAlertGroupsParams) WithTimeout(timeout time.Duration) *GetAlertGroupsParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the get alert groups params +func (o *GetAlertGroupsParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the get alert groups params +func (o *GetAlertGroupsParams) WithContext(ctx context.Context) *GetAlertGroupsParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the get alert groups params +func (o *GetAlertGroupsParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the get alert groups params +func (o *GetAlertGroupsParams) WithHTTPClient(client *http.Client) *GetAlertGroupsParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the get alert groups params +func (o *GetAlertGroupsParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithActive adds the active to the get alert groups params +func (o *GetAlertGroupsParams) WithActive(active *bool) *GetAlertGroupsParams { + o.SetActive(active) + return o +} + +// SetActive adds the active to the get alert groups params +func (o *GetAlertGroupsParams) SetActive(active *bool) { + o.Active = active +} + +// WithFilter adds the filter to the get alert groups params +func (o *GetAlertGroupsParams) WithFilter(filter []string) *GetAlertGroupsParams { + o.SetFilter(filter) + return o +} + +// SetFilter adds the filter to the get alert groups params +func (o *GetAlertGroupsParams) SetFilter(filter []string) { + o.Filter = filter +} + +// WithInhibited adds the inhibited to the get alert groups params +func (o *GetAlertGroupsParams) WithInhibited(inhibited *bool) *GetAlertGroupsParams { + o.SetInhibited(inhibited) + return o +} + +// SetInhibited adds the inhibited to the get alert groups params +func (o *GetAlertGroupsParams) SetInhibited(inhibited *bool) { + o.Inhibited = inhibited +} + +// WithReceiver adds the receiver to the get alert groups params +func (o *GetAlertGroupsParams) WithReceiver(receiver *string) *GetAlertGroupsParams { + o.SetReceiver(receiver) + return o +} + +// SetReceiver adds the receiver to the get alert groups params +func (o *GetAlertGroupsParams) SetReceiver(receiver *string) { + o.Receiver = receiver +} + +// WithSilenced adds the silenced to the get alert groups params +func (o *GetAlertGroupsParams) WithSilenced(silenced *bool) *GetAlertGroupsParams { + o.SetSilenced(silenced) + return o +} + +// SetSilenced adds the silenced to the get alert groups params +func (o *GetAlertGroupsParams) SetSilenced(silenced *bool) { + o.Silenced = silenced +} + +// WriteToRequest writes these params to a swagger request +func (o *GetAlertGroupsParams) 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 + } + } + + } + + valuesFilter := o.Filter + + joinedFilter := swag.JoinByFormat(valuesFilter, "multi") + // query array param filter + if err := r.SetQueryParam("filter", joinedFilter...); 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.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 len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/api/v2/client/alertgroup/get_alert_groups_responses.go b/api/v2/client/alertgroup/get_alert_groups_responses.go new file mode 100644 index 00000000..2e3c5c3f --- /dev/null +++ b/api/v2/client/alertgroup/get_alert_groups_responses.go @@ -0,0 +1,147 @@ +// 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 alertgroup + +// 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" + + strfmt "github.com/go-openapi/strfmt" + + models "github.com/prometheus/alertmanager/api/v2/models" +) + +// GetAlertGroupsReader is a Reader for the GetAlertGroups structure. +type GetAlertGroupsReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *GetAlertGroupsReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) { + switch response.Code() { + + case 200: + result := NewGetAlertGroupsOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + + case 400: + result := NewGetAlertGroupsBadRequest() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + + case 500: + result := NewGetAlertGroupsInternalServerError() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + + default: + return nil, runtime.NewAPIError("unknown error", response, response.Code()) + } +} + +// NewGetAlertGroupsOK creates a GetAlertGroupsOK with default headers values +func NewGetAlertGroupsOK() *GetAlertGroupsOK { + return &GetAlertGroupsOK{} +} + +/*GetAlertGroupsOK handles this case with default header values. + +Get alert groups response +*/ +type GetAlertGroupsOK struct { + Payload models.AlertGroups +} + +func (o *GetAlertGroupsOK) Error() string { + return fmt.Sprintf("[GET /alerts/groups][%d] getAlertGroupsOK %+v", 200, o.Payload) +} + +func (o *GetAlertGroupsOK) 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 +} + +// NewGetAlertGroupsBadRequest creates a GetAlertGroupsBadRequest with default headers values +func NewGetAlertGroupsBadRequest() *GetAlertGroupsBadRequest { + return &GetAlertGroupsBadRequest{} +} + +/*GetAlertGroupsBadRequest handles this case with default header values. + +Bad request +*/ +type GetAlertGroupsBadRequest struct { + Payload string +} + +func (o *GetAlertGroupsBadRequest) Error() string { + return fmt.Sprintf("[GET /alerts/groups][%d] getAlertGroupsBadRequest %+v", 400, o.Payload) +} + +func (o *GetAlertGroupsBadRequest) 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 +} + +// NewGetAlertGroupsInternalServerError creates a GetAlertGroupsInternalServerError with default headers values +func NewGetAlertGroupsInternalServerError() *GetAlertGroupsInternalServerError { + return &GetAlertGroupsInternalServerError{} +} + +/*GetAlertGroupsInternalServerError handles this case with default header values. + +Internal server error +*/ +type GetAlertGroupsInternalServerError struct { + Payload string +} + +func (o *GetAlertGroupsInternalServerError) Error() string { + return fmt.Sprintf("[GET /alerts/groups][%d] getAlertGroupsInternalServerError %+v", 500, o.Payload) +} + +func (o *GetAlertGroupsInternalServerError) 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_client.go b/api/v2/client/alertmanager_client.go index dbdf7eeb..d30ab2a8 100644 --- a/api/v2/client/alertmanager_client.go +++ b/api/v2/client/alertmanager_client.go @@ -26,6 +26,7 @@ import ( strfmt "github.com/go-openapi/strfmt" "github.com/prometheus/alertmanager/api/v2/client/alert" + "github.com/prometheus/alertmanager/api/v2/client/alertgroup" "github.com/prometheus/alertmanager/api/v2/client/general" "github.com/prometheus/alertmanager/api/v2/client/receiver" "github.com/prometheus/alertmanager/api/v2/client/silence" @@ -76,6 +77,8 @@ func New(transport runtime.ClientTransport, formats strfmt.Registry) *Alertmanag cli.Alert = alert.New(transport, formats) + cli.Alertgroup = alertgroup.New(transport, formats) + cli.General = general.New(transport, formats) cli.Receiver = receiver.New(transport, formats) @@ -128,6 +131,8 @@ func (cfg *TransportConfig) WithSchemes(schemes []string) *TransportConfig { type Alertmanager struct { Alert *alert.Client + Alertgroup *alertgroup.Client + General *general.Client Receiver *receiver.Client @@ -143,6 +148,8 @@ func (c *Alertmanager) SetTransport(transport runtime.ClientTransport) { c.Alert.SetTransport(transport) + c.Alertgroup.SetTransport(transport) + c.General.SetTransport(transport) c.Receiver.SetTransport(transport) diff --git a/api/v2/models/alert_group.go b/api/v2/models/alert_group.go new file mode 100644 index 00000000..dcb271ca --- /dev/null +++ b/api/v2/models/alert_group.go @@ -0,0 +1,142 @@ +// 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 ( + "strconv" + + strfmt "github.com/go-openapi/strfmt" + + "github.com/go-openapi/errors" + "github.com/go-openapi/swag" +) + +// AlertGroup alert group +// swagger:model alertGroup +type AlertGroup struct { + + // alerts + Alerts []*GettableAlert `json:"alerts"` + + // labels + Labels LabelSet `json:"labels,omitempty"` + + // receiver + Receiver *Receiver `json:"receiver,omitempty"` +} + +// Validate validates this alert group +func (m *AlertGroup) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateAlerts(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 *AlertGroup) 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)) + } + return err + } + } + + } + + return nil +} + +func (m *AlertGroup) validateLabels(formats strfmt.Registry) error { + + if swag.IsZero(m.Labels) { // not required + return nil + } + + if err := m.Labels.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("labels") + } + return err + } + + return nil +} + +func (m *AlertGroup) validateReceiver(formats strfmt.Registry) error { + + if swag.IsZero(m.Receiver) { // not required + return nil + } + + if m.Receiver != nil { + if err := m.Receiver.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("receiver") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *AlertGroup) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *AlertGroup) UnmarshalBinary(b []byte) error { + var res AlertGroup + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/api/v2/models/alert_groups.go b/api/v2/models/alert_groups.go new file mode 100644 index 00000000..9cf8efe6 --- /dev/null +++ b/api/v2/models/alert_groups.go @@ -0,0 +1,59 @@ +// 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 ( + "strconv" + + strfmt "github.com/go-openapi/strfmt" + + "github.com/go-openapi/errors" + "github.com/go-openapi/swag" +) + +// AlertGroups alert groups +// swagger:model alertGroups +type AlertGroups []*AlertGroup + +// Validate validates this alert groups +func (m AlertGroups) Validate(formats strfmt.Registry) error { + var res []error + + for i := 0; i < len(m); i++ { + if swag.IsZero(m[i]) { // not required + continue + } + + if m[i] != nil { + if err := m[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName(strconv.Itoa(i)) + } + return err + } + } + + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/api/v2/openapi.yaml b/api/v2/openapi.yaml index 2477bffb..026818f2 100644 --- a/api/v2/openapi.yaml +++ b/api/v2/openapi.yaml @@ -195,6 +195,50 @@ paths: $ref: '#/responses/InternalServerError' '400': $ref: '#/responses/BadRequest' + /alerts/groups: + get: + tags: + - alertgroup + operationId: getAlertGroups + description: Get a list of alert groups + 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 + - 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 + responses: + '200': + description: Get alert groups response + schema: + '$ref': '#/definitions/alertGroups' + '400': + $ref: '#/responses/BadRequest' + '500': + $ref: '#/responses/InternalServerError' responses: BadRequest: @@ -416,6 +460,21 @@ definitions: annotations: $ref: '#/definitions/labelSet' - $ref: '#/definitions/alert' + alertGroups: + type: array + items: + $ref: '#/definitions/alertGroup' + alertGroup: + type: object + properties: + labels: + $ref: '#/definitions/labelSet' + receiver: + $ref: '#/definitions/receiver' + alerts: + type: array + items: + $ref: '#/definitions/gettableAlert' alertStatus: type: object properties: diff --git a/api/v2/restapi/configure_alertmanager.go b/api/v2/restapi/configure_alertmanager.go index 68a73af6..8cfe1fc6 100644 --- a/api/v2/restapi/configure_alertmanager.go +++ b/api/v2/restapi/configure_alertmanager.go @@ -26,6 +26,7 @@ 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/general" "github.com/prometheus/alertmanager/api/v2/restapi/operations/receiver" "github.com/prometheus/alertmanager/api/v2/restapi/operations/silence" @@ -54,6 +55,9 @@ func configureAPI(api *operations.AlertmanagerAPI) http.Handler { api.SilenceDeleteSilenceHandler = silence.DeleteSilenceHandlerFunc(func(params silence.DeleteSilenceParams) middleware.Responder { return middleware.NotImplemented("operation silence.DeleteSilence has not yet been implemented") }) + api.AlertgroupGetAlertGroupsHandler = alertgroup.GetAlertGroupsHandlerFunc(func(params alertgroup.GetAlertGroupsParams) middleware.Responder { + return middleware.NotImplemented("operation alertgroup.GetAlertGroups has not yet been implemented") + }) 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 f66621df..08201bf4 100644 --- a/api/v2/restapi/embedded_spec.go +++ b/api/v2/restapi/embedded_spec.go @@ -147,6 +147,68 @@ func init() { } } }, + "/alerts/groups": { + "get": { + "description": "Get a list of alert groups", + "tags": [ + "alertgroup" + ], + "operationId": "getAlertGroups", + "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": "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" + } + ], + "responses": { + "200": { + "description": "Get alert groups response", + "schema": { + "$ref": "#/definitions/alertGroups" + } + }, + "400": { + "$ref": "#/responses/BadRequest" + }, + "500": { + "$ref": "#/responses/InternalServerError" + } + } + } + }, "/receivers": { "get": { "description": "Get list of all receivers (name of notification integrations)", @@ -331,6 +393,29 @@ func init() { } } }, + "alertGroup": { + "type": "object", + "properties": { + "alerts": { + "type": "array", + "items": { + "$ref": "#/definitions/gettableAlert" + } + }, + "labels": { + "$ref": "#/definitions/labelSet" + }, + "receiver": { + "$ref": "#/definitions/receiver" + } + } + }, + "alertGroups": { + "type": "array", + "items": { + "$ref": "#/definitions/alertGroup" + } + }, "alertStatus": { "type": "object", "required": [ @@ -848,6 +933,74 @@ func init() { } } }, + "/alerts/groups": { + "get": { + "description": "Get a list of alert groups", + "tags": [ + "alertgroup" + ], + "operationId": "getAlertGroups", + "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": "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" + } + ], + "responses": { + "200": { + "description": "Get alert groups response", + "schema": { + "$ref": "#/definitions/alertGroups" + } + }, + "400": { + "description": "Bad request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "type": "string" + } + } + } + } + }, "/receivers": { "get": { "description": "Get list of all receivers (name of notification integrations)", @@ -1044,6 +1197,29 @@ func init() { } } }, + "alertGroup": { + "type": "object", + "properties": { + "alerts": { + "type": "array", + "items": { + "$ref": "#/definitions/gettableAlert" + } + }, + "labels": { + "$ref": "#/definitions/labelSet" + }, + "receiver": { + "$ref": "#/definitions/receiver" + } + } + }, + "alertGroups": { + "type": "array", + "items": { + "$ref": "#/definitions/alertGroup" + } + }, "alertStatus": { "type": "object", "required": [ diff --git a/api/v2/restapi/operations/alertgroup/get_alert_groups.go b/api/v2/restapi/operations/alertgroup/get_alert_groups.go new file mode 100644 index 00000000..9a07ad6f --- /dev/null +++ b/api/v2/restapi/operations/alertgroup/get_alert_groups.go @@ -0,0 +1,72 @@ +// 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 alertgroup + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + middleware "github.com/go-openapi/runtime/middleware" +) + +// GetAlertGroupsHandlerFunc turns a function with the right signature into a get alert groups handler +type GetAlertGroupsHandlerFunc func(GetAlertGroupsParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetAlertGroupsHandlerFunc) Handle(params GetAlertGroupsParams) middleware.Responder { + return fn(params) +} + +// GetAlertGroupsHandler interface for that can handle valid get alert groups params +type GetAlertGroupsHandler interface { + Handle(GetAlertGroupsParams) middleware.Responder +} + +// NewGetAlertGroups creates a new http.Handler for the get alert groups operation +func NewGetAlertGroups(ctx *middleware.Context, handler GetAlertGroupsHandler) *GetAlertGroups { + return &GetAlertGroups{Context: ctx, Handler: handler} +} + +/*GetAlertGroups swagger:route GET /alerts/groups alertgroup getAlertGroups + +Get a list of alert groups + +*/ +type GetAlertGroups struct { + Context *middleware.Context + Handler GetAlertGroupsHandler +} + +func (o *GetAlertGroups) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + r = rCtx + } + var Params = NewGetAlertGroupsParams() + + 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/alertgroup/get_alert_groups_parameters.go b/api/v2/restapi/operations/alertgroup/get_alert_groups_parameters.go new file mode 100644 index 00000000..4920f0ad --- /dev/null +++ b/api/v2/restapi/operations/alertgroup/get_alert_groups_parameters.go @@ -0,0 +1,242 @@ +// 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 alertgroup + +// 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/swag" + + strfmt "github.com/go-openapi/strfmt" +) + +// NewGetAlertGroupsParams creates a new GetAlertGroupsParams object +// with the default values initialized. +func NewGetAlertGroupsParams() GetAlertGroupsParams { + + var ( + // initialize parameters with default values + + activeDefault = bool(true) + + inhibitedDefault = bool(true) + + silencedDefault = bool(true) + ) + + return GetAlertGroupsParams{ + Active: &activeDefault, + + Inhibited: &inhibitedDefault, + + Silenced: &silencedDefault, + } +} + +// GetAlertGroupsParams contains all the bound params for the get alert groups operation +// typically these are obtained from a http.Request +// +// swagger:parameters getAlertGroups +type GetAlertGroupsParams 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 + /*Show inhibited alerts + In: query + Default: true + */ + Inhibited *bool + /*A regex matching receivers to filter alerts by + In: query + */ + Receiver *string + /*Show silenced alerts + In: query + Default: true + */ + Silenced *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 NewGetAlertGroupsParams() beforehand. +func (o *GetAlertGroupsParams) 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) + } + + qInhibited, qhkInhibited, _ := qs.GetOK("inhibited") + if err := o.bindInhibited(qInhibited, qhkInhibited, 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) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindActive binds and validates parameter Active from query. +func (o *GetAlertGroupsParams) 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 NewGetAlertGroupsParams() + 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 *GetAlertGroupsParams) 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 +} + +// bindInhibited binds and validates parameter Inhibited from query. +func (o *GetAlertGroupsParams) 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 NewGetAlertGroupsParams() + return nil + } + + value, err := swag.ConvertBool(raw) + if err != nil { + return errors.InvalidType("inhibited", "query", "bool", raw) + } + o.Inhibited = &value + + return nil +} + +// bindReceiver binds and validates parameter Receiver from query. +func (o *GetAlertGroupsParams) 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 *GetAlertGroupsParams) 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 NewGetAlertGroupsParams() + return nil + } + + value, err := swag.ConvertBool(raw) + if err != nil { + return errors.InvalidType("silenced", "query", "bool", raw) + } + o.Silenced = &value + + return nil +} diff --git a/api/v2/restapi/operations/alertgroup/get_alert_groups_responses.go b/api/v2/restapi/operations/alertgroup/get_alert_groups_responses.go new file mode 100644 index 00000000..1218c129 --- /dev/null +++ b/api/v2/restapi/operations/alertgroup/get_alert_groups_responses.go @@ -0,0 +1,161 @@ +// 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 alertgroup + +// 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" + + models "github.com/prometheus/alertmanager/api/v2/models" +) + +// GetAlertGroupsOKCode is the HTTP code returned for type GetAlertGroupsOK +const GetAlertGroupsOKCode int = 200 + +/*GetAlertGroupsOK Get alert groups response + +swagger:response getAlertGroupsOK +*/ +type GetAlertGroupsOK struct { + + /* + In: Body + */ + Payload models.AlertGroups `json:"body,omitempty"` +} + +// NewGetAlertGroupsOK creates GetAlertGroupsOK with default headers values +func NewGetAlertGroupsOK() *GetAlertGroupsOK { + + return &GetAlertGroupsOK{} +} + +// WithPayload adds the payload to the get alert groups o k response +func (o *GetAlertGroupsOK) WithPayload(payload models.AlertGroups) *GetAlertGroupsOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get alert groups o k response +func (o *GetAlertGroupsOK) SetPayload(payload models.AlertGroups) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetAlertGroupsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + payload := o.Payload + if payload == nil { + payload = make(models.AlertGroups, 0, 50) + } + + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + +} + +// GetAlertGroupsBadRequestCode is the HTTP code returned for type GetAlertGroupsBadRequest +const GetAlertGroupsBadRequestCode int = 400 + +/*GetAlertGroupsBadRequest Bad request + +swagger:response getAlertGroupsBadRequest +*/ +type GetAlertGroupsBadRequest struct { + + /* + In: Body + */ + Payload string `json:"body,omitempty"` +} + +// NewGetAlertGroupsBadRequest creates GetAlertGroupsBadRequest with default headers values +func NewGetAlertGroupsBadRequest() *GetAlertGroupsBadRequest { + + return &GetAlertGroupsBadRequest{} +} + +// WithPayload adds the payload to the get alert groups bad request response +func (o *GetAlertGroupsBadRequest) WithPayload(payload string) *GetAlertGroupsBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get alert groups bad request response +func (o *GetAlertGroupsBadRequest) SetPayload(payload string) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetAlertGroupsBadRequest) 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 + } + +} + +// GetAlertGroupsInternalServerErrorCode is the HTTP code returned for type GetAlertGroupsInternalServerError +const GetAlertGroupsInternalServerErrorCode int = 500 + +/*GetAlertGroupsInternalServerError Internal server error + +swagger:response getAlertGroupsInternalServerError +*/ +type GetAlertGroupsInternalServerError struct { + + /* + In: Body + */ + Payload string `json:"body,omitempty"` +} + +// NewGetAlertGroupsInternalServerError creates GetAlertGroupsInternalServerError with default headers values +func NewGetAlertGroupsInternalServerError() *GetAlertGroupsInternalServerError { + + return &GetAlertGroupsInternalServerError{} +} + +// WithPayload adds the payload to the get alert groups internal server error response +func (o *GetAlertGroupsInternalServerError) WithPayload(payload string) *GetAlertGroupsInternalServerError { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get alert groups internal server error response +func (o *GetAlertGroupsInternalServerError) SetPayload(payload string) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetAlertGroupsInternalServerError) 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/alertgroup/get_alert_groups_urlbuilder.go b/api/v2/restapi/operations/alertgroup/get_alert_groups_urlbuilder.go new file mode 100644 index 00000000..919547b3 --- /dev/null +++ b/api/v2/restapi/operations/alertgroup/get_alert_groups_urlbuilder.go @@ -0,0 +1,158 @@ +// 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 alertgroup + +// 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" +) + +// GetAlertGroupsURL generates an URL for the get alert groups operation +type GetAlertGroupsURL struct { + Active *bool + Filter []string + Inhibited *bool + Receiver *string + Silenced *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 *GetAlertGroupsURL) WithBasePath(bp string) *GetAlertGroupsURL { + 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 *GetAlertGroupsURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetAlertGroupsURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/alerts/groups" + + _basePath := o._basePath + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + qs := make(url.Values) + + var active string + if o.Active != nil { + active = swag.FormatBool(*o.Active) + } + if active != "" { + qs.Set("active", active) + } + + 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 inhibited string + if o.Inhibited != nil { + inhibited = swag.FormatBool(*o.Inhibited) + } + if inhibited != "" { + qs.Set("inhibited", inhibited) + } + + var receiver string + if o.Receiver != nil { + receiver = *o.Receiver + } + if receiver != "" { + qs.Set("receiver", receiver) + } + + var silenced string + if o.Silenced != nil { + silenced = swag.FormatBool(*o.Silenced) + } + if silenced != "" { + qs.Set("silenced", silenced) + } + + _result.RawQuery = qs.Encode() + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *GetAlertGroupsURL) 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 *GetAlertGroupsURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetAlertGroupsURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetAlertGroupsURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetAlertGroupsURL") + } + + 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 *GetAlertGroupsURL) 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 e999dba5..8f57c7a6 100644 --- a/api/v2/restapi/operations/alertmanager_api.go +++ b/api/v2/restapi/operations/alertmanager_api.go @@ -34,6 +34,7 @@ import ( "github.com/go-openapi/swag" "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/general" "github.com/prometheus/alertmanager/api/v2/restapi/operations/receiver" "github.com/prometheus/alertmanager/api/v2/restapi/operations/silence" @@ -59,6 +60,9 @@ func NewAlertmanagerAPI(spec *loads.Document) *AlertmanagerAPI { SilenceDeleteSilenceHandler: silence.DeleteSilenceHandlerFunc(func(params silence.DeleteSilenceParams) middleware.Responder { return middleware.NotImplemented("operation SilenceDeleteSilence has not yet been implemented") }), + AlertgroupGetAlertGroupsHandler: alertgroup.GetAlertGroupsHandlerFunc(func(params alertgroup.GetAlertGroupsParams) middleware.Responder { + return middleware.NotImplemented("operation AlertgroupGetAlertGroups has not yet been implemented") + }), AlertGetAlertsHandler: alert.GetAlertsHandlerFunc(func(params alert.GetAlertsParams) middleware.Responder { return middleware.NotImplemented("operation AlertGetAlerts has not yet been implemented") }), @@ -113,6 +117,8 @@ type AlertmanagerAPI struct { // SilenceDeleteSilenceHandler sets the operation handler for the delete silence operation SilenceDeleteSilenceHandler silence.DeleteSilenceHandler + // AlertgroupGetAlertGroupsHandler sets the operation handler for the get alert groups operation + AlertgroupGetAlertGroupsHandler alertgroup.GetAlertGroupsHandler // AlertGetAlertsHandler sets the operation handler for the get alerts operation AlertGetAlertsHandler alert.GetAlertsHandler // ReceiverGetReceiversHandler sets the operation handler for the get receivers operation @@ -194,6 +200,10 @@ func (o *AlertmanagerAPI) Validate() error { unregistered = append(unregistered, "silence.DeleteSilenceHandler") } + if o.AlertgroupGetAlertGroupsHandler == nil { + unregistered = append(unregistered, "alertgroup.GetAlertGroupsHandler") + } + if o.AlertGetAlertsHandler == nil { unregistered = append(unregistered, "alert.GetAlertsHandler") } @@ -325,6 +335,11 @@ func (o *AlertmanagerAPI) initHandlerCache() { } o.handlers["DELETE"]["/silence/{silenceID}"] = silence.NewDeleteSilence(o.context, o.SilenceDeleteSilenceHandler) + 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) } diff --git a/cmd/alertmanager/main.go b/cmd/alertmanager/main.go index 7adee6e4..146d9e0d 100644 --- a/cmd/alertmanager/main.go +++ b/cmd/alertmanager/main.go @@ -246,6 +246,13 @@ func run() int { } defer alerts.Close() + var disp *dispatch.Dispatcher + defer 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) + } + api, err := api.New(api.Options{ Alerts: alerts, Silences: silences, @@ -255,6 +262,7 @@ func run() int { Concurrency: *getConcurrency, Logger: log.With(logger, "component", "api"), Registry: prometheus.DefaultRegisterer, + GroupFunc: groupFn, }) if err != nil { @@ -284,11 +292,8 @@ func run() int { silencer *silence.Silencer tmpl *template.Template pipeline notify.Stage - disp *dispatch.Dispatcher ) - defer disp.Stop() - configCoordinator := config.NewCoordinator( *configFile, prometheus.DefaultRegisterer, diff --git a/dispatch/dispatch.go b/dispatch/dispatch.go index 445f0917..f6360fd4 100644 --- a/dispatch/dispatch.go +++ b/dispatch/dispatch.go @@ -133,6 +133,86 @@ func (d *Dispatcher) run(it provider.AlertIterator) { } } +// AlertGroup represents how alerts exist within an aggrGroup. +type AlertGroup struct { + Alerts []*types.Alert + Labels model.LabelSet + Receiver string +} + +type AlertGroups []*AlertGroup + +func (ag AlertGroups) Swap(i, j int) { ag[i], ag[j] = ag[j], ag[i] } +func (ag AlertGroups) Less(i, j int) bool { return ag[i].Labels.Before(ag[j].Labels) } +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) { + groups := AlertGroups{} + + d.mtx.RLock() + defer d.mtx.RUnlock() + + seen := map[model.Fingerprint]*AlertGroup{} + + // Keep a list of receivers for an alert to prevent checking each alert + // again against all routes. The alert has already matched against this + // route on ingestion. + receivers := map[model.Fingerprint][]string{} + + for route, ags := range d.aggrGroups { + if !routeFilter(route) { + continue + } + + for _, ag := range ags { + receiver := route.RouteOpts.Receiver + alertGroup, ok := seen[ag.fingerprint()] + if !ok { + alertGroup = &AlertGroup{ + Labels: ag.labels, + Receiver: receiver, + } + + seen[ag.fingerprint()] = alertGroup + } + + now := time.Now() + + alerts := ag.alerts.List() + filteredAlerts := make([]*types.Alert, 0, len(alerts)) + for a := range alerts { + if !alertFilter(a, now) { + continue + } + + fp := a.Fingerprint() + if r, ok := receivers[fp]; ok { + // Receivers slice already exists. Add + // the current receiver to the slice. + receivers[fp] = append(r, receiver) + } else { + // First time we've seen this alert fingerprint. + // Initialize a new receivers slice. + receivers[fp] = []string{receiver} + } + + filteredAlerts = append(filteredAlerts, a) + } + if len(filteredAlerts) == 0 { + continue + } + alertGroup.Alerts = filteredAlerts + + groups = append(groups, alertGroup) + } + } + + sort.Sort(groups) + + return groups, receivers +} + // Stop the dispatcher. func (d *Dispatcher) Stop() { if d == nil || d.cancel == nil { diff --git a/dispatch/dispatch_test.go b/dispatch/dispatch_test.go index 8ce94679..46518686 100644 --- a/dispatch/dispatch_test.go +++ b/dispatch/dispatch_test.go @@ -22,9 +22,13 @@ import ( "time" "github.com/go-kit/kit/log" + "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" + "github.com/stretchr/testify/require" + "github.com/prometheus/alertmanager/config" "github.com/prometheus/alertmanager/notify" + "github.com/prometheus/alertmanager/provider/mem" "github.com/prometheus/alertmanager/types" ) @@ -323,3 +327,98 @@ func TestGroupByAllLabels(t *testing.T) { t.Fatalf("expected labels are %v, but got %v", expLs, ls) } } + +func TestGroups(t *testing.T) { + logger := log.NewNopLogger() + conf, _, err := config.LoadFile("testdata/conf.yml") + if err != nil { + t.Fatal(err) + } + route := NewRoute(conf.Route, nil) + marker := types.NewMarker(prometheus.DefaultRegisterer) + alerts, err := mem.NewAlerts(context.Background(), marker, time.Hour, logger) + if err != nil { + t.Fatal(err) + } + defer alerts.Close() + timeout := func(d time.Duration) time.Duration { return time.Duration(0) } + dispatcher := NewDispatcher(alerts, route, &noopStage{}, marker, timeout, logger) + go dispatcher.Run() + + // create alerts. the dispatcher will automatically create the groups + alerts.Put( + newAlert(model.LabelSet{"env": "testing", "alertname": "TestingAlert", "service": "api"}), + newAlert(model.LabelSet{"env": "prod", "alertname": "HighErrorRate", "cluster": "aa", "service": "api"}), + newAlert(model.LabelSet{"env": "prod", "alertname": "HighErrorRate", "cluster": "bb", "service": "api"}), + newAlert(model.LabelSet{"env": "prod", "alertname": "HighLatency", "cluster": "bb", "service": "db", "kafka": "yes"}), + ) + // Let alerts get processed + time.Sleep(time.Second) + + var routeFilter, alertFilter bool + alertGroups, receivers := dispatcher.Groups(func(_ *Route) bool { + routeFilter = true + return true + }, func(_ *types.Alert, _ time.Time) bool { + alertFilter = true + return true + }) + + // Verify filter functions were called + require.True(t, routeFilter) + require.True(t, alertFilter) + + // Verify grouping works + require.Equal(t, 5, len(alertGroups)) + for _, ag := range alertGroups { + if len(ag.Labels) == 2 { + // testing receiver + require.Equal(t, 2, len(ag.Labels)) + require.Equal(t, model.LabelSet{"alertname": "TestingAlert", "service": "api"}, ag.Labels) + for _, alert := range ag.Alerts { + alertsReceivers, ok := receivers[alert.Fingerprint()] + require.True(t, ok) + require.Equal(t, 1, len(alertsReceivers)) + require.Equal(t, "testing", alertsReceivers[0]) + } + continue + } + require.Equal(t, 3, len(ag.Labels)) + for _, alert := range ag.Alerts { + alertsReceivers, ok := receivers[alert.Fingerprint()] + require.True(t, ok) + if labelValue := ag.Labels["alertname"]; string(labelValue) == "HighLatency" { + // Matches both prod and kafka receivers + require.Equal(t, []string{"prod", "kafka"}, alertsReceivers) + continue + } + require.Equal(t, 1, len(alertsReceivers)) + require.Equal(t, "prod", alertsReceivers[0]) + } + } +} + +type noopStage struct{} + +func (n *noopStage) Exec(ctx context.Context, l log.Logger, alerts ...*types.Alert) (context.Context, []*types.Alert, error) { + return ctx, nil, nil +} + +var ( + t0 = time.Now() + t1 = t0.Add(100 * time.Millisecond) +) + +func newAlert(labels model.LabelSet) *types.Alert { + return &types.Alert{ + Alert: model.Alert{ + Labels: labels, + Annotations: model.LabelSet{"foo": "bar"}, + StartsAt: t0, + EndsAt: t1, + GeneratorURL: "http://example.com/prometheus", + }, + UpdatedAt: t0, + Timeout: false, + } +} diff --git a/dispatch/testdata/conf.yml b/dispatch/testdata/conf.yml new file mode 100644 index 00000000..e0876fa2 --- /dev/null +++ b/dispatch/testdata/conf.yml @@ -0,0 +1,34 @@ +global: + resolve_timeout: 5m + +receivers: +- name: 'testing' + webhook_configs: + - url: 'http://127.0.0.1:5001/' +- name: 'prod' + webhook_configs: + - url: 'http://127.0.0.1:5001/' +- name: 'kafka' + webhook_configs: + - url: 'http://127.0.0.1:5001/' + +route: + group_by: ['alertname'] + group_wait: 10s + group_interval: 10s + repeat_interval: 1h + 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']