diff --git a/.gitignore b/.gitignore index 7ef77d321a..26a705f498 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,7 @@ !/.promu.yml !/api/v2/openapi.yaml !.github/workflows/*.yml + +# Editor +.vscode +.DS_Store diff --git a/api/api.go b/api/api.go index 2e1e1ea425..facbf56fc9 100644 --- a/api/api.go +++ b/api/api.go @@ -29,6 +29,7 @@ import ( "github.com/prometheus/alertmanager/cluster" "github.com/prometheus/alertmanager/config" "github.com/prometheus/alertmanager/dispatch" + "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/provider" "github.com/prometheus/alertmanager/silence" "github.com/prometheus/alertmanager/types" @@ -186,8 +187,8 @@ func (api *API) Register(r *route.Router, routePrefix string) *http.ServeMux { // Update config and resolve timeout of each API. APIv2 also needs // setAlertStatus to be updated. -func (api *API) Update(cfg *config.Config, setAlertStatus func(model.LabelSet)) { - api.v2.Update(cfg, setAlertStatus) +func (api *API) Update(cfg *config.Config, receivers []*notify.Receiver, setAlertStatus func(model.LabelSet)) { + api.v2.Update(cfg, setAlertStatus, receivers) } func (api *API) limitHandler(h http.Handler) http.Handler { diff --git a/api/v2/api.go b/api/v2/api.go index b4f57e75e2..6b20501ae3 100644 --- a/api/v2/api.go +++ b/api/v2/api.go @@ -33,7 +33,6 @@ import ( "github.com/prometheus/common/version" "github.com/rs/cors" - "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" @@ -42,10 +41,13 @@ import ( 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" + + "github.com/prometheus/alertmanager/api/metrics" "github.com/prometheus/alertmanager/cluster" "github.com/prometheus/alertmanager/config" "github.com/prometheus/alertmanager/dispatch" "github.com/prometheus/alertmanager/matchers/compat" + "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/pkg/labels" "github.com/prometheus/alertmanager/provider" "github.com/prometheus/alertmanager/silence" @@ -73,7 +75,8 @@ type API struct { logger log.Logger m *metrics.Alerts - Handler http.Handler + Handler http.Handler + receivers []*notify.Receiver } type ( @@ -155,13 +158,14 @@ func (api *API) requestLogger(req *http.Request) log.Logger { } // Update sets the API struct members that may change between reloads of alertmanager. -func (api *API) Update(cfg *config.Config, setAlertStatus setAlertStatusFn) { +func (api *API) Update(cfg *config.Config, setAlertStatus setAlertStatusFn, receivers []*notify.Receiver) { api.mtx.Lock() defer api.mtx.Unlock() api.alertmanagerConfig = cfg api.route = dispatch.NewRoute(cfg.Route, nil) api.setAlertStatus = setAlertStatus + api.receivers = receivers } func (api *API) getStatusHandler(params general_ops.GetStatusParams) middleware.Responder { @@ -222,11 +226,40 @@ func (api *API) getStatusHandler(params general_ops.GetStatusParams) middleware. func (api *API) getReceiversHandler(params receiver_ops.GetReceiversParams) middleware.Responder { api.mtx.RLock() - defer api.mtx.RUnlock() + configReceivers := api.receivers + api.mtx.RUnlock() + + receivers := make([]*open_api_models.Receiver, 0, len(configReceivers)) + for _, r := range configReceivers { + integrations := make([]*open_api_models.Integration, 0, len(r.Integrations())) + + for _, integration := range r.Integrations() { + notify, duration, err := integration.GetReport() + iname := integration.String() + sendResolved := integration.SendResolved() + integrations = append(integrations, &open_api_models.Integration{ + Name: &iname, + SendResolved: &sendResolved, + LastNotifyAttempt: strfmt.DateTime(notify.UTC()), + LastNotifyAttemptDuration: duration.String(), + LastNotifyAttemptError: func() string { + if err != nil { + return err.Error() + } + return "" + }(), + }) + } + + rName := r.Name() + active := r.Active() + model := &open_api_models.Receiver{ + Name: &rName, + Active: &active, + Integrations: integrations, + } - receivers := make([]*open_api_models.Receiver, 0, len(api.alertmanagerConfig.Receivers)) - for i := range api.alertmanagerConfig.Receivers { - receivers = append(receivers, &open_api_models.Receiver{Name: &api.alertmanagerConfig.Receivers[i].Name}) + receivers = append(receivers, model) } return receiver_ops.NewGetReceiversOK().WithPayload(receivers) diff --git a/api/v2/api_test.go b/api/v2/api_test.go index d520dbde13..79e89158d3 100644 --- a/api/v2/api_test.go +++ b/api/v2/api_test.go @@ -33,6 +33,7 @@ import ( receiver_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/receiver" silence_ops "github.com/prometheus/alertmanager/api/v2/restapi/operations/silence" "github.com/prometheus/alertmanager/config" + "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/pkg/labels" "github.com/prometheus/alertmanager/silence" "github.com/prometheus/alertmanager/silence/silencepb" @@ -484,6 +485,10 @@ receivers: uptime: time.Now(), logger: log.NewNopLogger(), alertmanagerConfig: cfg, + receivers: []*notify.Receiver{ + notify.NewReceiver("team-X", true, nil), + notify.NewReceiver("team-Y", true, nil), + }, } for _, tc := range []struct { @@ -491,7 +496,7 @@ receivers: expectedCode int }{ { - `[{"name":"team-X"},{"name":"team-Y"}]`, + `[{"active":true,"integrations":[],"name":"team-X"},{"active":true,"integrations":[],"name":"team-Y"}]`, 200, }, } { diff --git a/api/v2/models/integration.go b/api/v2/models/integration.go new file mode 100644 index 0000000000..46f903826d --- /dev/null +++ b/api/v2/models/integration.go @@ -0,0 +1,128 @@ +// 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" +) + +// Integration integration +// +// swagger:model integration +type Integration struct { + + // A timestamp indicating the last attempt to deliver a notification regardless of the outcome. + // Format: date-time + LastNotifyAttempt strfmt.DateTime `json:"lastNotifyAttempt,omitempty"` + + // Duration of the last attempt to deliver a notification in humanized format (`1s` or `15ms`, etc). + LastNotifyAttemptDuration string `json:"lastNotifyAttemptDuration,omitempty"` + + // Error string for the last attempt to deliver a notification. Empty if the last attempt was successful. + LastNotifyAttemptError string `json:"lastNotifyAttemptError,omitempty"` + + // name + // Required: true + Name *string `json:"name"` + + // send resolved + // Required: true + SendResolved *bool `json:"sendResolved"` +} + +// Validate validates this integration +func (m *Integration) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateLastNotifyAttempt(formats); err != nil { + res = append(res, err) + } + + if err := m.validateName(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSendResolved(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *Integration) validateLastNotifyAttempt(formats strfmt.Registry) error { + if swag.IsZero(m.LastNotifyAttempt) { // not required + return nil + } + + if err := validate.FormatOf("lastNotifyAttempt", "body", "date-time", m.LastNotifyAttempt.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *Integration) validateName(formats strfmt.Registry) error { + + if err := validate.Required("name", "body", m.Name); err != nil { + return err + } + + return nil +} + +func (m *Integration) validateSendResolved(formats strfmt.Registry) error { + + if err := validate.Required("sendResolved", "body", m.SendResolved); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this integration based on context it is used +func (m *Integration) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *Integration) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *Integration) UnmarshalBinary(b []byte) error { + var res Integration + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/api/v2/models/receiver.go b/api/v2/models/receiver.go index 8e1bf9ee45..8c248ea737 100644 --- a/api/v2/models/receiver.go +++ b/api/v2/models/receiver.go @@ -21,6 +21,7 @@ package models import ( "context" + "strconv" "github.com/go-openapi/errors" "github.com/go-openapi/strfmt" @@ -33,6 +34,14 @@ import ( // swagger:model receiver type Receiver struct { + // active + // Required: true + Active *bool `json:"active"` + + // integrations + // Required: true + Integrations []*Integration `json:"integrations"` + // name // Required: true Name *string `json:"name"` @@ -42,6 +51,14 @@ type Receiver struct { func (m *Receiver) Validate(formats strfmt.Registry) error { var res []error + if err := m.validateActive(formats); err != nil { + res = append(res, err) + } + + if err := m.validateIntegrations(formats); err != nil { + res = append(res, err) + } + if err := m.validateName(formats); err != nil { res = append(res, err) } @@ -52,6 +69,42 @@ func (m *Receiver) Validate(formats strfmt.Registry) error { return nil } +func (m *Receiver) validateActive(formats strfmt.Registry) error { + + if err := validate.Required("active", "body", m.Active); err != nil { + return err + } + + return nil +} + +func (m *Receiver) validateIntegrations(formats strfmt.Registry) error { + + if err := validate.Required("integrations", "body", m.Integrations); err != nil { + return err + } + + for i := 0; i < len(m.Integrations); i++ { + if swag.IsZero(m.Integrations[i]) { // not required + continue + } + + if m.Integrations[i] != nil { + if err := m.Integrations[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("integrations" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("integrations" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + func (m *Receiver) validateName(formats strfmt.Registry) error { if err := validate.Required("name", "body", m.Name); err != nil { @@ -61,8 +114,42 @@ func (m *Receiver) validateName(formats strfmt.Registry) error { return nil } -// ContextValidate validates this receiver based on context it is used +// ContextValidate validate this receiver based on the context it is used func (m *Receiver) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateIntegrations(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *Receiver) contextValidateIntegrations(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.Integrations); i++ { + + if m.Integrations[i] != nil { + + if swag.IsZero(m.Integrations[i]) { // not required + return nil + } + + if err := m.Integrations[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("integrations" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("integrations" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + return nil } diff --git a/api/v2/openapi.yaml b/api/v2/openapi.yaml index 549cc33e7f..c956a94d39 100644 --- a/api/v2/openapi.yaml +++ b/api/v2/openapi.yaml @@ -510,8 +510,36 @@ definitions: properties: name: type: string + active: + type: boolean + integrations: + type: array + items: + $ref: '#/definitions/integration' + required: + - name + - active + - integrations + integration: + type: object + properties: + name: + type: string + sendResolved: + type: boolean + lastNotifyAttempt: + description: A timestamp indicating the last attempt to deliver a notification regardless of the outcome. + type: string + format: date-time + lastNotifyAttemptDuration: + description: Duration of the last attempt to deliver a notification in humanized format (`1s` or `15ms`, etc). + type: string + lastNotifyAttemptError: + description: Error string for the last attempt to deliver a notification. Empty if the last attempt was successful. + type: string required: - name + - sendResolved labelSet: type: object additionalProperties: diff --git a/api/v2/restapi/embedded_spec.go b/api/v2/restapi/embedded_spec.go index 0e23efd060..87a920764f 100644 --- a/api/v2/restapi/embedded_spec.go +++ b/api/v2/restapi/embedded_spec.go @@ -605,6 +605,34 @@ func init() { "$ref": "#/definitions/gettableSilence" } }, + "integration": { + "type": "object", + "required": [ + "name", + "sendResolved" + ], + "properties": { + "lastNotifyAttempt": { + "description": "A timestamp indicating the last attempt to deliver a notification regardless of the outcome.", + "type": "string", + "format": "date-time" + }, + "lastNotifyAttemptDuration": { + "description": "Duration of the last attempt to deliver a notification in humanized format (` + "`" + `1s` + "`" + ` or ` + "`" + `15ms` + "`" + `, etc).", + "type": "string" + }, + "lastNotifyAttemptError": { + "description": "Error string for the last attempt to deliver a notification. Empty if the last attempt was successful.", + "type": "string" + }, + "name": { + "type": "string" + }, + "sendResolved": { + "type": "boolean" + } + } + }, "labelSet": { "type": "object", "additionalProperties": { @@ -703,9 +731,20 @@ func init() { "receiver": { "type": "object", "required": [ - "name" + "name", + "active", + "integrations" ], "properties": { + "active": { + "type": "boolean" + }, + "integrations": { + "type": "array", + "items": { + "$ref": "#/definitions/integration" + } + }, "name": { "type": "string" } @@ -1428,6 +1467,34 @@ func init() { "$ref": "#/definitions/gettableSilence" } }, + "integration": { + "type": "object", + "required": [ + "name", + "sendResolved" + ], + "properties": { + "lastNotifyAttempt": { + "description": "A timestamp indicating the last attempt to deliver a notification regardless of the outcome.", + "type": "string", + "format": "date-time" + }, + "lastNotifyAttemptDuration": { + "description": "Duration of the last attempt to deliver a notification in humanized format (` + "`" + `1s` + "`" + ` or ` + "`" + `15ms` + "`" + `, etc).", + "type": "string" + }, + "lastNotifyAttemptError": { + "description": "Error string for the last attempt to deliver a notification. Empty if the last attempt was successful.", + "type": "string" + }, + "name": { + "type": "string" + }, + "sendResolved": { + "type": "boolean" + } + } + }, "labelSet": { "type": "object", "additionalProperties": { @@ -1526,9 +1593,20 @@ func init() { "receiver": { "type": "object", "required": [ - "name" + "name", + "active", + "integrations" ], "properties": { + "active": { + "type": "boolean" + }, + "integrations": { + "type": "array", + "items": { + "$ref": "#/definitions/integration" + } + }, "name": { "type": "string" } diff --git a/cmd/alertmanager/main.go b/cmd/alertmanager/main.go index b2938189d5..6a5fbd63f5 100644 --- a/cmd/alertmanager/main.go +++ b/cmd/alertmanager/main.go @@ -380,26 +380,26 @@ func run() int { // Build the routing tree and record which receivers are used. routes := dispatch.NewRoute(conf.Route, nil) - activeReceivers := make(map[string]struct{}) + activeReceiversMap := make(map[string]struct{}) routes.Walk(func(r *dispatch.Route) { - activeReceivers[r.RouteOpts.Receiver] = struct{}{} + activeReceiversMap[r.RouteOpts.Receiver] = struct{}{} }) // Build the map of receiver to integrations. - receivers := make(map[string][]notify.Integration, len(activeReceivers)) + receivers := make([]*notify.Receiver, 0, len(activeReceiversMap)) var integrationsNum int for _, rcv := range conf.Receivers { - if _, found := activeReceivers[rcv.Name]; !found { + if _, found := activeReceiversMap[rcv.Name]; !found { // No need to build a receiver if no route is using it. level.Info(configLogger).Log("msg", "skipping creation of receiver not referenced by any route", "receiver", rcv.Name) + receivers = append(receivers, notify.NewReceiver(rcv.Name, false, nil)) continue } integrations, err := receiver.BuildReceiverIntegrations(rcv, tmpl, logger) if err != nil { return err } - // rcv.Name is guaranteed to be unique across all receivers. - receivers[rcv.Name] = integrations + receivers = append(receivers, notify.NewReceiver(rcv.Name, true, integrations)) integrationsNum += len(integrations) } @@ -429,8 +429,16 @@ func run() int { pipelinePeer = peer } + activeReceivers := make([]*notify.Receiver, 0, len(receivers)) + for i := range receivers { + if !receivers[i].Active() { + continue + } + activeReceivers = append(activeReceivers, receivers[i]) + } + pipeline := pipelineBuilder.New( - receivers, + activeReceivers, waitFunc, inhibitor, silencer, @@ -439,11 +447,11 @@ func run() int { pipelinePeer, ) - configuredReceivers.Set(float64(len(activeReceivers))) + configuredReceivers.Set(float64(len(activeReceiversMap))) configuredIntegrations.Set(float64(integrationsNum)) configuredInhibitionRules.Set(float64(len(conf.InhibitRules))) - api.Update(conf, func(labels model.LabelSet) { + api.Update(conf, receivers, func(labels model.LabelSet) { inhibitor.Mutes(labels) silencer.Mutes(labels) }) diff --git a/config/receiver/receiver.go b/config/receiver/receiver.go index 9bb039ef05..fdbf35e698 100644 --- a/config/receiver/receiver.go +++ b/config/receiver/receiver.go @@ -39,10 +39,10 @@ import ( // BuildReceiverIntegrations builds a list of integration notifiers off of a // receiver config. -func BuildReceiverIntegrations(nc config.Receiver, tmpl *template.Template, logger log.Logger, httpOpts ...commoncfg.HTTPClientOption) ([]notify.Integration, error) { +func BuildReceiverIntegrations(nc config.Receiver, tmpl *template.Template, logger log.Logger, httpOpts ...commoncfg.HTTPClientOption) ([]*notify.Integration, error) { var ( errs types.MultiError - integrations []notify.Integration + integrations []*notify.Integration add = func(name string, i int, rs notify.ResolvedSender, f func(l log.Logger) (notify.Notifier, error)) { n, err := f(log.With(logger, "integration", name)) if err != nil { diff --git a/config/receiver/receiver_test.go b/config/receiver/receiver_test.go index 3d146a98d0..e19820e99c 100644 --- a/config/receiver/receiver_test.go +++ b/config/receiver/receiver_test.go @@ -31,7 +31,7 @@ func TestBuildReceiverIntegrations(t *testing.T) { for _, tc := range []struct { receiver config.Receiver err bool - exp []notify.Integration + exp []*notify.Integration }{ { receiver: config.Receiver{ @@ -48,7 +48,7 @@ func TestBuildReceiverIntegrations(t *testing.T) { }, }, }, - exp: []notify.Integration{ + exp: []*notify.Integration{ notify.NewIntegration(nil, sendResolved(false), "webhook", 0, "foo"), notify.NewIntegration(nil, sendResolved(true), "webhook", 1, "foo"), }, diff --git a/go.mod b/go.mod index 76aa4a2f4b..a8cb53d38c 100644 --- a/go.mod +++ b/go.mod @@ -10,14 +10,14 @@ require ( github.com/cenkalti/backoff/v4 v4.2.1 github.com/cespare/xxhash/v2 v2.2.0 github.com/go-kit/log v0.2.1 - github.com/go-openapi/analysis v0.22.2 - github.com/go-openapi/errors v0.21.0 - github.com/go-openapi/loads v0.21.5 + github.com/go-openapi/analysis v0.23.0 + github.com/go-openapi/errors v0.22.0 + github.com/go-openapi/loads v0.22.0 github.com/go-openapi/runtime v0.27.1 - github.com/go-openapi/spec v0.20.14 - github.com/go-openapi/strfmt v0.22.0 - github.com/go-openapi/swag v0.22.9 - github.com/go-openapi/validate v0.23.0 + github.com/go-openapi/spec v0.21.0 + github.com/go-openapi/strfmt v0.23.0 + github.com/go-openapi/swag v0.23.0 + github.com/go-openapi/validate v0.24.0 github.com/gofrs/uuid v4.4.0+incompatible github.com/gogo/protobuf v1.3.2 github.com/hashicorp/go-sockaddr v1.0.6 @@ -36,7 +36,7 @@ require ( github.com/rs/cors v1.10.1 github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 github.com/xlab/treeprint v1.2.0 go.uber.org/atomic v1.11.0 golang.org/x/mod v0.14.0 @@ -57,11 +57,11 @@ require ( github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.20.2 // indirect - github.com/go-openapi/jsonreference v0.20.4 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/btree v1.0.0 // indirect - github.com/google/uuid v1.5.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-msgpack v0.5.3 // indirect @@ -81,7 +81,7 @@ require ( github.com/prometheus/procfs v0.12.0 // indirect github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect - go.mongodb.org/mongo-driver v1.13.1 // indirect + go.mongodb.org/mongo-driver v1.14.0 // indirect go.opentelemetry.io/otel v1.17.0 // indirect go.opentelemetry.io/otel/metric v1.17.0 // indirect go.opentelemetry.io/otel/trace v1.17.0 // indirect diff --git a/go.sum b/go.sum index 60f1a0a695..fccc070695 100644 --- a/go.sum +++ b/go.sum @@ -154,26 +154,26 @@ github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/analysis v0.22.2 h1:ZBmNoP2h5omLKr/srIC9bfqrUGzT6g6gNv03HE9Vpj0= -github.com/go-openapi/analysis v0.22.2/go.mod h1:pDF4UbZsQTo/oNuRfAWWd4dAh4yuYf//LYorPTjrpvo= -github.com/go-openapi/errors v0.21.0 h1:FhChC/duCnfoLj1gZ0BgaBmzhJC2SL/sJr8a2vAobSY= -github.com/go-openapi/errors v0.21.0/go.mod h1:jxNTMUxRCKj65yb/okJGEtahVd7uvWnuWfj53bse4ho= -github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= -github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= -github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= -github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= -github.com/go-openapi/loads v0.21.5 h1:jDzF4dSoHw6ZFADCGltDb2lE4F6De7aWSpe+IcsRzT0= -github.com/go-openapi/loads v0.21.5/go.mod h1:PxTsnFBoBe+z89riT+wYt3prmSBP6GDAQh2l9H1Flz8= +github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= +github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= +github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w= +github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco= +github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs= github.com/go-openapi/runtime v0.27.1 h1:ae53yaOoh+fx/X5Eaq8cRmavHgDma65XPZuvBqvJYto= github.com/go-openapi/runtime v0.27.1/go.mod h1:fijeJEiEclyS8BRurYE1DE5TLb9/KZl6eAdbzjsrlLU= -github.com/go-openapi/spec v0.20.14 h1:7CBlRnw+mtjFGlPDRZmAMnq35cRzI91xj03HVyUi/Do= -github.com/go-openapi/spec v0.20.14/go.mod h1:8EOhTpBoFiask8rrgwbLC3zmJfz4zsCUueRuPM6GNkw= -github.com/go-openapi/strfmt v0.22.0 h1:Ew9PnEYc246TwrEspvBdDHS4BVKXy/AOVsfqGDgAcaI= -github.com/go-openapi/strfmt v0.22.0/go.mod h1:HzJ9kokGIju3/K6ap8jL+OlGAbjpSv27135Yr9OivU4= -github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZCE= -github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE= -github.com/go-openapi/validate v0.23.0 h1:2l7PJLzCis4YUGEoW6eoQw3WhyM65WSIcjX6SQnlfDw= -github.com/go-openapi/validate v0.23.0/go.mod h1:EeiAZ5bmpSIOJV1WLfyYF9qp/B1ZgSaEpHTJHtN5cbE= +github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= +github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= +github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c= +github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58= +github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= @@ -219,7 +219,6 @@ github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= @@ -262,8 +261,8 @@ github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= @@ -339,7 +338,6 @@ github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4d github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= @@ -390,7 +388,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -483,30 +480,25 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= -github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= -github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+DHwTGEbU= go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= -go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk= -go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo= +go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= +go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -536,10 +528,8 @@ golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -577,7 +567,6 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -628,7 +617,6 @@ golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -665,7 +653,6 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -746,7 +733,6 @@ golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -761,8 +747,6 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -823,7 +807,6 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/notify/notify.go b/notify/notify.go index 30861a3027..64e0147eb4 100644 --- a/notify/notify.go +++ b/notify/notify.go @@ -67,11 +67,16 @@ type Integration struct { name string idx int receiverName string + + mtx sync.RWMutex + lastNotifyAttempt time.Time + lastNotifyAttemptDuration model.Duration + lastNotifyAttemptError error } // NewIntegration returns a new integration. -func NewIntegration(notifier Notifier, rs ResolvedSender, name string, idx int, receiverName string) Integration { - return Integration{ +func NewIntegration(notifier Notifier, rs ResolvedSender, name string, idx int, receiverName string) *Integration { + return &Integration{ notifier: notifier, rs: rs, name: name, @@ -100,6 +105,22 @@ func (i *Integration) Index() int { return i.idx } +func (i *Integration) Report(start time.Time, duration model.Duration, notifyError error) { + i.mtx.Lock() + defer i.mtx.Unlock() + + i.lastNotifyAttempt = start + i.lastNotifyAttemptDuration = duration + i.lastNotifyAttemptError = notifyError +} + +func (i *Integration) GetReport() (time.Time, model.Duration, error) { + i.mtx.RLock() + defer i.mtx.RUnlock() + + return i.lastNotifyAttempt, i.lastNotifyAttemptDuration, i.lastNotifyAttemptError +} + // String implements the Stringer interface. func (i *Integration) String() string { return fmt.Sprintf("%s[%d]", i.name, i.idx) @@ -308,7 +329,7 @@ func NewMetrics(r prometheus.Registerer, ff featurecontrol.Flagger) *Metrics { return m } -func (m *Metrics) InitializeFor(receiver map[string][]Integration) { +func (m *Metrics) InitializeFor(receivers []*Receiver) { if m.ff.EnableReceiverNamesInMetrics() { // Reset the vectors to take into account receiver names changing after hot reloads. @@ -318,8 +339,9 @@ func (m *Metrics) InitializeFor(receiver map[string][]Integration) { m.notificationLatencySeconds.Reset() m.numTotalFailedNotifications.Reset() - for name, integrations := range receiver { - for _, integration := range integrations { + for _, receiver := range receivers { + name := receiver.Name() + for _, integration := range receiver.Integrations() { m.numNotifications.WithLabelValues(integration.Name(), name) m.numNotificationRequestsTotal.WithLabelValues(integration.Name(), name) @@ -376,7 +398,7 @@ func NewPipelineBuilder(r prometheus.Registerer, ff featurecontrol.Flagger) *Pip // New returns a map of receivers to Stages. func (pb *PipelineBuilder) New( - receivers map[string][]Integration, + receivers []*Receiver, wait func() time.Duration, inhibitor *inhibit.Inhibitor, silencer *silence.Silencer, @@ -392,9 +414,9 @@ func (pb *PipelineBuilder) New( tms := NewTimeMuteStage(intervener, pb.metrics) ss := NewMuteStage(silencer, pb.metrics) - for name := range receivers { - st := createReceiverStage(name, receivers[name], wait, notificationLog, pb.metrics) - rs[name] = MultiStage{ms, is, tas, tms, ss, st} + for _, r := range receivers { + st := createReceiverStage(r, wait, notificationLog, pb.metrics) + rs[r.groupName] = MultiStage{ms, is, tas, tms, ss, st} } pb.metrics.InitializeFor(receivers) @@ -404,23 +426,22 @@ func (pb *PipelineBuilder) New( // createReceiverStage creates a pipeline of stages for a receiver. func createReceiverStage( - name string, - integrations []Integration, + receiver *Receiver, wait func() time.Duration, notificationLog NotificationLog, metrics *Metrics, ) Stage { var fs FanoutStage - for i := range integrations { + for i := range receiver.integrations { recv := &nflogpb.Receiver{ - GroupName: name, - Integration: integrations[i].Name(), - Idx: uint32(integrations[i].Index()), + GroupName: receiver.groupName, + Integration: receiver.integrations[i].Name(), + Idx: uint32(receiver.integrations[i].Index()), } var s MultiStage s = append(s, NewWaitStage(wait)) - s = append(s, NewDedupStage(&integrations[i], notificationLog, recv)) - s = append(s, NewRetryStage(integrations[i], name, metrics)) + s = append(s, NewDedupStage(receiver.integrations[i], notificationLog, recv)) + s = append(s, NewRetryStage(receiver.integrations[i], receiver.groupName, metrics)) s = append(s, NewSetNotifiesStage(notificationLog, recv)) fs = append(fs, s) @@ -733,14 +754,14 @@ func (n *DedupStage) Exec(ctx context.Context, _ log.Logger, alerts ...*types.Al // 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 + integration *Integration groupName string metrics *Metrics labelValues []string } // 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) *RetryStage { labelValues := []string{i.Name()} if metrics.ff.EnableReceiverNamesInMetrics() { @@ -834,9 +855,12 @@ func (r RetryStage) exec(ctx context.Context, l log.Logger, alerts ...*types.Ale case <-tick.C: now := time.Now() retry, err := r.integration.Notify(ctx, sent...) - dur := time.Since(now) - r.metrics.notificationLatencySeconds.WithLabelValues(r.labelValues...).Observe(dur.Seconds()) + + duration := time.Since(now) + r.metrics.notificationLatencySeconds.WithLabelValues(r.labelValues...).Observe(duration.Seconds()) r.metrics.numNotificationRequestsTotal.WithLabelValues(r.labelValues...).Inc() + + r.integration.Report(now, model.Duration(duration), err) if err != nil { r.metrics.numNotificationRequestsFailedTotal.WithLabelValues(r.labelValues...).Inc() if !retry { @@ -857,7 +881,7 @@ func (r RetryStage) exec(ctx context.Context, l log.Logger, alerts ...*types.Ale lvl = level.Debug(log.With(l, "alerts", fmt.Sprintf("%v", alerts))) } - lvl.Log("msg", "Notify success", "attempts", i, "duration", dur) + lvl.Log("msg", "Notify success", "attempts", i, "duration", duration) return ctx, alerts, nil } case <-ctx.Done(): diff --git a/notify/notify_test.go b/notify/notify_test.go index d5ce9612e3..6411f1f91d 100644 --- a/notify/notify_test.go +++ b/notify/notify_test.go @@ -381,7 +381,7 @@ func TestRoutingStage(t *testing.T) { func TestRetryStageWithError(t *testing.T) { fail, retry := true, true sent := []*types.Alert{} - i := Integration{ + i := &Integration{ notifier: notifierFunc(func(ctx context.Context, alerts ...*types.Alert) (bool, error) { if fail { fail = false @@ -435,7 +435,7 @@ func TestRetryStageWithErrorCode(t *testing.T) { for _, testData := range testcases { retry := false testData := testData - i := Integration{ + i := &Integration{ name: "test", notifier: notifierFunc(func(ctx context.Context, alerts ...*types.Alert) (bool, error) { if !testData.isNewErrorWithReason { @@ -472,7 +472,7 @@ func TestRetryStageWithErrorCode(t *testing.T) { func TestRetryStageWithContextCanceled(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) - i := Integration{ + i := &Integration{ name: "test", notifier: notifierFunc(func(ctx context.Context, alerts ...*types.Alert) (bool, error) { cancel() @@ -504,7 +504,7 @@ func TestRetryStageWithContextCanceled(t *testing.T) { func TestRetryStageNoResolved(t *testing.T) { sent := []*types.Alert{} - i := Integration{ + i := &Integration{ notifier: notifierFunc(func(ctx context.Context, alerts ...*types.Alert) (bool, error) { sent = append(sent, alerts...) return false, nil @@ -555,7 +555,7 @@ func TestRetryStageNoResolved(t *testing.T) { func TestRetryStageSendResolved(t *testing.T) { sent := []*types.Alert{} - i := Integration{ + i := &Integration{ notifier: notifierFunc(func(ctx context.Context, alerts ...*types.Alert) (bool, error) { sent = append(sent, alerts...) return false, nil diff --git a/notify/receiver.go b/notify/receiver.go new file mode 100644 index 0000000000..d8bc1d0dfc --- /dev/null +++ b/notify/receiver.go @@ -0,0 +1,42 @@ +// Copyright 2022 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 notify + +type Receiver struct { + groupName string + integrations []*Integration + + // A receiver is considered active if a route is using it. + active bool +} + +func (r *Receiver) Name() string { + return r.groupName +} + +func (r *Receiver) Active() bool { + return r.active +} + +func (r *Receiver) Integrations() []*Integration { + return r.integrations +} + +func NewReceiver(name string, active bool, integrations []*Integration) *Receiver { + return &Receiver{ + groupName: name, + active: active, + integrations: integrations, + } +} diff --git a/silence/silence.go b/silence/silence.go index 710323f747..1fea036381 100644 --- a/silence/silence.go +++ b/silence/silence.go @@ -573,12 +573,35 @@ func (s *Silences) setSilence(sil *pb.Silence, now time.Time, skipValidate bool) return nil } +// Upsert allows creating silences with a predefined ID. +func (s *Silences) Upsert(sil *pb.Silence) (string, error) { + s.mtx.Lock() + defer s.mtx.Unlock() + + id, err := s.set(sil) + if !errors.Is(err, ErrNotFound) { + return id, err + } + + // If the silence was not found, create it with the given ID. + now := s.nowUTC() + if sil.StartsAt.Before(now) { + sil.StartsAt = now + } + + return sil.Id, s.setSilence(sil, now, false) +} + // Set the specified silence. If a silence with the ID already exists and the modification // modifies history, the old silence gets expired and a new one is created. func (s *Silences) Set(sil *pb.Silence) (string, error) { s.mtx.Lock() defer s.mtx.Unlock() + return s.set(sil) +} +// set assumes a lock is being held in the calling method. +func (s *Silences) set(sil *pb.Silence) (string, error) { now := s.nowUTC() prev, ok := s.getSilence(sil.Id) diff --git a/silence/silence_test.go b/silence/silence_test.go index 2a880dd3b6..3311869969 100644 --- a/silence/silence_test.go +++ b/silence/silence_test.go @@ -459,6 +459,50 @@ func TestSilenceSet(t *testing.T) { require.Equal(t, want, s.st, "unexpected state after silence creation") } +func TestSilenceUpsert(t *testing.T) { + s, err := New(Options{ + Retention: time.Hour, + }) + require.NoError(t, err) + + clock := clock.NewMock() + s.clock = clock + + // Insert silence with predefined id. + clock.Add(time.Minute) + start := s.nowUTC() + + testID := "some_id" + sil := &pb.Silence{ + Id: testID, + Matchers: []*pb.Matcher{{Name: "a", Pattern: "b"}}, + StartsAt: start.Add(2 * time.Minute), + EndsAt: start.Add(5 * time.Minute), + } + id, err := s.Upsert(sil) + require.NoError(t, err) + require.Equal(t, testID, id) + + want := state{ + id: &pb.MeshSilence{ + Silence: &pb.Silence{ + Id: testID, + Matchers: []*pb.Matcher{{Name: "a", Pattern: "b"}}, + StartsAt: start.Add(2 * time.Minute), + EndsAt: start.Add(5 * time.Minute), + UpdatedAt: start, + }, + ExpiresAt: start.Add(5*time.Minute + s.retention), + }, + } + require.Equal(t, want, s.st, "unexpected state after silence creation") + + // Trying to insert an invalid silence should fail. + clock.Add(time.Minute) + _, err = s.Upsert(&pb.Silence{}) + checkErr(t, "silence invalid", err) +} + func TestSetActiveSilence(t *testing.T) { s, err := New(Options{ Retention: time.Hour,